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

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

a few small changes to get fastcgi working properly here at Waikato -
hopefully changes will work everywhere ...

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