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

Last change on this file since 1779 was 1779, checked in by sjboddie, 23 years ago

Implemented LogDateFormat configuration option.

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