/********************************************************************** * * cgiwrapper.cpp -- output pages using the cgi protocol * 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: cgiwrapper.cpp 1279 2000-07-12 22:21:53Z sjboddie $ * *********************************************************************/ /* $Log$ Revision 1.22.4.1 2000/07/12 22:21:35 sjboddie merged changes to trunk into New_Config_Format branch Revision 1.31 2000/07/12 04:51:05 nzdl added an error message when no "valid" collections are available Revision 1.30 2000/07/05 21:49:31 sjboddie Receptionist now caches collection information to avoid making multiple get_collectinfo calls to collection server Revision 1.29 2000/06/29 03:57:14 sjboddie Now append to error log (errout.txt) instead of overwriting it each time Revision 1.28 2000/06/28 01:30:23 nzdl *** empty log message *** Revision 1.27 2000/06/28 01:24:59 sjboddie got "POST" cgi data to work when using fastcgi Revision 1.26 2000/05/12 03:09:26 sjboddie minor modifications to get web library compiling under VC++ 6.0 Revision 1.25 2000/04/14 04:45:19 sjboddie Modified the English of the debug output slightly Revision 1.24 2000/04/14 03:10:35 sjboddie tidied up a few issues concerning the new debug info which showed up on windows Revision 1.23 2000/04/14 02:52:05 sjboddie tidied up error messaging and set up some debugging info to be output when running library from command line Revision 1.22 2000/02/21 21:56:46 sjboddie gsdlhome now comes from gsdlsite.cfg Revision 1.21 2000/01/25 22:45:59 sjboddie few changes to get fastcgi to work properly Revision 1.20 1999/09/07 04:56:53 sjboddie added GPL notice Revision 1.19 1999/09/02 00:24:36 rjmcnab fixed bug in getting POST arguments Revision 1.18 1999/08/20 01:02:07 sjboddie added some usage logging Revision 1.17 1999/07/15 06:03:15 rjmcnab Moved the adding of the actions to librarymain so that they can be overriden easily. Revision 1.16 1999/07/14 08:31:05 rjmcnab Fixed a small bug in the POST implementation. Revision 1.15 1999/07/13 23:32:17 rjmcnab Added authenaction and usersaction Revision 1.14 1999/07/11 01:03:37 rjmcnab Added ability to receive POST cgi form data. Revision 1.13 1999/06/24 05:12:18 sjboddie lots of small changes Revision 1.12 1999/04/30 01:59:40 sjboddie lots of stuff - getting documentaction working (documentaction replaces old browseaction) Revision 1.11 1999/03/25 03:12:01 sjboddie subjectbrowseaction was replaced with browseaction Revision 1.10 1999/03/05 03:53:54 sjboddie fixed some bugs Revision 1.9 1999/03/04 22:38:21 sjboddie Added subjectbrowseaction. - Doesn't do anything yet. Revision 1.8 1999/02/28 20:00:13 rjmcnab Fixed a few things. Revision 1.7 1999/02/21 22:33:53 rjmcnab Lots of stuff :-) Revision 1.6 1999/02/12 02:40:17 sjboddie Added page action Revision 1.5 1999/02/11 01:24:04 rjmcnab Fixed a few compiler warnings. Revision 1.4 1999/02/08 01:28:01 rjmcnab Got the receptionist producing something using the statusaction. Revision 1.3 1999/02/05 10:42:44 rjmcnab Continued working on receptionist Revision 1.2 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.1 1999/02/04 01:16:17 rjmcnab Initial revision. Revision 1.5 1999/01/19 01:38:18 rjmcnab Made the source more portable. Revision 1.4 1999/01/12 01:51:04 rjmcnab Standard header. */ #include "gsdlconf.h" #include "cgiwrapper.h" #include "recptconfig.h" #include "fileutil.h" #include #include #if defined(GSDL_USE_OBJECTSPACE) # include # include #elif defined(GSDL_USE_IOS_H) # include # include #else # include # include #endif #ifdef USE_FASTCGI #include "fcgiapp.h" #endif #ifdef USE_FASTCGI // used to output the text from receptionist class fcgistreambuf : public streambuf { public: fcgistreambuf (); int sync (); int overflow (int ch); int underflow () {return EOF;} void fcgisbreset() {fcgx_stream = NULL; other_ostream = NULL;}; void set_fcgx_stream(FCGX_Stream *newone) {fcgx_stream=newone;}; void set_other_ostream(ostream *newone) {other_ostream=newone;}; private: FCGX_Stream *fcgx_stream; ostream *other_ostream; }; fcgistreambuf::fcgistreambuf() { fcgisbreset(); if (base() == ebuf()) allocate(); setp (base(), ebuf()); }; int fcgistreambuf::sync () { if ((fcgx_stream != NULL) && (FCGX_PutStr (pbase(), out_waiting(), fcgx_stream) < 0)) { fcgx_stream = NULL; } if (other_ostream != NULL) { char *thepbase=pbase(); for (int i=0;i 0), and are in the collection's\n" << " index directory (i.e. NOT the building directory)\n\n"; recptprotolistclass *protos = recpt.get_recptprotolist_ptr(); recptprotolistclass::iterator rprotolist_here = protos->begin(); recptprotolistclass::iterator rprotolist_end = protos->end(); bool found_valid_col = false; while (rprotolist_here != rprotolist_end) { if ((*rprotolist_here).p != NULL) { text_tarray collist; comerror_t err; (*rprotolist_here).p->get_collection_list (collist, err, cerr); if (err == noError) { text_tarray::iterator collist_here = collist.begin(); text_tarray::iterator collist_end = collist.end(); while (collist_here != collist_end) { cout << text_t2ascii << *collist_here; int spaces = (22 - (*collist_here).size()); if (spaces < 2) spaces = 2; text_t outspaces; for (int i = 0; i < spaces; i++) outspaces.push_back (' '); cout << text_t2ascii << outspaces; ColInfoResponse_t *cinfo = recpt.get_collectinfo_ptr ((*rprotolist_here).p, *collist_here, cerr); if (cinfo != NULL) { if (cinfo->isPublic) cout << "public "; else cout << "private"; if (cinfo->buildDate > 0) { cout << " running "; found_valid_col = true; } else { cout << " not running"; } } cout << "\n"; collist_here ++; } } } rprotolist_here ++; } if (!found_valid_col) { cout << "WARNING: No \"running\" collections were found. You need to\n"; cout << " build one of the above collections\n"; } cout << "\n------------------------------------------------------------\n"; cout << "------------------------------------------------------------\n\n"; cout << "receptionist running in command line debug mode\n"; cout << "enter cgi arguments as name=value pairs (e.g. 'a=p&p=home'):\n"; } // cgiwrapper does everything necessary to output a page // using the cgi protocol. If this is being run for a particular // collection then "collection" should be set, otherwise it // should equal "". void cgiwrapper (receptionist &recpt, text_t collection) { int numrequests = 0; bool debug = false; recptconf configinfo = recpt.get_configinfo (); // find out whether this is being run as a cgi-script // or a fastcgi script #ifdef USE_FASTCGI fcgistreambuf outbuf; int isfastcgi = !FCGX_IsCGI(); FCGX_Stream *fcgiin, *fcgiout, *fcgierr; FCGX_ParamArray fcgienvp; #else int isfastcgi = 0; #endif // get the query string if it is not being run as a fastcgi // script text_t argstr = ""; cgiargsclass args; char *aURIStr; if (!isfastcgi) { char *request_method_str = getenv("REQUEST_METHOD"); char *content_length_str = getenv("CONTENT_LENGTH"); if (request_method_str != NULL && strcmp(request_method_str, "POST") == 0 && content_length_str != NULL) { // POST form data int content_length = text_t(content_length_str).getint(); if (content_length > 0) { char c; do { cin.get(c); if (cin.eof()) break; argstr.push_back (c); content_length--; } while (content_length > 0); } } else { aURIStr = getenv("QUERY_STRING"); if ((request_method_str != NULL && strcmp(request_method_str, "GET") == 0) || aURIStr != NULL) { // GET form data if (aURIStr != NULL) argstr = aURIStr; } else { // debugging from command line debug = true; } } } if (debug) { cout << "Configuring Greenstone...\n"; cout << flush; } // init stuff - we can't output error pages directly with // fastcgi so the pages are stored until we can output them text_t errorpage; outconvertclass text_t2ascii; // set defaults int maxrequests = 10000; recpt.configure ("collection", collection); recpt.configure ("httpimg", "/gsdl/images"); char *script_name = getenv("SCRIPT_NAME"); if (script_name != NULL) recpt.configure("gwcgi", script_name); else recpt.configure("gwcgi", "/cgi-bin/gw"); // read in the configuration files. text_t gsdlhome; if (!site_cfg_read (recpt, gsdlhome, maxrequests)) { // couldn't find the site configuration file page_errorsitecfg (errorpage, debug, 0); } else if (gsdlhome.empty()) { // no gsdlhome in gsdlsite.cfg page_errorsitecfg (errorpage, debug, 1); } else if (!main_cfg_read (recpt, gsdlhome, collection)) { // couldn't find the main configuration file page_errormaincfg (gsdlhome, collection, debug, errorpage); } else if (configinfo.collectinfo.empty()) { // don't have any collections page_errorcollect (gsdlhome, errorpage, debug); } if (errorpage.empty()) { // initialise the library software if (debug) { cout << "Initializing...\n"; cout << flush; } text_t init_file = filename_cat (gsdlhome, "etc", "initout.txt"); char *iout = init_file.getcstr(); ofstream initout (iout); delete iout; if (!recpt.init(initout)) { // an error occurred during the initialisation initout.close(); page_errorinit(gsdlhome, debug, errorpage); } initout.close(); } if (debug && errorpage.empty()) { // get query string from command line print_debug_info (recpt); char cinURIStr[1024]; cin.get(cinURIStr, 1024); argstr = cinURIStr; } // cgi scripts only deal with one request if (!isfastcgi) maxrequests = 1; // Page-request loop. If this is not being run as a fastcgi // process then only one request will be processed and then // the process will exit. while (numrequests < maxrequests) { #ifdef USE_FASTCGI if (isfastcgi) { if (FCGX_Accept(&fcgiin, &fcgiout, &fcgierr, &fcgienvp) < 0) break; char *request_method_str = FCGX_GetParam ("REQUEST_METHOD", fcgienvp); char *content_length_str = FCGX_GetParam ("CONTENT_LENGTH", fcgienvp); if (request_method_str != NULL && strcmp(request_method_str, "POST") == 0 && content_length_str != NULL) { // POST form data int content_length = text_t(content_length_str).getint(); if (content_length > 0) { argstr.clear(); int c; do { c = FCGX_GetChar (fcgiin); if (c < 0) break; argstr.push_back (c); content_length--; } while (content_length > 0); } } else { // GET form data aURIStr = FCGX_GetParam("QUERY_STRING", fcgienvp); if (aURIStr != NULL) argstr = aURIStr; else argstr = ""; } } #endif // get output streams ready #ifdef USE_FASTCGI outbuf.fcgisbreset (); if (isfastcgi) outbuf.set_fcgx_stream (fcgiout); else outbuf.set_other_ostream (&cout); ostream pageout (&outbuf); #else #define pageout cout #endif // if using fastcgi we'll load environment into a map, // otherwise simply pass empty map (can't get environment // variables using getenv() while using FCGX versions // of fastcgi - at least I can't ;-) - Stefan) text_tmap fastcgienv; #ifdef USE_FASTCGI if (isfastcgi) { for(; *fcgienvp != NULL; fcgienvp++) { text_t fvalue = *fcgienvp; text_t::const_iterator begin = fvalue.begin(); text_t::const_iterator end = fvalue.end(); text_t::const_iterator equals_sign = findchar (begin, end, '='); if (equals_sign != end) fastcgienv[substr(begin, equals_sign)] = substr(equals_sign+1, end); } } #endif // temporarily need to configure gwcgi here when using fastcgi as I can't // get it to pass the SCRIPT_NAME environment variable to the initial // environment (if anyone can work out how to do this using the apache // server, let me know). Note that this overrides the gwcgi field in // site.cfg (which it shouldn't do) but I can't at present set gwcgi // from site.cfg as I have old receptionists laying around that wouldn't // appreciate it. The following 5 lines of code should be deleted once // I either a: get the server to pass SCRIPT_NAME at initialization // time or b: convert all the collections using old receptionists over // to this version and uncomment gwcgi in the site.cfg file -- Stefan. #ifdef USE_FASTCGI if (isfastcgi) { recpt.configure("gwcgi", fastcgienv["SCRIPT_NAME"]); } #endif if (errorpage.empty()) { text_t error_file = filename_cat (gsdlhome, "etc", "errout.txt"); char *eout = error_file.getcstr(); ofstream errout (eout, ios::app); delete eout; cerr = errout; // parse the cgi arguments and produce the resulting page if there // has been no errors so far if (!recpt.parse_cgi_args (argstr, args, errout, fastcgienv)) { errout.close (); page_errorparseargs(gsdlhome, debug, errorpage); } else { if (!recpt.produce_cgi_page (args, pageout, errout, fastcgienv)) { errout.close (); page_errorcgipage(gsdlhome, debug, errorpage); } recpt.log_cgi_args (args, errout, fastcgienv); errout.close (); } } // there was an error, output the error page if (!errorpage.empty()) { pageout << text_t2ascii << errorpage; errorpage.clear(); numrequests = maxrequests; // make this the last page } pageout << flush; // finish with the output streams #ifdef USE_FASTCGI if (isfastcgi) FCGX_Finish(); #endif numrequests++; } return; }