/********************************************************************** * * receptionist.cpp -- a web interface for the gsdl * Copyright (C) 1999 The New Zealand Digital Library Project * * A component of the Greenstone digital library software * from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: receptionist.cpp 1170 2000-05-12 03:09:27Z sjboddie $ * *********************************************************************/ /* $Log$ Revision 1.50 2000/05/12 03:09:25 sjboddie minor modifications to get web library compiling under VC++ 6.0 Revision 1.49 2000/05/04 05:18:46 sjboddie attempting to get end-user collection building to work under windows Revision 1.48 2000/04/14 02:52:06 sjboddie tidied up error messaging and set up some debugging info to be output when running library from command line Revision 1.47 2000/02/17 22:26:17 sjboddie set macros for displaying macrons in utf8 Revision 1.46 2000/02/17 02:34:09 sjboddie made compressedoptions macro dm_safe - added SCRIPT_NAME to log string Revision 1.45 2000/02/03 01:48:52 sjboddie fixed potential bug in ccscols stuff Revision 1.44 2000/01/24 22:53:09 sjboddie a few small changes to get fastcgi working properly here at Waikato - hopefully changes will work everywhere ... Revision 1.43 1999/12/06 01:14:16 sjboddie added arabic encoding Revision 1.42 1999/12/05 21:21:04 sjboddie added support for multiple gsdlhomes and gdbmhomes Revision 1.41 1999/11/25 21:44:16 sjboddie fixed bug in logout Revision 1.40 1999/11/08 20:26:37 sjboddie added multiplevalue option to cgiarginfo Revision 1.39 1999/11/03 22:49:10 sjboddie A location url may now contain macros Revision 1.38 1999/11/01 21:49:34 sjboddie changes to arguments of many functions Revision 1.37 1999/10/20 03:55:03 sjboddie yet another problem with calling browserclass processOID functions correctly Revision 1.36 1999/10/19 03:23:44 davidb Collection building support through web pages and internal and external link handling for collection documents Revision 1.35 1999/10/18 20:07:05 sjboddie tidied up a few things - moved processing of "hp" argument to htmlbrowserclass Revision 1.34 1999/10/14 23:00:52 sjboddie finished changes to browsing support Revision 1.33 1999/10/10 08:14:10 sjboddie - metadata now returns mp rather than array - redesigned browsing support (although it's not finished so won't currently work ;-) Revision 1.32 1999/09/21 11:28:45 sjboddie tidied up file locking Revision 1.31 1999/09/16 21:38:17 sjboddie added some file locking stuff for logging. Windows still needs to be done. Revision 1.30 1999/09/07 04:56:58 sjboddie added GPL notice Revision 1.29 1999/09/03 10:02:30 rjmcnab Made the page parameters configurable. Now the page parameters must correspond to cgi arguments in name and value (ie language=zh should now be l=zh) which makes things more consistent anyway. Removed a couple of specialised NZDL page parameters. Moved the combining of the cgi arguments so that the receptionist does all the configuration now. Made the macro precedence configurable. Made cgi arguments totally configurable. Now any piece of information about a cgi argument can be configured meaning that cgi arguments can be declared from the configuration file. Removed the argdefault configuration argument. This should now be done using cgiarg. Revision 1.28 1999/09/03 04:39:46 rjmcnab Made cookies and logs optional (they are turned off by default). To turn them on put usecookies true logcgiargs true in your configuration file. Revision 1.27 1999/09/02 00:27:21 rjmcnab A few small things. Revision 1.26 1999/08/25 04:43:06 sjboddie made FilterRequest_t::docSet an array rather than a set Revision 1.25 1999/08/20 00:59:01 sjboddie -fixed up location redirection -added some usage logging, also now set a GSDL_UID cookie. Logging does NOT presently lock the log file while it's in use. That has yet to be done. Revision 1.24 1999/08/13 04:16:42 sjboddie added some collection-level metadata stuff Revision 1.23 1999/08/11 23:28:59 sjboddie added support for html classifier (i.e. the hp argumant now must be translated too). Revision 1.22 1999/08/10 22:45:21 sjboddie format option ShowTopPages is now called DocumentTopPages Revision 1.21 1999/08/09 04:25:17 sjboddie moved OID translation stuff from documentaction::define_external_macros to receptionist Revision 1.20 1999/07/30 02:13:09 sjboddie -added collectinfo argument to some functions -made some function prototypes virtual Revision 1.19 1999/07/15 06:02:05 rjmcnab Moved the setting of argsinfo into the constructor. Added the configuration command argdefault (as used by the actions). Added code to output the correct charset based on the page encoding so that the user does not need to specify the encoding used for a particular page. Revision 1.18 1999/07/11 01:05:20 rjmcnab Stored origin of cgiarg with argument. Revision 1.17 1999/07/10 22:18:26 rjmcnab Added calls to define_external_cgiargs. Revision 1.16 1999/06/27 21:49:03 sjboddie fixed a couple of version conflicts - tidied up some small things Revision 1.15 1999/06/26 01:14:32 rjmcnab Made a couple of changes to handle different encodings. Revision 1.14 1999/06/09 00:08:36 sjboddie query string macro (_cgiargq_) is now made html safe before being set Revision 1.13 1999/06/08 04:29:31 sjboddie added argsinfo to the call to check_cgiargs to make it easy to set args to their default if they're found to be screwed up Revision 1.12 1999/04/30 01:59:42 sjboddie lots of stuff - getting documentaction working (documentaction replaces old browseaction) Revision 1.11 1999/03/25 03:06:43 sjboddie altered receptionist slightly so it now passes *collectproto to define_internal_macros and define_external_macros - need it for browseaction Revision 1.10 1999/03/05 03:53:54 sjboddie fixed some bugs Revision 1.9 1999/02/28 20:00:16 rjmcnab Fixed a few things. Revision 1.8 1999/02/25 21:58:59 rjmcnab Merged sources. Revision 1.7 1999/02/21 22:33:55 rjmcnab Lots of stuff :-) Revision 1.6 1999/02/11 01:24:05 rjmcnab Fixed a few compiler warnings. Revision 1.5 1999/02/08 01:28:02 rjmcnab Got the receptionist producing something using the statusaction. Revision 1.4 1999/02/05 10:42:46 rjmcnab Continued working on receptionist Revision 1.3 1999/02/04 10:00:56 rjmcnab Developed the idea of an "action" and having them define the cgi arguments which they need and how those cgi arguments function. Revision 1.2 1999/02/04 01:17:27 rjmcnab Got it outputing something. */ #include "receptionist.h" #include "fileutil.h" #include "cgiutils.h" #include "htmlutils.h" #include "gsdltools.h" #include "OIDtools.h" #include #include #include #if defined (GSDL_USE_IOS_H) #include #else #include #endif #if defined (__WIN32_) #include "wincgiutils.h" #endif void recptconf::clear () { gsdlhome.clear(); gdbmhome.clear(); collectinfo.erase(collectinfo.begin(), collectinfo.end()); collection.clear(); collectdir.clear(); httpprefix.clear(); httpimg.clear(); gwcgi.clear(); macrofiles.erase(macrofiles.begin(), macrofiles.end()); saveconf.clear(); usecookies = false; logcgiargs = false; // these default page parameters can always be overriden // in the configuration file pageparams.erase(pageparams.begin(), pageparams.end()); pageparams["c"] = ""; pageparams["l"] = "en"; #ifdef MACROPRECEDENCE macroprecedence = MACROPRECEDENCE; #else macroprecedence.clear(); #endif } receptionist::receptionist () { // create a list of cgi arguments // this must be done before the configuration cgiarginfo ainfo; ainfo.shortname = "e"; ainfo.longname = "compressed arguments"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::good; ainfo.argdefault = ""; ainfo.savedarginfo = cgiarginfo::mustnot; argsinfo.addarginfo (NULL, ainfo); ainfo.shortname = "a"; ainfo.longname = "action"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::none; ainfo.argdefault = ""; ainfo.savedarginfo = cgiarginfo::must; argsinfo.addarginfo (NULL, ainfo); // w=western ainfo.shortname = "w"; ainfo.longname = "encoding"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::weak; ainfo.argdefault = "w"; ainfo.savedarginfo = cgiarginfo::must; argsinfo.addarginfo (NULL, ainfo); ainfo.shortname = "nw"; ainfo.longname = "new encoding"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::none; ainfo.argdefault = ""; ainfo.savedarginfo = cgiarginfo::mustnot; argsinfo.addarginfo (NULL, ainfo); ainfo.shortname = "c"; ainfo.longname = "collection"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::none; ainfo.argdefault = ""; ainfo.savedarginfo = cgiarginfo::must; argsinfo.addarginfo (NULL, ainfo); // the interface language name should use the ISO 639 // standard ainfo.shortname = "l"; ainfo.longname = "interface language"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::weak; ainfo.argdefault = "en"; ainfo.savedarginfo = cgiarginfo::must; argsinfo.addarginfo (NULL, ainfo); // the GSDL_UID (cookie) ainfo.shortname = "z"; ainfo.longname = "gsdl uid"; ainfo.multiplechar = true; ainfo.defaultstatus = cgiarginfo::none; ainfo.argdefault = ""; ainfo.savedarginfo = cgiarginfo::mustnot; argsinfo.addarginfo (NULL, ainfo); } void receptionist::add_action (action *theaction) { // make sure we have an action to add if (theaction == NULL) return; // add this action to the list of actions actions.addaction(theaction); // add the cgi arguments from this action argsinfo.addarginfo (NULL, theaction->getargsinfo()); } void receptionist::add_browser (browserclass *thebrowser) { // make sure we have a browser to add if (thebrowser == NULL) return; // add this browser to the list of browsers browsers.addbrowser(thebrowser); } void receptionist::setdefaultbrowser (const text_t &browsername) { browsers.setdefaultbrowser (browsername); } // configure should be called for each line in the // configuration files to configure the receptionist and everything // it contains. The configuration should take place after everything // has been added but before the initialisation. void receptionist::configure (const text_t &key, const text_tarray &cfgline) { // configure the receptionist if (cfgline.size() >= 1) { cgiarginfo *info = NULL; if (key == "gsdlhome") configinfo.gsdlhome = cfgline[0]; else if (key == "collection") { configinfo.collection = cfgline[0]; // also need to set the default arg to this collection if ((info = argsinfo.getarginfo("c")) != NULL) { info->defaultstatus = cgiarginfo::good; info->argdefault = cfgline[0]; } } else if (key == "collectdir") configinfo.collectdir = cfgline[0]; else if (key == "httpprefix") configinfo.httpprefix = cfgline[0]; else if (key == "httpimg") configinfo.httpimg = cfgline[0]; else if (key == "gwcgi") configinfo.gwcgi = cfgline[0]; else if (key == "macrofiles") { // want to append to macrofiles (i.e. may be several config files // contributing, maybe from several collections). text_tarray::const_iterator here = cfgline.begin(); text_tarray::const_iterator end = cfgline.end(); while (here != end) { configinfo.macrofiles.insert (*here); here ++; } } else if (key == "saveconf") configinfo.saveconf = cfgline[0]; else if (key == "usecookies") configinfo.usecookies = (cfgline[0] == "true"); else if (key == "logcgiargs") configinfo.logcgiargs = (cfgline[0] == "true"); else if (key == "pageparam") { if (cfgline.size() >= 2) configinfo.pageparams[cfgline[0]] = cfgline[1]; else configinfo.pageparams[cfgline[0]] = ""; } else if (key == "macroprecedence") configinfo.macroprecedence = cfgline[0]; else if (key == "collectinfo") { if (cfgline.size() >= 3) { collectioninfo_t cinfo; cinfo.gsdl_gsdlhome = cfgline[1]; cinfo.gsdl_gdbmhome = cfgline[2]; configinfo.collectinfo[cfgline[0]] = cinfo; } } else if (key == "cgiarg") { // get shortname bool seen_defaultstatus = false; text_t subkey, subvalue; text_t shortname; text_t::const_iterator cfglinesub_here; text_tarray::const_iterator cfgline_here = cfgline.begin(); text_tarray::const_iterator cfgline_end = cfgline.end(); while (cfgline_here != cfgline_end) { cfglinesub_here = getdelimitstr((*cfgline_here).begin(), (*cfgline_here).end(), '=', subkey); if (subkey == "shortname") { shortname = substr (cfglinesub_here, (*cfgline_here).end()); } cfgline_here++; } // if we found the shortname process the line again filling in values if (!shortname.empty()) { cgiarginfo &chinfo = argsinfo[shortname]; chinfo.shortname = shortname; // in case this is a new argument cfgline_here = cfgline.begin(); while (cfgline_here != cfgline_end) { cfglinesub_here = getdelimitstr((*cfgline_here).begin(), (*cfgline_here).end(), '=', subkey); subvalue = substr (cfglinesub_here, (*cfgline_here).end()); if (subkey == "longname") chinfo.longname = subvalue; else if (subkey == "multiplechar") chinfo.multiplechar = (subvalue == "true"); else if (subkey == "defaultstatus") { seen_defaultstatus = true; if (subvalue == "none") chinfo.defaultstatus = cgiarginfo::none; else if (subvalue == "weak") chinfo.defaultstatus = cgiarginfo::weak; else if (subvalue == "good") chinfo.defaultstatus = cgiarginfo::good; else if (subvalue == "config") chinfo.defaultstatus = cgiarginfo::config; else if (subvalue == "imperative") chinfo.defaultstatus = cgiarginfo::imperative; } else if (subkey == "argdefault") { chinfo.argdefault = subvalue; if (!seen_defaultstatus) chinfo.defaultstatus = cgiarginfo::config; } else if (subkey == "savedarginfo") { if (subvalue == "mustnot") chinfo.savedarginfo = cgiarginfo::mustnot; else if (subvalue == "can") chinfo.savedarginfo = cgiarginfo::can; else if (subvalue == "must") chinfo.savedarginfo = cgiarginfo::must; } cfgline_here++; } } } } // configure the actions actionptrmap::iterator actionhere = actions.begin (); actionptrmap::iterator actionend = actions.end (); while (actionhere != actionend) { assert ((*actionhere).second.a != NULL); if ((*actionhere).second.a != NULL) (*actionhere).second.a->configure(key, cfgline); actionhere++; } // configure the protocols recptprotolistclass::iterator protohere = protocols.begin (); recptprotolistclass::iterator protoend = protocols.end (); while (protohere != protoend) { assert ((*protohere).p != NULL); if ((*protohere).p != NULL) (*protohere).p->configure(key, cfgline); protohere++; } // configure the browsers browserptrmap::iterator browserhere = browsers.begin (); browserptrmap::iterator browserend = browsers.end (); while (browserhere != browserend) { assert ((*browserhere).second.b != NULL); if ((*browserhere).second.b != NULL) (*browserhere).second.b->configure(key, cfgline); browserhere++; } } void receptionist::configure (const text_t &key, const text_t &value) { text_tarray cfgline; cfgline.push_back (value); configure(key, cfgline); } // init should be called after all the actions, protocols, and // converters have been added to the receptionist and after everything // has been configured but before any pages are created. // It returns true on success and false on failure. If false is // returned getpage should not be called (without producing // meaningless output), instead an error page should be // produced by the calling code. bool receptionist::init (ostream &logout) { // first configure collectdir text_t thecollectdir = configinfo.gsdlhome; if (!configinfo.collection.empty()) { // collection specific mode if (!configinfo.collectdir.empty()) { // has already been configured thecollectdir = configinfo.collectdir; } else { // decide where collectdir is by searching for collect.cfg // look in $GSDLHOME/collect/collection-name/etc/collect.cfg and // then $GSDLHOME/etc/collect.cfg thecollectdir = filename_cat (configinfo.gsdlhome, "collect"); thecollectdir = filename_cat (thecollectdir, configinfo.collection); text_t filename = filename_cat (thecollectdir, "etc"); filename = filename_cat (filename, "collect.cfg"); if (!file_exists(filename)) thecollectdir = configinfo.gsdlhome; } } configure("collectdir", thecollectdir); // read in the macro files if (!read_macrofiles (logout)) return false; // there must be at least one action defined if (actions.empty()) { logout << "Error: no actions have been added to the receptionist\n"; return false; } // there must be at least one browser defined if (browsers.empty()) { logout << "Error: no browsers have been added to the receptionist\n"; return false; } // create a saveconf string if there isn't one already if (configinfo.saveconf.empty()) configinfo.saveconf = create_save_conf_str (argsinfo, logout); // check the saveconf string if (!check_save_conf_str (configinfo.saveconf, argsinfo, logout)) return false; // set a random seed srand (time(NULL)); // make the output converters remove all the zero-width spaces convertinfoclass::iterator converthere = converters.begin (); convertinfoclass::iterator convertend = converters.end (); text_t defaultconvertname; while (converthere != convertend) { assert ((*converthere).second.outconverter != NULL); if ((*converthere).second.outconverter != NULL) { (*converthere).second.outconverter->set_rzws(1); if (defaultconvertname.empty()) defaultconvertname = (*converthere).second.name; } converthere++; } // set default converter if no good one has been defined if (!defaultconvertname.empty()) { cgiarginfo *ainfo = argsinfo.getarginfo ("w"); if (ainfo->argdefault != "w") { if ((ainfo != NULL) && (converters.get_outconverter(ainfo->argdefault) == NULL)) { ainfo->defaultstatus = cgiarginfo::good; ainfo->argdefault = defaultconvertname; } } } // init the actions actionptrmap::iterator actionhere = actions.begin (); actionptrmap::iterator actionend = actions.end (); while (actionhere != actionend) { if (((*actionhere).second.a == NULL) || !(*actionhere).second.a->init(logout)) return false; actionhere++; } // init the protocols recptprotolistclass::iterator protohere = protocols.begin (); recptprotolistclass::iterator protoend = protocols.end (); while (protohere != protoend) { if (((*protohere).p == NULL) || !(*protohere).p->init(logout)) return false; protohere++; } // init the browsers browserptrmap::iterator browserhere = browsers.begin (); browserptrmap::iterator browserend = browsers.end (); while (browserhere != browserend) { if (((*browserhere).second.b == NULL) || !(*browserhere).second.b->init(logout)) return false; browserhere++; } return true; } // parse_cgi_args parses cgi arguments into an argument class. // This function should be called for each page request. It returns false // if there was a major problem with the cgi arguments. bool receptionist::parse_cgi_args (const text_t &argstr, cgiargsclass &args, ostream &logout, text_tmap &fcgienv) { outconvertclass text_t2ascii; // get an initial list of cgi arguments args.clear(); split_cgi_args (argsinfo, argstr, args); // expand the compressed argument (if there was one) if (!expand_save_args (argsinfo, configinfo.saveconf, args, logout)) return false; // add the defaults add_default_args (argsinfo, args, logout); // get the cookie if (configinfo.usecookies) get_cookie(args["z"], fcgienv); // get the input encoding text_t &arg_w = args["w"]; inconvertclass defaultinconvert; inconvertclass *inconvert = converters.get_inconverter (arg_w); if (inconvert == NULL) inconvert = &defaultinconvert; // see if the next page will have a different encoding if (args.getarg("nw") != NULL) arg_w = args["nw"]; // convert arguments which aren't in unicode to unicode args_tounicode (args, *inconvert); // decide on the output conversion class (needed for checking the external // cgi arguments) rzwsoutconvertclass defaultoutconverter; rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w); if (outconverter == NULL) outconverter = &defaultoutconverter; outconverter->reset(); // check the main cgi arguments if (!check_mainargs (args, logout)) return false; // check the arguments for the action action *a = actions.getaction (args["a"]); if (a != NULL) { if (!a->check_cgiargs (argsinfo, args, logout)) return false; } else { // the action was not found!! logout << text_t2ascii << "Error: the action \"" << args["a"] << "\" could not be found.\n"; return false; } // check external cgi arguments for each action actionptrmap::iterator actionhere = actions.begin (); actionptrmap::iterator actionend = actions.end (); while (actionhere != actionend) { assert ((*actionhere).second.a != NULL); if ((*actionhere).second.a != NULL) { if (!(*actionhere).second.a->check_external_cgiargs (argsinfo, args, *outconverter, configinfo.saveconf, logout)) return false; } actionhere++; } // the action might have changed but we will assume that // the cgiargs were checked properly when the change was made return true; } // returns true if cookie already existed, false // if it was generated bool receptionist::get_cookie (text_t &cookie, text_tmap &fcgienv) { text_t cookiestring = gsdl_getenv ("HTTP_COOKIE", fcgienv); text_t::const_iterator end = cookiestring.end(); text_t::const_iterator here = findchar (cookiestring.begin(), end, 'G'); while (here+9 < end) { if (substr(here, here+8) == "GSDL_UID") { cookie = substr (here+9, findchar (here+9, end, ';')); return true; } here = findchar (cookiestring.begin(), end, 'G'); } cookie.clear(); text_t host = gsdl_getenv("REMOTE_ADDR", fcgienv); time_t ttime = time(NULL); if (!host.empty()) { cookie += host; cookie.push_back ('-'); } cookie += text_t(ttime); return false; } // as above but just tests if cookie exists bool receptionist::get_cookie (text_tmap &fcgienv) { text_t c = gsdl_getenv("HTTP_COOKIE", fcgienv); if (!c.empty()) { text_t cookiestring = c; text_t::const_iterator end = cookiestring.end(); text_t::const_iterator here = findchar (cookiestring.begin(), end, 'G'); while (here+9 < end) { if (substr(here, here+8) == "GSDL_UID") return true; here = findchar (cookiestring.begin(), end, 'G'); } } return false; } bool receptionist::log_cgi_args (cgiargsclass &args, ostream &logout, text_tmap &fcgienv) { // see if we want to log the cgi arguments if (!configinfo.logcgiargs) return true; text_t host = gsdl_getenv ("REMOTE_HOST", fcgienv); text_t script_name = gsdl_getenv ("SCRIPT_NAME", fcgienv); if (host.empty()) host = gsdl_getenv ("REMOTE_ADDR", fcgienv); text_t browser = gsdl_getenv ("HTTP_USER_AGENT", fcgienv); time_t ttime = time(NULL); cgiargsclass::const_iterator args_here = args.begin(); cgiargsclass::const_iterator args_end = args.end(); text_t argstr; bool first = true; while (args_here != args_end) { if (!first) argstr += ", "; argstr += (*args_here).first + "=" + (*args_here).second.value; first = false; args_here ++; } text_t logfile = filename_cat (configinfo.gsdlhome, "etc"); logfile = filename_cat (logfile, "usage.txt"); text_t logstr = script_name; logstr += " " + host; logstr += " ["; logstr += ttime; logstr += "] (" + argstr + ") \""; logstr += browser; logstr += "\"\n"; return append_logstr (logfile, logstr, logout); } bool receptionist::append_logstr (const text_t &filename, const text_t &logstr, ostream &logout) { utf8outconvertclass text_t2utf8; char *lfile = filename.getcstr(); ofstream log (lfile, ios::app); if (!log) { logout << "Error: Couldn't open file " << lfile << "\n"; delete lfile; return false; } int fd = GSDL_GET_FILEDESC(log); // lock_val is set to 0 if file is locked successfully int lock_val = 1; GSDL_LOCK_FILE (fd); if (lock_val == 0) { log << text_t2utf8 << logstr; GSDL_UNLOCK_FILE (fd); } else { logout << "Error: Couldn't lock file " << lfile << "\n"; log.close(); delete lfile; return false; } log.close(); delete lfile; return true; } text_t receptionist::expandmacros (const text_t &astring, cgiargsclass &args, ostream &logout) { text_t outstring; outconvertclass text_t2ascii; action *a = actions.getaction (args["a"]); prepare_page (a, args, text_t2ascii, logout); disp.expandstring ("Global", astring, outstring); return outstring; } // produce_cgi_page will call get_cgihead_info and // produce_content in the appropriate way to output a cgi header and // the page content (if needed). If a page could not be created it // will return false bool receptionist::produce_cgi_page (cgiargsclass &args, ostream &contentout, ostream &logout, text_tmap &fcgienv) { outconvertclass text_t2ascii; response_t response; text_t response_data; // produce cgi header get_cgihead_info (args, response, response_data, logout, fcgienv); if (response == location) { // location response (url may contain macros!!) response_data = expandmacros (response_data, args, logout); contentout << text_t2ascii << "Location: " << response_data << "\n\n"; contentout << flush; return true; } else if (response == content) { // content response contentout << text_t2ascii << "Content-type: " << response_data << "\n\n"; } else { // unknown response logout << "Error: get_cgihead_info returned an unknown response type.\n"; return false; } // produce cgi page if (!produce_content (args, contentout, logout)) return false; // flush contentout contentout << flush; return true; } // get_cgihead_info determines the cgi header information for // a set of cgi arguments. If response contains location then // response_data contains the redirect address. If reponse // contains content then reponse_data contains the content-type. // Note that images can now be produced by the receptionist. void receptionist::get_cgihead_info (cgiargsclass &args, response_t &response, text_t &response_data, ostream &logout, text_tmap &fcgienv) { outconvertclass text_t2ascii; // get the action action *a = actions.getaction (args["a"]); if (a != NULL) { a->get_cgihead_info (args, &protocols, response, response_data, logout); } else { // the action was not found!! logout << text_t2ascii << "Error receptionist::get_cgihead_info: the action \"" << args["a"] << "\" could not be found.\n"; response = content; response_data = "text/html"; } // add the encoding information if (response == content) { if (args["w"] == "u") { response_data += "; charset=UTF-8"; } else if (args["w"] == "g") { response_data += "; charset=GBK"; } else if (args["w"] == "a") { response_data += "; charset=windows-1256"; } else { response_data += "; charset=ISO-8859-1"; } // add cookie if required if (configinfo.usecookies && !get_cookie(fcgienv)) response_data += "\nSet-Cookie: GSDL_UID=" + args["z"] + "; expires=25-Dec-37 00:00:00 GMT"; } } // produce the page content bool receptionist::produce_content (cgiargsclass &args, ostream &contentout, ostream &logout) { // decide on the output conversion class text_t &arg_w = args["w"]; rzwsoutconvertclass defaultoutconverter; rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w); if (outconverter == NULL) outconverter = &defaultoutconverter; outconverter->reset(); recptproto *collectproto = protocols.getrecptproto (args["c"], logout); if (collectproto != NULL) { // get browsers to process OID text_t OID = args["d"]; if (OID.empty()) OID = args["cl"]; if (!OID.empty()) { text_tset metadata; text_tarray OIDs; OIDs.push_back (OID); if (!is_top(OID)) OIDs.push_back (OID + ".pr"); FilterResponse_t response; metadata.insert ("childtype"); if (get_info (OIDs, args["c"], metadata, false, collectproto, response, logout)) { text_t classifytype; if (!response.docInfo[0].metadata["childtype"].values[0].empty()) classifytype = response.docInfo[0].metadata["childtype"].values[0]; else if (!is_top (OID)) { if (!response.docInfo[1].metadata["childtype"].values[0].empty()) classifytype = response.docInfo[1].metadata["childtype"].values[0]; } browserclass *b = browsers.getbrowser (classifytype); b->processOID (args, collectproto, logout); } } // translate "d" and "cl" arguments if required translate_OIDs (args, collectproto, logout); } // produce the page using the desired action action *a = actions.getaction (args["a"]); if (a != NULL) { if (a->uses_display(args)) prepare_page (a, args, (*outconverter), logout); if (!a->do_action (args, &protocols, &browsers, disp, (*outconverter), contentout, logout)) return false; } else { // the action was not found!! outconvertclass text_t2ascii; logout << text_t2ascii << "Error receptionist::produce_content: the action \"" << args["a"] << "\" could not be found.\n"; contentout << (*outconverter) << "\n" << "\n" << "Error\n" << "\n" << "\n" << "

