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

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

fixed potential bug in ccscols stuff

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