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

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

attempting to get end-user collection building to work under windows

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