source: trunk/gsdl/src/recpt/receptionist.cpp@ 1170

Last change on this file since 1170 was 1170, checked in by sjboddie, 24 years ago

minor modifications to get web library compiling under VC++ 6.0

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 41.8 KB
Line 
1/**********************************************************************
2 *
3 * receptionist.cpp -- a web interface for the gsdl
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 * $Id: receptionist.cpp 1170 2000-05-12 03:09:27Z sjboddie $
25 *
26 *********************************************************************/
27
28/*
29 $Log$
30 Revision 1.50 2000/05/12 03:09:25 sjboddie
31 minor modifications to get web library compiling under VC++ 6.0
32
33 Revision 1.49 2000/05/04 05:18:46 sjboddie
34 attempting to get end-user collection building to work under windows
35
36 Revision 1.48 2000/04/14 02:52:06 sjboddie
37 tidied up error messaging and set up some debugging info to be output
38 when running library from command line
39
40 Revision 1.47 2000/02/17 22:26:17 sjboddie
41 set macros for displaying macrons in utf8
42
43 Revision 1.46 2000/02/17 02:34:09 sjboddie
44 made compressedoptions macro dm_safe - added SCRIPT_NAME to log string
45
46 Revision 1.45 2000/02/03 01:48:52 sjboddie
47 fixed potential bug in ccscols stuff
48
49 Revision 1.44 2000/01/24 22:53:09 sjboddie
50 a few small changes to get fastcgi working properly here at Waikato -
51 hopefully changes will work everywhere ...
52
53 Revision 1.43 1999/12/06 01:14:16 sjboddie
54 added arabic encoding
55
56 Revision 1.42 1999/12/05 21:21:04 sjboddie
57 added support for multiple gsdlhomes and gdbmhomes
58
59 Revision 1.41 1999/11/25 21:44:16 sjboddie
60 fixed bug in logout
61
62 Revision 1.40 1999/11/08 20:26:37 sjboddie
63 added multiplevalue option to cgiarginfo
64
65 Revision 1.39 1999/11/03 22:49:10 sjboddie
66 A location url may now contain macros
67
68 Revision 1.38 1999/11/01 21:49:34 sjboddie
69 changes to arguments of many functions
70
71 Revision 1.37 1999/10/20 03:55:03 sjboddie
72 yet another problem with calling browserclass
73 processOID functions correctly
74
75 Revision 1.36 1999/10/19 03:23:44 davidb
76 Collection building support through web pages
77 and internal and external link handling for collection documents
78
79 Revision 1.35 1999/10/18 20:07:05 sjboddie
80 tidied up a few things - moved processing of "hp" argument
81 to htmlbrowserclass
82
83 Revision 1.34 1999/10/14 23:00:52 sjboddie
84 finished changes to browsing support
85
86 Revision 1.33 1999/10/10 08:14:10 sjboddie
87 - metadata now returns mp rather than array
88 - redesigned browsing support (although it's not finished so
89 won't currently work ;-)
90
91 Revision 1.32 1999/09/21 11:28:45 sjboddie
92 tidied up file locking
93
94 Revision 1.31 1999/09/16 21:38:17 sjboddie
95 added some file locking stuff for logging. Windows still needs to
96 be done.
97
98 Revision 1.30 1999/09/07 04:56:58 sjboddie
99 added GPL notice
100
101 Revision 1.29 1999/09/03 10:02:30 rjmcnab
102 Made the page parameters configurable. Now the page parameters must
103 correspond to cgi arguments in name and value (ie language=zh should now
104 be l=zh) which makes things more consistent anyway. Removed a couple of
105 specialised NZDL page parameters.
106
107 Moved the combining of the cgi arguments so that the receptionist does
108 all the configuration now.
109
110 Made the macro precedence configurable.
111
112 Made cgi arguments totally configurable. Now any piece of information about
113 a cgi argument can be configured meaning that cgi arguments can be declared
114 from the configuration file.
115
116 Removed the argdefault configuration argument. This should now be done
117 using cgiarg.
118
119 Revision 1.28 1999/09/03 04:39:46 rjmcnab
120 Made cookies and logs optional (they are turned off by default). To
121 turn them on put
122
123 usecookies true
124 logcgiargs true
125
126 in your configuration file.
127
128 Revision 1.27 1999/09/02 00:27:21 rjmcnab
129 A few small things.
130
131 Revision 1.26 1999/08/25 04:43:06 sjboddie
132 made FilterRequest_t::docSet an array rather than a set
133
134 Revision 1.25 1999/08/20 00:59:01 sjboddie
135 -fixed up location redirection
136 -added some usage logging, also now set a GSDL_UID cookie. Logging
137 does NOT presently lock the log file while it's in use. That has yet
138 to be done.
139
140 Revision 1.24 1999/08/13 04:16:42 sjboddie
141 added some collection-level metadata stuff
142
143 Revision 1.23 1999/08/11 23:28:59 sjboddie
144 added support for html classifier (i.e. the hp argumant now must be
145 translated too).
146
147 Revision 1.22 1999/08/10 22:45:21 sjboddie
148 format option ShowTopPages is now called DocumentTopPages
149
150 Revision 1.21 1999/08/09 04:25:17 sjboddie
151 moved OID translation stuff from documentaction::define_external_macros
152 to receptionist
153
154 Revision 1.20 1999/07/30 02:13:09 sjboddie
155 -added collectinfo argument to some functions
156 -made some function prototypes virtual
157
158 Revision 1.19 1999/07/15 06:02:05 rjmcnab
159 Moved the setting of argsinfo into the constructor. Added the configuration
160 command argdefault (as used by the actions). Added code to output the
161 correct charset based on the page encoding so that the user does not need
162 to specify the encoding used for a particular page.
163
164 Revision 1.18 1999/07/11 01:05:20 rjmcnab
165 Stored origin of cgiarg with argument.
166
167 Revision 1.17 1999/07/10 22:18:26 rjmcnab
168 Added calls to define_external_cgiargs.
169
170 Revision 1.16 1999/06/27 21:49:03 sjboddie
171 fixed a couple of version conflicts - tidied up some small things
172
173 Revision 1.15 1999/06/26 01:14:32 rjmcnab
174 Made a couple of changes to handle different encodings.
175
176 Revision 1.14 1999/06/09 00:08:36 sjboddie
177 query string macro (_cgiargq_) is now made html safe before being set
178
179 Revision 1.13 1999/06/08 04:29:31 sjboddie
180 added argsinfo to the call to check_cgiargs to make it easy to set
181 args to their default if they're found to be screwed up
182
183 Revision 1.12 1999/04/30 01:59:42 sjboddie
184 lots of stuff - getting documentaction working (documentaction replaces
185 old browseaction)
186
187 Revision 1.11 1999/03/25 03:06:43 sjboddie
188
189 altered receptionist slightly so it now passes *collectproto to
190 define_internal_macros and define_external_macros - need it
191 for browseaction
192
193 Revision 1.10 1999/03/05 03:53:54 sjboddie
194
195 fixed some bugs
196
197 Revision 1.9 1999/02/28 20:00:16 rjmcnab
198
199
200 Fixed a few things.
201
202 Revision 1.8 1999/02/25 21:58:59 rjmcnab
203
204 Merged sources.
205
206 Revision 1.7 1999/02/21 22:33:55 rjmcnab
207
208 Lots of stuff :-)
209
210 Revision 1.6 1999/02/11 01:24:05 rjmcnab
211
212 Fixed a few compiler warnings.
213
214 Revision 1.5 1999/02/08 01:28:02 rjmcnab
215
216 Got the receptionist producing something using the statusaction.
217
218 Revision 1.4 1999/02/05 10:42:46 rjmcnab
219
220 Continued working on receptionist
221
222 Revision 1.3 1999/02/04 10:00:56 rjmcnab
223
224 Developed the idea of an "action" and having them define the cgi arguments
225 which they need and how those cgi arguments function.
226
227 Revision 1.2 1999/02/04 01:17:27 rjmcnab
228
229 Got it outputing something.
230
231
232 */
233
234
235#include "receptionist.h"
236#include "fileutil.h"
237#include "cgiutils.h"
238#include "htmlutils.h"
239#include "gsdltools.h"
240#include "OIDtools.h"
241#include <assert.h>
242#include <time.h>
243#include <stdio.h>
244#if defined (GSDL_USE_IOS_H)
245#include <fstream.h>
246#else
247#include <fstream>
248#endif
249
250#if defined (__WIN32_)
251#include "wincgiutils.h"
252#endif
253
254void recptconf::clear () {
255 gsdlhome.clear();
256 gdbmhome.clear();
257 collectinfo.erase(collectinfo.begin(), collectinfo.end());
258 collection.clear();
259 collectdir.clear();
260 httpprefix.clear();
261 httpimg.clear();
262 gwcgi.clear();
263 macrofiles.erase(macrofiles.begin(), macrofiles.end());
264 saveconf.clear();
265 usecookies = false;
266 logcgiargs = false;
267
268 // these default page parameters can always be overriden
269 // in the configuration file
270 pageparams.erase(pageparams.begin(), pageparams.end());
271 pageparams["c"] = "";
272 pageparams["l"] = "en";
273
274#ifdef MACROPRECEDENCE
275 macroprecedence = MACROPRECEDENCE;
276#else
277 macroprecedence.clear();
278#endif
279}
280
281
282
283receptionist::receptionist () {
284 // create a list of cgi arguments
285 // this must be done before the configuration
286
287 cgiarginfo ainfo;
288
289 ainfo.shortname = "e";
290 ainfo.longname = "compressed arguments";
291 ainfo.multiplechar = true;
292 ainfo.defaultstatus = cgiarginfo::good;
293 ainfo.argdefault = "";
294 ainfo.savedarginfo = cgiarginfo::mustnot;
295 argsinfo.addarginfo (NULL, ainfo);
296
297 ainfo.shortname = "a";
298 ainfo.longname = "action";
299 ainfo.multiplechar = true;
300 ainfo.defaultstatus = cgiarginfo::none;
301 ainfo.argdefault = "";
302 ainfo.savedarginfo = cgiarginfo::must;
303 argsinfo.addarginfo (NULL, ainfo);
304
305 // w=western
306 ainfo.shortname = "w";
307 ainfo.longname = "encoding";
308 ainfo.multiplechar = true;
309 ainfo.defaultstatus = cgiarginfo::weak;
310 ainfo.argdefault = "w";
311 ainfo.savedarginfo = cgiarginfo::must;
312 argsinfo.addarginfo (NULL, ainfo);
313
314 ainfo.shortname = "nw";
315 ainfo.longname = "new encoding";
316 ainfo.multiplechar = true;
317 ainfo.defaultstatus = cgiarginfo::none;
318 ainfo.argdefault = "";
319 ainfo.savedarginfo = cgiarginfo::mustnot;
320 argsinfo.addarginfo (NULL, ainfo);
321
322 ainfo.shortname = "c";
323 ainfo.longname = "collection";
324 ainfo.multiplechar = true;
325 ainfo.defaultstatus = cgiarginfo::none;
326 ainfo.argdefault = "";
327 ainfo.savedarginfo = cgiarginfo::must;
328 argsinfo.addarginfo (NULL, ainfo);
329
330 // the interface language name should use the ISO 639
331 // standard
332 ainfo.shortname = "l";
333 ainfo.longname = "interface language";
334 ainfo.multiplechar = true;
335 ainfo.defaultstatus = cgiarginfo::weak;
336 ainfo.argdefault = "en";
337 ainfo.savedarginfo = cgiarginfo::must;
338 argsinfo.addarginfo (NULL, ainfo);
339
340 // the GSDL_UID (cookie)
341 ainfo.shortname = "z";
342 ainfo.longname = "gsdl uid";
343 ainfo.multiplechar = true;
344 ainfo.defaultstatus = cgiarginfo::none;
345 ainfo.argdefault = "";
346 ainfo.savedarginfo = cgiarginfo::mustnot;
347 argsinfo.addarginfo (NULL, ainfo);
348}
349
350
351void receptionist::add_action (action *theaction) {
352 // make sure we have an action to add
353 if (theaction == NULL) return;
354
355 // add this action to the list of actions
356 actions.addaction(theaction);
357
358 // add the cgi arguments from this action
359 argsinfo.addarginfo (NULL, theaction->getargsinfo());
360}
361
362
363void receptionist::add_browser (browserclass *thebrowser) {
364 // make sure we have a browser to add
365 if (thebrowser == NULL) return;
366
367 // add this browser to the list of browsers
368 browsers.addbrowser(thebrowser);
369}
370
371
372void receptionist::setdefaultbrowser (const text_t &browsername) {
373 browsers.setdefaultbrowser (browsername);
374}
375
376
377// configure should be called for each line in the
378// configuration files to configure the receptionist and everything
379// it contains. The configuration should take place after everything
380// has been added but before the initialisation.
381void receptionist::configure (const text_t &key, const text_tarray &cfgline) {
382 // configure the receptionist
383 if (cfgline.size() >= 1) {
384 cgiarginfo *info = NULL;
385 if (key == "gsdlhome") configinfo.gsdlhome = cfgline[0];
386 else if (key == "collection") {
387 configinfo.collection = cfgline[0];
388 // also need to set the default arg to this collection
389 if ((info = argsinfo.getarginfo("c")) != NULL) {
390 info->defaultstatus = cgiarginfo::good;
391 info->argdefault = cfgline[0];
392 }
393
394 } else if (key == "collectdir") configinfo.collectdir = cfgline[0];
395 else if (key == "httpprefix") configinfo.httpprefix = cfgline[0];
396 else if (key == "httpimg") configinfo.httpimg = cfgline[0];
397 else if (key == "gwcgi") configinfo.gwcgi = cfgline[0];
398 else if (key == "macrofiles") {
399 // want to append to macrofiles (i.e. may be several config files
400 // contributing, maybe from several collections).
401 text_tarray::const_iterator here = cfgline.begin();
402 text_tarray::const_iterator end = cfgline.end();
403 while (here != end) {
404 configinfo.macrofiles.insert (*here);
405 here ++;
406 }
407 }
408 else if (key == "saveconf") configinfo.saveconf = cfgline[0];
409 else if (key == "usecookies") configinfo.usecookies = (cfgline[0] == "true");
410 else if (key == "logcgiargs") configinfo.logcgiargs = (cfgline[0] == "true");
411 else if (key == "pageparam") {
412 if (cfgline.size() >= 2) configinfo.pageparams[cfgline[0]] = cfgline[1];
413 else configinfo.pageparams[cfgline[0]] = "";
414 }
415 else if (key == "macroprecedence") configinfo.macroprecedence = cfgline[0];
416 else if (key == "collectinfo") {
417 if (cfgline.size() >= 3) {
418 collectioninfo_t cinfo;
419 cinfo.gsdl_gsdlhome = cfgline[1];
420 cinfo.gsdl_gdbmhome = cfgline[2];
421 configinfo.collectinfo[cfgline[0]] = cinfo;
422 }
423 }
424
425 else if (key == "cgiarg") {
426 // get shortname
427 bool seen_defaultstatus = false;
428 text_t subkey, subvalue;
429 text_t shortname;
430 text_t::const_iterator cfglinesub_here;
431 text_tarray::const_iterator cfgline_here = cfgline.begin();
432 text_tarray::const_iterator cfgline_end = cfgline.end();
433 while (cfgline_here != cfgline_end) {
434 cfglinesub_here = getdelimitstr((*cfgline_here).begin(),
435 (*cfgline_here).end(), '=', subkey);
436 if (subkey == "shortname") {
437 shortname = substr (cfglinesub_here, (*cfgline_here).end());
438 }
439 cfgline_here++;
440 }
441
442 // if we found the shortname process the line again filling in values
443 if (!shortname.empty()) {
444 cgiarginfo &chinfo = argsinfo[shortname];
445 chinfo.shortname = shortname; // in case this is a new argument
446
447 cfgline_here = cfgline.begin();
448 while (cfgline_here != cfgline_end) {
449 cfglinesub_here = getdelimitstr((*cfgline_here).begin(),
450 (*cfgline_here).end(), '=', subkey);
451 subvalue = substr (cfglinesub_here, (*cfgline_here).end());
452
453 if (subkey == "longname") chinfo.longname = subvalue;
454 else if (subkey == "multiplechar") chinfo.multiplechar = (subvalue == "true");
455 else if (subkey == "defaultstatus") {
456 seen_defaultstatus = true;
457 if (subvalue == "none") chinfo.defaultstatus = cgiarginfo::none;
458 else if (subvalue == "weak") chinfo.defaultstatus = cgiarginfo::weak;
459 else if (subvalue == "good") chinfo.defaultstatus = cgiarginfo::good;
460 else if (subvalue == "config") chinfo.defaultstatus = cgiarginfo::config;
461 else if (subvalue == "imperative") chinfo.defaultstatus = cgiarginfo::imperative;
462 }
463 else if (subkey == "argdefault") {
464 chinfo.argdefault = subvalue;
465 if (!seen_defaultstatus) chinfo.defaultstatus = cgiarginfo::config;
466 }
467 else if (subkey == "savedarginfo") {
468 if (subvalue == "mustnot") chinfo.savedarginfo = cgiarginfo::mustnot;
469 else if (subvalue == "can") chinfo.savedarginfo = cgiarginfo::can;
470 else if (subvalue == "must") chinfo.savedarginfo = cgiarginfo::must;
471 }
472
473 cfgline_here++;
474 }
475 }
476 }
477 }
478
479 // configure the actions
480 actionptrmap::iterator actionhere = actions.begin ();
481 actionptrmap::iterator actionend = actions.end ();
482
483 while (actionhere != actionend) {
484 assert ((*actionhere).second.a != NULL);
485 if ((*actionhere).second.a != NULL)
486 (*actionhere).second.a->configure(key, cfgline);
487
488 actionhere++;
489 }
490
491 // configure the protocols
492 recptprotolistclass::iterator protohere = protocols.begin ();
493 recptprotolistclass::iterator protoend = protocols.end ();
494
495 while (protohere != protoend) {
496 assert ((*protohere).p != NULL);
497 if ((*protohere).p != NULL)
498 (*protohere).p->configure(key, cfgline);
499
500 protohere++;
501 }
502
503 // configure the browsers
504 browserptrmap::iterator browserhere = browsers.begin ();
505 browserptrmap::iterator browserend = browsers.end ();
506
507 while (browserhere != browserend) {
508 assert ((*browserhere).second.b != NULL);
509 if ((*browserhere).second.b != NULL)
510 (*browserhere).second.b->configure(key, cfgline);
511
512 browserhere++;
513 }
514}
515
516
517void receptionist::configure (const text_t &key, const text_t &value) {
518 text_tarray cfgline;
519 cfgline.push_back (value);
520 configure(key, cfgline);
521}
522
523
524// init should be called after all the actions, protocols, and
525// converters have been added to the receptionist and after everything
526// has been configured but before any pages are created.
527// It returns true on success and false on failure. If false is
528// returned getpage should not be called (without producing
529// meaningless output), instead an error page should be
530// produced by the calling code.
531bool receptionist::init (ostream &logout) {
532 // first configure collectdir
533 text_t thecollectdir = configinfo.gsdlhome;
534 if (!configinfo.collection.empty()) {
535 // collection specific mode
536 if (!configinfo.collectdir.empty()) {
537 // has already been configured
538 thecollectdir = configinfo.collectdir;
539 } else {
540 // decide where collectdir is by searching for collect.cfg
541 // look in $GSDLHOME/collect/collection-name/etc/collect.cfg and
542 // then $GSDLHOME/etc/collect.cfg
543 thecollectdir = filename_cat (configinfo.gsdlhome, "collect");
544 thecollectdir = filename_cat (thecollectdir, configinfo.collection);
545 text_t filename = filename_cat (thecollectdir, "etc");
546 filename = filename_cat (filename, "collect.cfg");
547
548 if (!file_exists(filename)) thecollectdir = configinfo.gsdlhome;
549 }
550 }
551 configure("collectdir", thecollectdir);
552
553 // read in the macro files
554 if (!read_macrofiles (logout)) return false;
555
556 // there must be at least one action defined
557 if (actions.empty()) {
558 logout << "Error: no actions have been added to the receptionist\n";
559 return false;
560 }
561
562 // there must be at least one browser defined
563 if (browsers.empty()) {
564 logout << "Error: no browsers have been added to the receptionist\n";
565 return false;
566 }
567
568 // create a saveconf string if there isn't one already
569 if (configinfo.saveconf.empty())
570 configinfo.saveconf = create_save_conf_str (argsinfo, logout);
571
572 // check the saveconf string
573 if (!check_save_conf_str (configinfo.saveconf, argsinfo, logout))
574 return false;
575
576 // set a random seed
577 srand (time(NULL));
578
579 // make the output converters remove all the zero-width spaces
580 convertinfoclass::iterator converthere = converters.begin ();
581 convertinfoclass::iterator convertend = converters.end ();
582 text_t defaultconvertname;
583 while (converthere != convertend) {
584 assert ((*converthere).second.outconverter != NULL);
585 if ((*converthere).second.outconverter != NULL) {
586 (*converthere).second.outconverter->set_rzws(1);
587 if (defaultconvertname.empty())
588 defaultconvertname = (*converthere).second.name;
589 }
590 converthere++;
591 }
592
593 // set default converter if no good one has been defined
594 if (!defaultconvertname.empty()) {
595 cgiarginfo *ainfo = argsinfo.getarginfo ("w");
596 if (ainfo->argdefault != "w") {
597 if ((ainfo != NULL) && (converters.get_outconverter(ainfo->argdefault) == NULL)) {
598 ainfo->defaultstatus = cgiarginfo::good;
599 ainfo->argdefault = defaultconvertname;
600 }
601 }
602 }
603
604 // init the actions
605 actionptrmap::iterator actionhere = actions.begin ();
606 actionptrmap::iterator actionend = actions.end ();
607 while (actionhere != actionend) {
608 if (((*actionhere).second.a == NULL) ||
609 !(*actionhere).second.a->init(logout)) return false;
610 actionhere++;
611 }
612
613 // init the protocols
614 recptprotolistclass::iterator protohere = protocols.begin ();
615 recptprotolistclass::iterator protoend = protocols.end ();
616 while (protohere != protoend) {
617 if (((*protohere).p == NULL) ||
618 !(*protohere).p->init(logout)) return false;
619 protohere++;
620 }
621
622 // init the browsers
623 browserptrmap::iterator browserhere = browsers.begin ();
624 browserptrmap::iterator browserend = browsers.end ();
625 while (browserhere != browserend) {
626 if (((*browserhere).second.b == NULL) ||
627 !(*browserhere).second.b->init(logout)) return false;
628 browserhere++;
629 }
630
631 return true;
632}
633
634
635// parse_cgi_args parses cgi arguments into an argument class.
636// This function should be called for each page request. It returns false
637// if there was a major problem with the cgi arguments.
638bool receptionist::parse_cgi_args (const text_t &argstr, cgiargsclass &args,
639 ostream &logout, text_tmap &fcgienv) {
640 outconvertclass text_t2ascii;
641
642 // get an initial list of cgi arguments
643 args.clear();
644 split_cgi_args (argsinfo, argstr, args);
645
646 // expand the compressed argument (if there was one)
647 if (!expand_save_args (argsinfo, configinfo.saveconf, args, logout)) return false;
648
649 // add the defaults
650 add_default_args (argsinfo, args, logout);
651
652 // get the cookie
653 if (configinfo.usecookies) get_cookie(args["z"], fcgienv);
654
655 // get the input encoding
656 text_t &arg_w = args["w"];
657 inconvertclass defaultinconvert;
658 inconvertclass *inconvert = converters.get_inconverter (arg_w);
659 if (inconvert == NULL) inconvert = &defaultinconvert;
660
661 // see if the next page will have a different encoding
662 if (args.getarg("nw") != NULL) arg_w = args["nw"];
663
664 // convert arguments which aren't in unicode to unicode
665 args_tounicode (args, *inconvert);
666
667
668 // decide on the output conversion class (needed for checking the external
669 // cgi arguments)
670 rzwsoutconvertclass defaultoutconverter;
671 rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w);
672 if (outconverter == NULL) outconverter = &defaultoutconverter;
673 outconverter->reset();
674
675 // check the main cgi arguments
676 if (!check_mainargs (args, logout)) return false;
677
678 // check the arguments for the action
679 action *a = actions.getaction (args["a"]);
680 if (a != NULL) {
681 if (!a->check_cgiargs (argsinfo, args, logout)) return false;
682 } else {
683 // the action was not found!!
684 logout << text_t2ascii << "Error: the action \"" << args["a"]
685 << "\" could not be found.\n";
686 return false;
687 }
688
689 // check external cgi arguments for each action
690 actionptrmap::iterator actionhere = actions.begin ();
691 actionptrmap::iterator actionend = actions.end ();
692 while (actionhere != actionend) {
693 assert ((*actionhere).second.a != NULL);
694 if ((*actionhere).second.a != NULL) {
695 if (!(*actionhere).second.a->check_external_cgiargs (argsinfo, args, *outconverter,
696 configinfo.saveconf, logout))
697 return false;
698 }
699 actionhere++;
700 }
701
702 // the action might have changed but we will assume that
703 // the cgiargs were checked properly when the change was made
704
705 return true;
706}
707
708// returns true if cookie already existed, false
709// if it was generated
710bool receptionist::get_cookie (text_t &cookie, text_tmap &fcgienv) {
711
712 text_t cookiestring = gsdl_getenv ("HTTP_COOKIE", fcgienv);
713
714 text_t::const_iterator end = cookiestring.end();
715 text_t::const_iterator here = findchar (cookiestring.begin(), end, 'G');
716
717 while (here+9 < end) {
718
719 if (substr(here, here+8) == "GSDL_UID") {
720 cookie = substr (here+9, findchar (here+9, end, ';'));
721 return true;
722 }
723 here = findchar (cookiestring.begin(), end, 'G');
724 }
725
726 cookie.clear();
727 text_t host = gsdl_getenv("REMOTE_ADDR", fcgienv);
728 time_t ttime = time(NULL);
729 if (!host.empty()) {
730 cookie += host;
731 cookie.push_back ('-');
732 }
733 cookie += text_t(ttime);
734
735 return false;
736}
737
738// as above but just tests if cookie exists
739bool receptionist::get_cookie (text_tmap &fcgienv) {
740
741 text_t c = gsdl_getenv("HTTP_COOKIE", fcgienv);
742 if (!c.empty()) {
743 text_t cookiestring = c;
744
745 text_t::const_iterator end = cookiestring.end();
746 text_t::const_iterator here = findchar (cookiestring.begin(), end, 'G');
747
748 while (here+9 < end) {
749 if (substr(here, here+8) == "GSDL_UID") return true;
750 here = findchar (cookiestring.begin(), end, 'G');
751 }
752 }
753 return false;
754}
755
756bool receptionist::log_cgi_args (cgiargsclass &args, ostream &logout, text_tmap &fcgienv) {
757
758 // see if we want to log the cgi arguments
759 if (!configinfo.logcgiargs) return true;
760
761 text_t host = gsdl_getenv ("REMOTE_HOST", fcgienv);
762 text_t script_name = gsdl_getenv ("SCRIPT_NAME", fcgienv);
763 if (host.empty()) host = gsdl_getenv ("REMOTE_ADDR", fcgienv);
764 text_t browser = gsdl_getenv ("HTTP_USER_AGENT", fcgienv);
765 time_t ttime = time(NULL);
766
767 cgiargsclass::const_iterator args_here = args.begin();
768 cgiargsclass::const_iterator args_end = args.end();
769
770 text_t argstr;
771 bool first = true;
772 while (args_here != args_end) {
773 if (!first) argstr += ", ";
774 argstr += (*args_here).first + "=" + (*args_here).second.value;
775 first = false;
776 args_here ++;
777 }
778
779 text_t logfile = filename_cat (configinfo.gsdlhome, "etc");
780 logfile = filename_cat (logfile, "usage.txt");
781
782 text_t logstr = script_name;
783 logstr += " " + host;
784 logstr += " [";
785 logstr += ttime;
786 logstr += "] (" + argstr + ") \"";
787 logstr += browser;
788 logstr += "\"\n";
789
790 return append_logstr (logfile, logstr, logout);
791}
792
793bool receptionist::append_logstr (const text_t &filename, const text_t &logstr,
794 ostream &logout) {
795
796 utf8outconvertclass text_t2utf8;
797 char *lfile = filename.getcstr();
798
799 ofstream log (lfile, ios::app);
800
801 if (!log) {
802 logout << "Error: Couldn't open file " << lfile << "\n";
803 delete lfile;
804 return false;
805 }
806
807 int fd = GSDL_GET_FILEDESC(log);
808
809 // lock_val is set to 0 if file is locked successfully
810 int lock_val = 1;
811 GSDL_LOCK_FILE (fd);
812 if (lock_val == 0) {
813 log << text_t2utf8 << logstr;
814 GSDL_UNLOCK_FILE (fd);
815 } else {
816 logout << "Error: Couldn't lock file " << lfile << "\n";
817 log.close();
818 delete lfile;
819 return false;
820 }
821
822 log.close();
823
824 delete lfile;
825 return true;
826}
827
828text_t receptionist::expandmacros (const text_t &astring, cgiargsclass &args,
829 ostream &logout) {
830 text_t outstring;
831 outconvertclass text_t2ascii;
832
833 action *a = actions.getaction (args["a"]);
834 prepare_page (a, args, text_t2ascii, logout);
835 disp.expandstring ("Global", astring, outstring);
836 return outstring;
837}
838
839// produce_cgi_page will call get_cgihead_info and
840// produce_content in the appropriate way to output a cgi header and
841// the page content (if needed). If a page could not be created it
842// will return false
843bool receptionist::produce_cgi_page (cgiargsclass &args, ostream &contentout,
844 ostream &logout, text_tmap &fcgienv) {
845 outconvertclass text_t2ascii;
846
847 response_t response;
848 text_t response_data;
849
850 // produce cgi header
851 get_cgihead_info (args, response, response_data, logout, fcgienv);
852 if (response == location) {
853 // location response (url may contain macros!!)
854 response_data = expandmacros (response_data, args, logout);
855 contentout << text_t2ascii << "Location: " << response_data << "\n\n";
856 contentout << flush;
857 return true;
858 } else if (response == content) {
859 // content response
860 contentout << text_t2ascii << "Content-type: " << response_data << "\n\n";
861 } else {
862 // unknown response
863 logout << "Error: get_cgihead_info returned an unknown response type.\n";
864 return false;
865 }
866
867 // produce cgi page
868 if (!produce_content (args, contentout, logout)) return false;
869
870 // flush contentout
871 contentout << flush;
872 return true;
873}
874
875
876// get_cgihead_info determines the cgi header information for
877// a set of cgi arguments. If response contains location then
878// response_data contains the redirect address. If reponse
879// contains content then reponse_data contains the content-type.
880// Note that images can now be produced by the receptionist.
881void receptionist::get_cgihead_info (cgiargsclass &args, response_t &response,
882 text_t &response_data, ostream &logout,
883 text_tmap &fcgienv) {
884 outconvertclass text_t2ascii;
885
886 // get the action
887 action *a = actions.getaction (args["a"]);
888 if (a != NULL) {
889 a->get_cgihead_info (args, &protocols, response, response_data, logout);
890
891 } else {
892 // the action was not found!!
893 logout << text_t2ascii << "Error receptionist::get_cgihead_info: the action \""
894 << args["a"] << "\" could not be found.\n";
895 response = content;
896 response_data = "text/html";
897 }
898
899 // add the encoding information
900 if (response == content) {
901 if (args["w"] == "u") {
902 response_data += "; charset=UTF-8";
903 } else if (args["w"] == "g") {
904 response_data += "; charset=GBK";
905 } else if (args["w"] == "a") {
906 response_data += "; charset=windows-1256";
907 } else {
908 response_data += "; charset=ISO-8859-1";
909 }
910
911 // add cookie if required
912 if (configinfo.usecookies && !get_cookie(fcgienv))
913 response_data += "\nSet-Cookie: GSDL_UID=" + args["z"]
914 + "; expires=25-Dec-37 00:00:00 GMT";
915 }
916}
917
918
919// produce the page content
920bool receptionist::produce_content (cgiargsclass &args, ostream &contentout,
921 ostream &logout) {
922
923 // decide on the output conversion class
924 text_t &arg_w = args["w"];
925 rzwsoutconvertclass defaultoutconverter;
926 rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w);
927 if (outconverter == NULL) outconverter = &defaultoutconverter;
928 outconverter->reset();
929
930
931 recptproto *collectproto = protocols.getrecptproto (args["c"], logout);
932 if (collectproto != NULL) {
933 // get browsers to process OID
934 text_t OID = args["d"];
935 if (OID.empty()) OID = args["cl"];
936 if (!OID.empty()) {
937 text_tset metadata;
938 text_tarray OIDs;
939 OIDs.push_back (OID);
940 if (!is_top(OID)) OIDs.push_back (OID + ".pr");
941 FilterResponse_t response;
942 metadata.insert ("childtype");
943 if (get_info (OIDs, args["c"], metadata, false, collectproto, response, logout)) {
944 text_t classifytype;
945 if (!response.docInfo[0].metadata["childtype"].values[0].empty())
946 classifytype = response.docInfo[0].metadata["childtype"].values[0];
947 else if (!is_top (OID)) {
948 if (!response.docInfo[1].metadata["childtype"].values[0].empty())
949 classifytype = response.docInfo[1].metadata["childtype"].values[0];
950 }
951 browserclass *b = browsers.getbrowser (classifytype);
952 b->processOID (args, collectproto, logout);
953 }
954 }
955
956 // translate "d" and "cl" arguments if required
957 translate_OIDs (args, collectproto, logout);
958 }
959
960 // produce the page using the desired action
961 action *a = actions.getaction (args["a"]);
962 if (a != NULL) {
963 if (a->uses_display(args)) prepare_page (a, args, (*outconverter), logout);
964 if (!a->do_action (args, &protocols, &browsers, disp, (*outconverter), contentout, logout))
965 return false;
966
967 } else {
968 // the action was not found!!
969 outconvertclass text_t2ascii;
970
971 logout << text_t2ascii << "Error receptionist::produce_content: the action \""
972 << args["a"] << "\" could not be found.\n";
973
974 contentout << (*outconverter)
975 << "<html>\n"
976 << "<head>\n"
977 << "<title>Error</title>\n"
978 << "</head>\n"
979 << "<body>\n"
980 << "<h2>Oops!</h2>\n"
981 << "Undefined Page. The action \""
982 << args["a"] << "\" could not be found.\n"
983 << "</body>\n"
984 << "</html>\n";
985 }
986 return true;
987}
988
989
990// returns the compressed argument ("e") corresponding to the argument
991// list. This can be used to save preferences between sessions.
992text_t receptionist::get_compressed_arg (cgiargsclass &args, ostream &logout) {
993 // decide on the output conversion class
994 text_t &arg_w = args["w"];
995 rzwsoutconvertclass defaultoutconverter;
996 rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w);
997 if (outconverter == NULL) outconverter = &defaultoutconverter;
998 outconverter->reset();
999
1000 text_t compressed_args;
1001 if (compress_save_args (argsinfo, configinfo.saveconf, args,
1002 compressed_args, *outconverter, logout))
1003 return compressed_args;
1004
1005 return "";
1006}
1007
1008
1009// will read in all the macro files. If one is not found an
1010// error message will be written to logout and the method will
1011// return false.
1012bool receptionist::read_macrofiles (ostream &logout) {
1013 outconvertclass text_t2ascii;
1014
1015 // redirect the error output to logout
1016 ostream *savedlogout = disp.setlogout (&logout);
1017
1018 // load up the default macro files, the collection directory
1019 // is searched first for the file (if this is being used in
1020 // collection specific mode) and then the main directory(s)
1021 text_t colmacrodir = filename_cat (configinfo.collectdir, "macros");
1022
1023 text_tset maindirs;
1024 text_t gsdlmacrodir = filename_cat (configinfo.gsdlhome, "macros");
1025 maindirs.insert (gsdlmacrodir);
1026 colinfo_tmap::iterator colhere = configinfo.collectinfo.begin();
1027 colinfo_tmap::iterator colend = configinfo.collectinfo.end();
1028 while (colhere != colend) {
1029 gsdlmacrodir = filename_cat ((*colhere).second.gsdl_gsdlhome, "macros");
1030 maindirs.insert (gsdlmacrodir);
1031 colhere ++;
1032 }
1033
1034 text_tset::iterator arrhere = configinfo.macrofiles.begin();
1035 text_tset::iterator arrend = configinfo.macrofiles.end();
1036 text_t filename;
1037 while (arrhere != arrend) {
1038 bool foundfile = false;
1039
1040 // try in the collection directory if this is being
1041 // run in collection specific mode
1042 if (!configinfo.collection.empty()) {
1043 filename = filename_cat (colmacrodir, *arrhere);
1044 if (file_exists (filename)) {
1045 disp.loaddefaultmacros(filename);
1046 foundfile = true;
1047 }
1048 }
1049
1050 // if we haven't found the macro file yet try in
1051 // the main macro directory(s)
1052 // if file is found in more than one main directory
1053 // we'll load all copies
1054 if (!foundfile) {
1055 text_tset::const_iterator dirhere = maindirs.begin();
1056 text_tset::const_iterator dirend = maindirs.end();
1057 while (dirhere != dirend) {
1058 filename = filename_cat (*dirhere, *arrhere);
1059 if (file_exists (filename)) {
1060 disp.loaddefaultmacros(filename);
1061 foundfile = true;
1062 }
1063 dirhere ++;
1064 }
1065 }
1066
1067 // see if we found the file or not
1068 if (!foundfile) {
1069 logout << text_t2ascii
1070 << "Error: the macro file \"" << *arrhere << "\" could not be found.\n";
1071 if (configinfo.collection.empty()) {
1072 text_t dirs;
1073 joinchar (maindirs, ", ", dirs);
1074 logout << text_t2ascii
1075 << "It should be in either of the following directories ("
1076 << dirs << ").\n\n";
1077
1078 } else {
1079 logout << text_t2ascii
1080 << "It should be in either " << colmacrodir << " or in "
1081 << gsdlmacrodir << ".\n\n";
1082 }
1083 // reset logout to what it was
1084 disp.setlogout (savedlogout);
1085 return false;
1086 }
1087 arrhere++;
1088 }
1089
1090 // success
1091
1092 // reset logout to what it was
1093 disp.setlogout (savedlogout);
1094 return true;
1095}
1096
1097
1098// check_mainargs will check all the main arguments. If a major
1099// error is found it will return false and no cgi page should
1100// be created using the arguments.
1101bool receptionist::check_mainargs (cgiargsclass &args, ostream &logout) {
1102 // if this receptionist is running in collection dependant mode
1103 // then it should always set the collection argument to the
1104 // collection
1105 if (!configinfo.collection.empty()) args["c"] = configinfo.collection;
1106
1107 // if current collection uses ccscols make sure
1108 // "ccs" argument is set and make "cc" default to
1109 // all collections in "ccs"
1110 if (!args["c"].empty()) {
1111
1112 text_t &arg_c = args["c"];
1113 ColInfoResponse_t cinfo;
1114 comerror_t err;
1115 recptproto *collectproto = protocols.getrecptproto (arg_c, logout);
1116 collectproto->get_collectinfo (arg_c, cinfo, err, logout);
1117
1118 if (!cinfo.ccsCols.empty()) {
1119 args["ccs"] = 1;
1120 if (args["cc"].empty()) {
1121 text_tarray::const_iterator col_here = cinfo.ccsCols.begin();
1122 text_tarray::const_iterator col_end = cinfo.ccsCols.end();
1123 bool first = true;
1124 while (col_here != col_end) {
1125 // make sure it's a valid collection
1126 if (protocols.getrecptproto (*col_here, logout) != NULL) {
1127 if (!first) args["cc"].push_back (',');
1128 args["cc"] += *col_here;
1129 first = false;
1130 }
1131 col_here ++;
1132 }
1133 }
1134 }
1135 }
1136
1137 // argument "v" can only be 0 or 1. Use the default value
1138 // if it is out of range
1139 int arg_v = args.getintarg ("v");
1140 if (arg_v != 0 && arg_v != 1) {
1141 cgiarginfo *vinfo = argsinfo.getarginfo ("v");
1142 if (vinfo != NULL) args["v"] = vinfo->argdefault;
1143 }
1144
1145 // argument "f" can only be 0 or 1. Use the default value
1146 // if it is out of range
1147 int arg_f = args.getintarg ("f");
1148 if (arg_f != 0 && arg_f != 1) {
1149 cgiarginfo *finfo = argsinfo.getarginfo ("f");
1150 if (finfo != NULL) args["f"] = finfo->argdefault;
1151 }
1152
1153 return true;
1154}
1155
1156// translate_OIDs translates the "d" and "cl" arguments to their correct values
1157// if they use the tricky ".fc", ".lc" type syntax.
1158void receptionist::translate_OIDs (cgiargsclass &args, recptproto *collectproto,
1159 ostream &logout) {
1160
1161 FilterResponse_t response;
1162 FilterRequest_t request;
1163 comerror_t err;
1164 text_t &arg_d = args["d"];
1165 text_t &arg_cl = args["cl"];
1166 text_t &collection = args["c"];
1167
1168 // do a call to translate OIDs if required
1169 request.filterName = "NullFilter";
1170 request.filterResultOptions = FROID;
1171 if (!arg_d.empty() && needs_translating (arg_d)) {
1172 request.docSet.push_back (arg_d);
1173 collectproto->filter (collection, request, response, err, logout);
1174 arg_d = response.docInfo[0].OID;
1175 request.clear();
1176 }
1177 // we'll also check here that the "cl" argument has a "classify" doctype
1178 // (in case ".fc" or ".lc" have screwed up)
1179 if (needs_translating (arg_cl)) {
1180 request.fields.insert ("doctype");
1181 request.docSet.push_back (arg_cl);
1182 request.filterResultOptions = FRmetadata;
1183 collectproto->filter (collection, request, response, err, logout);
1184 // set to original value (without .xx stuff) if doctype isn't "classify"
1185 if (response.docInfo[0].metadata["doctype"].values[0] != "classify")
1186 strip_suffix (arg_cl);
1187 else
1188 arg_cl = response.docInfo[0].OID;
1189 }
1190}
1191
1192// prepare_page sets up page parameters, sets display macros
1193// and opens the page ready for output
1194void receptionist::prepare_page (action *a, cgiargsclass &args,
1195 outconvertclass &outconvert,
1196 ostream &logout) {
1197 // set up page parameters
1198 text_t pageparams;
1199 bool first = true;
1200
1201 text_tmap::iterator params_here = configinfo.pageparams.begin();
1202 text_tmap::iterator params_end = configinfo.pageparams.end();
1203 while (params_here != params_end) {
1204 if (args[(*params_here).first] != (*params_here).second) {
1205 if (!first) pageparams += ",";
1206 first = false;
1207 pageparams += (*params_here).first;
1208 pageparams += "=";
1209 pageparams += args[(*params_here).first];
1210 }
1211
1212 params_here++;
1213 }
1214
1215
1216 // open the page
1217 disp.openpage(pageparams, configinfo.macroprecedence);
1218
1219
1220 // define external macros for each action
1221 actionptrmap::iterator actionhere = actions.begin ();
1222 actionptrmap::iterator actionend = actions.end ();
1223
1224 while (actionhere != actionend) {
1225 assert ((*actionhere).second.a != NULL);
1226 if ((*actionhere).second.a != NULL)
1227 (*actionhere).second.a->define_external_macros (disp, args, &protocols, logout);
1228 actionhere++;
1229 }
1230
1231 // define internal macros for the current action
1232 a->define_internal_macros (disp, args, &protocols, logout);
1233
1234 // define general macros. the defining of general macros is done here so that
1235 // the last possible version of the cgi arguments are used
1236 define_general_macros (args, outconvert, logout);
1237}
1238
1239void receptionist::define_general_macros (cgiargsclass &args, outconvertclass &/*outconvert*/,
1240 ostream &logout) {
1241
1242 text_t &collection = args["c"];
1243
1244 disp.setmacro ("gsdlhome", "Global", dm_safe(configinfo.gsdlhome));
1245 disp.setmacro ("gwcgi", "Global", configinfo.gwcgi);
1246 disp.setmacro ("httpimg", "Global", configinfo.httpimg);
1247 disp.setmacro ("httpprefix", "Global", configinfo.httpprefix);
1248 text_t compressedoptions = get_compressed_arg(args, logout);
1249 disp.setmacro ("compressedoptions", "Global", dm_safe(compressedoptions));
1250 // need a decoded version of compressedoptions for use within forms
1251 // as browsers encode values from forms before sending to server
1252 // (e.g. %25 becomes %2525)
1253 decode_cgi_arg (compressedoptions);
1254 disp.setmacro ("decodedcompressedoptions", "Global", dm_safe(compressedoptions));
1255
1256 // set macron macros if encoding is utf8
1257 if (args["w"] == "u") {
1258 disp.setmacro ("Amn", "Global", "&#256;");
1259 disp.setmacro ("amn", "Global", "&#257;");
1260 disp.setmacro ("Emn", "Global", "&#274;");
1261 disp.setmacro ("emn", "Global", "&#275;");
1262 disp.setmacro ("Imn", "Global", "&#298;");
1263 disp.setmacro ("imn", "Global", "&#299;");
1264 disp.setmacro ("Omn", "Global", "&#332;");
1265 disp.setmacro ("omn", "Global", "&#333;");
1266 disp.setmacro ("Umn", "Global", "&#362;");
1267 disp.setmacro ("umn", "Global", "&#363;");
1268 }
1269
1270 // set _cgiargX_ macros for each cgi argument
1271 cgiargsclass::const_iterator argshere = args.begin();
1272 cgiargsclass::const_iterator argsend = args.end();
1273 while (argshere != argsend) {
1274 if (((*argshere).first == "q") ||
1275 ((*argshere).first == "qa") ||
1276 ((*argshere).first == "qtt") ||
1277 ((*argshere).first == "qty") ||
1278 ((*argshere).first == "qp") ||
1279 ((*argshere).first == "qpl") ||
1280 ((*argshere).first == "qr") ||
1281 ((*argshere).first == "q2"))
1282 // need to escape special characters from query string
1283 disp.setmacro ("cgiarg" + (*argshere).first,
1284 "Global", html_safe((*argshere).second.value));
1285 else
1286 disp.setmacro ("cgiarg" + (*argshere).first, "Global", dm_safe((*argshere).second.value));
1287 argshere ++;
1288 }
1289
1290 // display text right to left if language is arabic (and if browser can support it)
1291 if (args["l"] == "ar")
1292 disp.setmacro ("htmlextra", "Global", " dir=rtl");
1293
1294 // set collection specific macros
1295 if (!collection.empty()) {
1296 recptproto *collectproto = protocols.getrecptproto (collection, logout);
1297 if (collectproto != NULL) {
1298 FilterResponse_t response;
1299 text_tset metadata;
1300 get_info ("collection", collection, metadata, false,
1301 collectproto, response, logout);
1302
1303 if (!response.docInfo[0].metadata.empty()) {
1304 MetadataInfo_tmap::const_iterator here = response.docInfo[0].metadata.begin();
1305 MetadataInfo_tmap::const_iterator end = response.docInfo[0].metadata.end();
1306 while (here != end) {
1307 if (((*here).first != "haschildren") && ((*here).first != "hasnext") &&
1308 ((*here).first != "hasprevious")) {
1309 disp.setmacro ((*here).first, "Global", (*here).second.values[0]);
1310 }
1311 here ++;
1312 }
1313 }
1314 }
1315 }
1316}
Note: See TracBrowser for help on using the repository browser.