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

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

Added Encoding and Language options to main.cfg configuration file so
it's now hopefully a little easier to add new languages and encodings
to the interface. Each interface language also now has a default encoding
so that changing languages from the preferences page causes the encoding
to change to a value reasonable for the selected language.

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