Oops!

\n" << "Undefined Page. The action \"" << args["a"] << "\" could not be found.\n" << "\n" << "\n"; } return true; } // returns the compressed argument ("e") corresponding to the argument // list. This can be used to save preferences between sessions. text_t receptionist::get_compressed_arg (cgiargsclass &args, ostream &logout) { // decide on the output conversion class text_t &arg_w = args["w"]; rzwsoutconvertclass defaultoutconverter; rzwsoutconvertclass *outconverter = converters.get_outconverter (arg_w); if (outconverter == NULL) outconverter = &defaultoutconverter; outconverter->reset(); text_t compressed_args; if (compress_save_args (argsinfo, configinfo.saveconf, args, compressed_args, *outconverter, logout)) return compressed_args; return ""; } // will read in all the macro files. If one is not found an // error message will be written to logout and the method will // return false. bool receptionist::read_macrofiles (ostream &logout) { outconvertclass text_t2ascii; // redirect the error output to logout ostream *savedlogout = disp.setlogout (&logout); // load up the default macro files, the collection directory // is searched first for the file (if this is being used in // collection specific mode) and then the main directory(s) text_t colmacrodir = filename_cat (configinfo.collectdir, "macros"); text_tset maindirs; text_t gsdlmacrodir = filename_cat (configinfo.gsdlhome, "macros"); maindirs.insert (gsdlmacrodir); colinfo_tmap::iterator colhere = configinfo.collectinfo.begin(); colinfo_tmap::iterator colend = configinfo.collectinfo.end(); while (colhere != colend) { gsdlmacrodir = filename_cat ((*colhere).second.gsdl_gsdlhome, "macros"); maindirs.insert (gsdlmacrodir); colhere ++; } text_tset::iterator arrhere = configinfo.macrofiles.begin(); text_tset::iterator arrend = configinfo.macrofiles.end(); text_t filename; while (arrhere != arrend) { bool foundfile = false; // try in the collection directory if this is being // run in collection specific mode if (!configinfo.collection.empty()) { filename = filename_cat (colmacrodir, *arrhere); if (file_exists (filename)) { disp.loaddefaultmacros(filename); foundfile = true; } } // if we haven't found the macro file yet try in // the main macro directory(s) // if file is found in more than one main directory // we'll load all copies if (!foundfile) { text_tset::const_iterator dirhere = maindirs.begin(); text_tset::const_iterator dirend = maindirs.end(); while (dirhere != dirend) { filename = filename_cat (*dirhere, *arrhere); if (file_exists (filename)) { disp.loaddefaultmacros(filename); foundfile = true; } dirhere ++; } } // see if we found the file or not if (!foundfile) { logout << text_t2ascii << "Error: the macro file \"" << *arrhere << "\" could not be found.\n"; if (configinfo.collection.empty()) { text_t dirs; joinchar (maindirs, ", ", dirs); logout << text_t2ascii << "It should be in either of the following directories (" << dirs << ").\n\n"; } else { logout << text_t2ascii << "It should be in either " << colmacrodir << " or in " << gsdlmacrodir << ".\n\n"; } // reset logout to what it was disp.setlogout (savedlogout); return false; } arrhere++; } // success // reset logout to what it was disp.setlogout (savedlogout); return true; } // check_mainargs will check all the main arguments. If a major // error is found it will return false and no cgi page should // be created using the arguments. bool receptionist::check_mainargs (cgiargsclass &args, ostream &logout) { // if this receptionist is running in collection dependant mode // then it should always set the collection argument to the // collection if (!configinfo.collection.empty()) args["c"] = configinfo.collection; // if current collection uses ccscols make sure // "ccs" argument is set and make "cc" default to // all collections in "ccs" if (!args["c"].empty()) { text_t &arg_c = args["c"]; ColInfoResponse_t cinfo; comerror_t err; recptproto *collectproto = protocols.getrecptproto (arg_c, logout); collectproto->get_collectinfo (arg_c, cinfo, err, logout); if (!cinfo.ccsCols.empty()) { args["ccs"] = 1; if (args["cc"].empty()) { text_tarray::const_iterator col_here = cinfo.ccsCols.begin(); text_tarray::const_iterator col_end = cinfo.ccsCols.end(); bool first = true; while (col_here != col_end) { // make sure it's a valid collection if (protocols.getrecptproto (*col_here, logout) != NULL) { if (!first) args["cc"].push_back (','); args["cc"] += *col_here; first = false; } col_here ++; } } } } // argument "v" can only be 0 or 1. Use the default value // if it is out of range int arg_v = args.getintarg ("v"); if (arg_v != 0 && arg_v != 1) { cgiarginfo *vinfo = argsinfo.getarginfo ("v"); if (vinfo != NULL) args["v"] = vinfo->argdefault; } // argument "f" can only be 0 or 1. Use the default value // if it is out of range int arg_f = args.getintarg ("f"); if (arg_f != 0 && arg_f != 1) { cgiarginfo *finfo = argsinfo.getarginfo ("f"); if (finfo != NULL) args["f"] = finfo->argdefault; } return true; } // translate_OIDs translates the "d" and "cl" arguments to their correct values // if they use the tricky ".fc", ".lc" type syntax. void receptionist::translate_OIDs (cgiargsclass &args, recptproto *collectproto, ostream &logout) { FilterResponse_t response; FilterRequest_t request; comerror_t err; text_t &arg_d = args["d"]; text_t &arg_cl = args["cl"]; text_t &collection = args["c"]; // do a call to translate OIDs if required request.filterName = "NullFilter"; request.filterResultOptions = FROID; if (!arg_d.empty() && needs_translating (arg_d)) { request.docSet.push_back (arg_d); collectproto->filter (collection, request, response, err, logout); arg_d = response.docInfo[0].OID; request.clear(); } // we'll also check here that the "cl" argument has a "classify" doctype // (in case ".fc" or ".lc" have screwed up) if (needs_translating (arg_cl)) { request.fields.insert ("doctype"); request.docSet.push_back (arg_cl); request.filterResultOptions = FRmetadata; collectproto->filter (collection, request, response, err, logout); // set to original value (without .xx stuff) if doctype isn't "classify" if (response.docInfo[0].metadata["doctype"].values[0] != "classify") strip_suffix (arg_cl); else arg_cl = response.docInfo[0].OID; } } // prepare_page sets up page parameters, sets display macros // and opens the page ready for output void receptionist::prepare_page (action *a, cgiargsclass &args, outconvertclass &outconvert, ostream &logout) { // set up page parameters text_t pageparams; bool first = true; text_tmap::iterator params_here = configinfo.pageparams.begin(); text_tmap::iterator params_end = configinfo.pageparams.end(); while (params_here != params_end) { if (args[(*params_here).first] != (*params_here).second) { if (!first) pageparams += ","; first = false; pageparams += (*params_here).first; pageparams += "="; pageparams += args[(*params_here).first]; } params_here++; } // open the page disp.openpage(pageparams, configinfo.macroprecedence); // define external macros for each action actionptrmap::iterator actionhere = actions.begin (); actionptrmap::iterator actionend = actions.end (); while (actionhere != actionend) { assert ((*actionhere).second.a != NULL); if ((*actionhere).second.a != NULL) (*actionhere).second.a->define_external_macros (disp, args, &protocols, logout); actionhere++; } // define internal macros for the current action a->define_internal_macros (disp, args, &protocols, logout); // define general macros. the defining of general macros is done here so that // the last possible version of the cgi arguments are used define_general_macros (args, outconvert, logout); } void receptionist::define_general_macros (cgiargsclass &args, outconvertclass &/*outconvert*/, ostream &logout) { text_t &collection = args["c"]; disp.setmacro ("gsdlhome", "Global", dm_safe(configinfo.gsdlhome)); disp.setmacro ("gwcgi", "Global", configinfo.gwcgi); disp.setmacro ("httpimg", "Global", configinfo.httpimg); disp.setmacro ("httpprefix", "Global", configinfo.httpprefix); text_t compressedoptions = get_compressed_arg(args, logout); disp.setmacro ("compressedoptions", "Global", dm_safe(compressedoptions)); // need a decoded version of compressedoptions for use within forms // as browsers encode values from forms before sending to server // (e.g. %25 becomes %2525) decode_cgi_arg (compressedoptions); disp.setmacro ("decodedcompressedoptions", "Global", dm_safe(compressedoptions)); // set macron macros if encoding is utf8 if (args["w"] == "u") { disp.setmacro ("Amn", "Global", "Ā"); disp.setmacro ("amn", "Global", "ā"); disp.setmacro ("Emn", "Global", "Ē"); disp.setmacro ("emn", "Global", "ē"); disp.setmacro ("Imn", "Global", "Ī"); disp.setmacro ("imn", "Global", "ī"); disp.setmacro ("Omn", "Global", "Ō"); disp.setmacro ("omn", "Global", "ō"); disp.setmacro ("Umn", "Global", "Ū"); disp.setmacro ("umn", "Global", "ū"); } // set _cgiargX_ macros for each cgi argument cgiargsclass::const_iterator argshere = args.begin(); cgiargsclass::const_iterator argsend = args.end(); while (argshere != argsend) { if (((*argshere).first == "q") || ((*argshere).first == "qa") || ((*argshere).first == "qtt") || ((*argshere).first == "qty") || ((*argshere).first == "qp") || ((*argshere).first == "qpl") || ((*argshere).first == "qr") || ((*argshere).first == "q2")) // need to escape special characters from query string disp.setmacro ("cgiarg" + (*argshere).first, "Global", html_safe((*argshere).second.value)); else disp.setmacro ("cgiarg" + (*argshere).first, "Global", dm_safe((*argshere).second.value)); argshere ++; } // display text right to left if language is arabic (and if browser can support it) if (args["l"] == "ar") disp.setmacro ("htmlextra", "Global", " dir=rtl"); // set collection specific macros if (!collection.empty()) { recptproto *collectproto = protocols.getrecptproto (collection, logout); if (collectproto != NULL) { FilterResponse_t response; text_tset metadata; get_info ("collection", collection, metadata, false, collectproto, response, logout); if (!response.docInfo[0].metadata.empty()) { MetadataInfo_tmap::const_iterator here = response.docInfo[0].metadata.begin(); MetadataInfo_tmap::const_iterator end = response.docInfo[0].metadata.end(); while (here != end) { if (((*here).first != "haschildren") && ((*here).first != "hasnext") && ((*here).first != "hasprevious")) { disp.setmacro ((*here).first, "Global", (*here).second.values[0]); } here ++; } } } } }