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

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

a few small changes to get an initial release of the local library

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