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

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

fixed bug causing segmentation fault when an invalid collection
was supplied as the "c" cgi argument

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