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

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

set macros for displaying macrons in utf8

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