/**********************************************************************
 *
 * cgiwrapper.cpp -- windows local library cgiwrapper
 * 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.
 *
 *********************************************************************/

#include "text_t.h"

#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <direct.h>
#include "cgiwrapper.h"
#include "netio.h"
#include "wincgiutils.h"
#include "settings.h"
#include "fileutil.h"
#include "parse.h"

#include "gsdlconf.h"
#include "maincfg.h"

#if defined (GSDL_USE_OBJECTSPACE)
#include <ospace\std\iostream>
#include <ospace\std\fstream>
#elif defined (GSDL_USE_IOS_H)
#include <iostream.h>
#include <fstream.h>
#else
#include <iostream>
#include <fstream>
#endif

#include "receptionist.h"
#include "nullproto.h"

// actions
#include "statusaction.h"
#include "pageaction.h"
#include "pingaction.h"
#include "ispersistentaction.h"
#include "queryaction.h"
#if defined(USE_SQLITE)
#include "sqlqueryaction.h"
#endif

#include "documentaction.h"
#include "dynamicclassifieraction.h"
#include "tipaction.h"
#include "authenaction.h"
#include "usersaction.h"
#include "userdb.h"
#include "extlinkaction.h"
#include "collectoraction.h"
#ifdef ENABLE_MGPP
#include "phindaction.h"
#endif
#include "configaction.h"

// browsers
#include "vlistbrowserclass.h"
#include "hlistbrowserclass.h"
#include "datelistbrowserclass.h"
#include "invbrowserclass.h"
#include "pagedbrowserclass.h"
#include "htmlbrowserclass.h"
#include "phindbrowserclass.h"

// the number of times the library has been accessed
int libaccessnum = 0;

// used to output the text from receptionist
class textstreambuf : public streambuf
{
public:
  textstreambuf ();
  int sync ();
  int overflow (int ch);
  int underflow () {return EOF;}
  
  void tsbreset() {RInfo=NULL;casostr=NULL;}
  void setrequestinfo (RequestInfoT *theRInfo) {RInfo=theRInfo;}
  void cascadeoutput (ostream *thecasostr) {casostr=thecasostr;}
  
private:
  RequestInfoT *RInfo;
  ostream *casostr;
#if !defined (GSDL_USE_IOS_H)
  char buffer[256];
#endif
};

textstreambuf::textstreambuf() {
  tsbreset();
#if !defined (GSDL_USE_IOS_H)
  setp (&buffer[0], &buffer[255]);
#else
  if (base() == ebuf()) allocate();
  setp (base(), ebuf());
#endif
};

int textstreambuf::sync () {
  if ((RInfo != NULL) && 
      (Send_String_N(pbase(), pptr()-pbase(), RInfo) < 0)) {
    RInfo = NULL;
  }
  
  if (casostr != NULL) {
    char *thepbase=pbase();
    for (int i=0;i<(pptr()-pbase());++i) (*casostr).put(thepbase[i]);
  }
  
  setp (pbase(), epptr());
  
  return 0;
}

int textstreambuf::overflow (int ch) {
  if (sync () == EOF) return EOF;
  if (ch != EOF) sputc (ch);
  return 0;
}


// used to output all the log and error messages
// from receptionist
class logstreambuf : public streambuf
{
public:
  logstreambuf ();
  int sync ();
  int overflow (int ch);
  int underflow () {return EOF;}

#if !defined (GSDL_USE_IOS_H)
private:
  char buffer[256];
#endif
};

logstreambuf::logstreambuf () {
#if !defined (GSDL_USE_IOS_H)
  setp (&buffer[0], &buffer[255]);
#else
  if (base() == ebuf()) allocate();
  setp (base(), ebuf());
#endif
}

int logstreambuf::sync () {
  if (gsdl_keep_log || gsdl_show_console) {
    log_message ("LOCAL LIB MESSAGE: ");
		log_message_N (pbase(), pptr()-pbase());
  }

  setp (pbase(), epptr());
  return 0;
}

int logstreambuf::overflow (int ch) {
  if (sync () == EOF) return EOF;
  if (ch != EOF) sputc (ch);
  return 0;
}

static void page_errormaincfg (const text_t &gsdlhome, const text_t &collection) {

  if (collection.empty()) {
    text_t message = "Error\n\n"
      "The main.cfg configuration file could not be found. This file\n"
      "should contain configuration information relating to the\n"
      "setup of the interface. As this program is not being run\n"
      "in collection specific mode the file should reside at\n" +
      gsdlhome + "\\etc\\main.cfg.\n";

    MessageBox(NULL, message.getcstr(),
	       "Greenstone Digital Library Software"
	       ,MB_OK|MB_SYSTEMMODAL);
  } else {
    text_t message = "Neither the collect.cfg or main.cfg configuration files could\n"
      "be found. This file should contain configuration information\n"
      "relating to the setup of the interface. As this cgi script is\n"
      "being run in collection specific mode the file should reside\n"
      "at either " + gsdlhome + "\\collect\\" + collection + "\\etc\\collect.cfg,\n" +
      gsdlhome + "\\etc\\collect.cfg or " + gsdlhome + "\\etc\\main.cfg.\n";

    MessageBox(NULL, message.getcstr(),
	       "Greenstone Digital Library Software"
	       ,MB_OK|MB_SYSTEMMODAL);
  }
}

static void page_errorinit (const text_t &/*gsdlhome*/) {

  text_t message = "Error\n\n"
    "An error occurred during the initialisation of the Greenstone Digital\n"
    "Library software. It is likely that the software has not been setup\n"
    "correctly.\n";

  MessageBox(NULL, message.getcstr(),
	     "Greenstone Digital Library Software"
	     ,MB_OK|MB_SYSTEMMODAL);
}

static void page_errorparseargs (const text_t &/*gsdlhome*/) {

  text_t message = "Error\n\n"
    "An error occurred during the parsing of the cgi arguments.\n";

  MessageBox(NULL, message.getcstr(),
	     "Greenstone Digital Library Software"
	     ,MB_OK|MB_SYSTEMMODAL);
}

static void page_errorcgipage (const text_t &/*gsdlhome*/) {

  text_t message = "Error\n\n"
    "An error occurred during the construction of the cgi page.\n";

  MessageBox(NULL, message.getcstr(),
	     "Greenstone Digital Library Software"
	     ,MB_OK|MB_SYSTEMMODAL);
}

// returns 0 if the directories can't be found
// and the user wants to quit (it returns 1
// if everything is ok)
int checkdir (const text_t &thedir) {
  UINT curerrormode;
  int drive = _getdrive();
  char cwd[1024];
  char rootpath[4];
  UINT drivetype;
  char *cstrthedir = thedir.getcstr();
  int returnvalue = 1;
  
  // make sure no rude error messages are presented to the user
  curerrormode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
  
  // get the drive name
  if (thedir.size() >= 2 && thedir[1] == ':') {
    if (thedir[0] >= 'a' && thedir[0] <= 'z') {
      drive = thedir[0] - 'a' + 1;
      
    } else if (thedir[0] >= 'A' && thedir[0] <= 'Z') {
      drive = thedir[0] - 'A' + 1;
    }
  }
  
  // find the drive type
  rootpath[0] = drive + 'A' - 1;
  rootpath[1] = ':';
  rootpath[2] = '\\';
  rootpath[3] = '\0';
  drivetype = GetDriveType (rootpath);
  
  // try and set this directory to be the current working
  // directory
  _getcwd(cwd, 1024);
  while (_chdir(cstrthedir) != 0) {
    // failed
    if (drivetype == DRIVE_CDROM) {
      // message to insert the cdrom
      if (MessageBox (NULL, 
		      "Please insert the Greenstone Digital Library\n"
		      "CD-ROM into the CD-ROM drive and press 'OK'.\n\n"
		      "If you don't have the CD-ROM press 'Cancel' to exit\n"
		      "this program.", "Greenstone Digital Library Software", 
		      MB_OKCANCEL | MB_TASKMODAL) == IDCANCEL) {
	
	returnvalue = 0;
	break;
      }
      
    } else {
      // message saying that the system was unable
      // to find a certain directory
      text_t message = "Failed to find the directory:\n\n" + thedir;
      message += "\n\n"
	"This directory is needed for the successful operation\n"
	"of this software. Make sure it hasn't been deleted or\n"
	"moved, and restart the software. You may need to\n"
	"reinstall this software to correct the problem.";
      char *cstrmessage = message.getcstr();
      
      MessageBox (NULL, cstrmessage, "Greenstone Digital Library Software",
		  MB_OK | MB_TASKMODAL);
      
      delete []cstrmessage;
      
      returnvalue = 0;
      break;
    }
  }
  
  // revert to the previous error and cwd states
  _chdir(cwd);
  SetErrorMode(curerrormode);
  
  // free the allocated C string
  delete []cstrthedir;
  
  return returnvalue;
}


// c-string version of checkdir for the outside
// world
int cstrcheckdir (char *cstrthedir) {
  return checkdir (cstrthedir);
}


receptionist recpt;
nullproto nproto;
collectset *cservers = NULL;

textstreambuf textstream;
logstreambuf logstream;
DWORD lastlibaccesstime;
DWORD baseavailvirtual;
text_t current_gsdlhome;
text_t current_collecthome;
colinfo_tmap translated_collectinfo;
  
statusaction *astatusaction = NULL;
pageaction *apageaction = NULL;
pingaction *apingaction = NULL;
ispersistentaction *aIsPersistentAction = NULL;
tipaction *atipaction = NULL;
queryaction *aqueryaction = NULL;
#if defined(USE_SQLITE)
sqlqueryaction *asqlqueryaction = NULL;
#endif
documentaction *adocumentaction = NULL;
dynamicclassifieraction *adynamicclassifieraction = NULL;
usersaction *ausersaction = NULL;
extlinkaction *anextlinkaction = NULL;
collectoraction *acollectoraction = NULL;
authenaction *aauthenaction = NULL;
#ifdef ENABLE_MGPP
phindaction *aphindaction = NULL;
#endif
configaction *aconfigaction = NULL;
vlistbrowserclass *avlistbrowserclass = NULL;
hlistbrowserclass *ahlistbrowserclass = NULL;
datelistbrowserclass *adatelistbrowserclass = NULL;
invbrowserclass *ainvbrowserclass = NULL;
pagedbrowserclass *apagedbrowserclass = NULL;
htmlbrowserclass *ahtmlbrowserclass = NULL;
phindbrowserclass *aphindbrowserclass = NULL;
userdbclass *udb = NULL;
keydbclass *kdb = NULL;


// When (1) the "collecthome" collect dir is not the standard greenstone collect dir,
// or when (2) this collecthome is specified in a section other than [gsdl] in the (lls/gli)site.cfg file,
// this method gets called to load in that collect dir's collections into the server during gsdl_init
static void load_collections_from_collectdir(
	colinfo_tmap &translated_colinfo,
	text_tset::const_iterator &colhere, 
	text_tset::const_iterator &colend, 
	text_tset &these_collections,
	text_tset &dbhomes, 
	text_tset &clhomes, 
	text_tset &collections,
	const text_t &my_gsdl_home, 
	const text_t &my_gsdl_collecthome, 
	const text_t &my_gsdl_dbhome) 
{	
	these_collections.erase (these_collections.begin(), these_collections.end());
	read_dir (my_gsdl_collecthome, these_collections);
	colhere = these_collections.begin();
	colend = these_collections.end();
	while (colhere != colend) {		 
	  if ((collections.find (*colhere)) == collections.end()) {
		// make sure the build.cfg file is at gsdlhome (as it's possible that
		// the collection appears at this gsdlhome only because it's gdbm
		// file is installed here -- it's real gdbm will therefore be 
		// somewhere else).		
		
		// commenting out the following build_cfg test since the server won't know to load 
		// collection groups otherwise, as these don't have build_cfg files but ought to be loaded along
		// with collections (the real test for whether something ought to be loaded--whether an item read
		// in by read_dir is a collection or collection group--happens at a later time in the GS runtime code).
		
		//text_t build_cfg = filename_cat (my_gsdl_collecthome,*colhere, "index", "build.cfg");						 
		//if (file_exists (build_cfg)) {
		  collections.insert (*colhere);

		  // since gsdl_collectinfo keys will be stuff like collection#1
		  // for a multiple volume collection we want to translate it
		  // so that the keys are the actual collection names
		  collectioninfo_t tmp;
		  tmp.gsdl_gsdlhome = my_gsdl_home;
		  tmp.gsdl_collecthome = my_gsdl_collecthome;
		  tmp.gsdl_dbhome = my_gsdl_dbhome;			
		  translated_colinfo[*colhere] = tmp;
		//}
	  }
	  ++colhere;
	}
	dbhomes.insert (gsdl_dbhome);
	clhomes.insert (gsdl_collecthome);
}


// returns 1 if successful, 0 if unsuccessful (note that as well as being
// called when the server first starts up this function is called when the
// "restart library" button is pressed)
int gsdl_init (bool atStartup) {		 
  if (atStartup) {
#if defined (GSDL_USE_IOS_H)
    cerr = &logstream;
    cout = &textstream;
#else
    cerr.rdbuf(&logstream);
    cout.rdbuf(&textstream);
#endif
  }

  // collection should be set to "" unless in collection specific mode -
  // changing this to the name of the collection should be all that's
  // required to create a collection specific receptionist
  text_t collection = "";
  text_tset dbhomes;
  text_tset clhomes; // collecthome companion for dbhome
  text_tset collections;

  if (atStartup) {
    // note the current time
    lastlibaccesstime = GetTickCount();
    
    // before we do the init we should make sure
    // that we can find the relevant directories
    if (!checkdir (gsdl_gsdlhome + "\\")) return 0;
    if (!checkdir (gsdl_collecthome + "\\")) return 0;
    if (!checkdir (gsdl_gsdlhome + "\\macros\\")) return 0;
  }    

  // deleting cservers clears all the collection servers 
  if (cservers != NULL) delete cservers;

  cservers = new collectset();

  // get all collections from each gsdlhome (this relies 
  // on there not being more than one collection with the same 
  // name)
  if (!collection.empty()) {  
    // collection specific receptionist - one collection, one gsdlhome
    collections.insert (collection);
    dbhomes.insert (gsdl_dbhome);
    clhomes.insert (gsdl_collecthome);
    collectioninfo_t tmp;
    tmp.gsdl_gsdlhome = gsdl_gsdlhome;
    tmp.gsdl_collecthome = gsdl_collecthome;
    tmp.gsdl_dbhome = gsdl_dbhome;
    translated_collectinfo[collection] = tmp;

  } else {
	text_tset::const_iterator colhere;
    text_tset::const_iterator colend;
    text_tset these_collections;
  
    // First volume gsdlhome's
	// read in all the collections from sections other than [gsdl] in the site config file (llssite.cfg)
    colinfo_tmap::const_iterator this_info = gsdl_collectinfo.begin();
    colinfo_tmap::const_iterator end_info = gsdl_collectinfo.end();
    while (this_info != end_info) {
      if (dbhomes.find ((*this_info).second.gsdl_dbhome) == dbhomes.end()) {
			load_collections_from_collectdir(translated_collectinfo,
					colhere, colend, these_collections,
					dbhomes, clhomes, collections,
					(*this_info).second.gsdl_gsdlhome, 
					(*this_info).second.gsdl_collecthome, 
					(*this_info).second.gsdl_dbhome);	 
      }
      ++this_info;
    }
	
	// if non-standard collecthome under [gsdl] section of site config file (llssite.cfg),
	// then need to read in all the collections from this collecthome
	if(gsdl_collecthome != filename_cat(gsdl_gsdlhome,"collect")) { 
		load_collections_from_collectdir(translated_collectinfo,
				colhere, colend, these_collections,
				dbhomes, clhomes, collections,
				gsdl_gsdlhome, gsdl_collecthome, gsdl_dbhome);		
	}	
  
  // But when the default greenstone collect dir (gsdlhome\collect) is used, the following is used to load the collections:
  
    // then if necessary the main dbhome (this should only happen if the 
    // gsdl.ini is a little screwed up and no volume dbhomes occurred)
    if (dbhomes.find (gsdl_dbhome) == dbhomes.end()) {
	  load_collections_from_collectdir(translated_collectinfo,
				colhere, colend, these_collections,
				dbhomes, clhomes, collections,
				gsdl_gsdlhome, filename_cat (gsdl_dbhome, "collect"), gsdl_dbhome);
    }	
  }

  text_tset::const_iterator thiscol = collections.begin();
  text_tset::const_iterator endcol = collections.end();

  while (thiscol != endcol) {
    
    // ignore the modelcol
    if (*thiscol == "modelcol") {
      ++thiscol;
      continue;
    }
    
    // create collection server and add to null protocol
    text_t this_gsdlhome = gsdl_gsdlhome;
    text_t this_collecthome = gsdl_collecthome;
    text_t this_dbhome = gsdl_dbhome;
    colinfo_tmap::const_iterator it = translated_collectinfo.find (*thiscol);
    assert (it != translated_collectinfo.end());
    this_gsdlhome = (*it).second.gsdl_gsdlhome;
    this_collecthome = (*it).second.gsdl_collecthome;
    this_dbhome = (*it).second.gsdl_dbhome;

    cservers->add_collection (*thiscol, this_gsdlhome, this_collecthome);
    // in case it is a collection group
    cservers->add_collection_group(*thiscol, this_gsdlhome, this_collecthome);
    
    ++thiscol;
  }
 
  // set up the null protocol
  nproto.set_collectset(cservers);
  
  if ((ausersaction == NULL) && (aauthenaction == NULL)){
	udb = new userdbclass(gsdl_gsdlhome);
	kdb = new keydbclass(gsdl_gsdlhome);
  }
  // add the protocol to the receptionist
  if (atStartup) recpt.add_protocol (&nproto);
  
  // the list of actions.
  if (astatusaction == NULL) {
    astatusaction = new statusaction();
    astatusaction->set_receptionist (&recpt);
    recpt.add_action (astatusaction);
  }
  
  if (apageaction == NULL) {
    apageaction = new pageaction();
    apageaction->set_receptionist (&recpt);
    recpt.add_action (apageaction);
  }
  
  if (apingaction == NULL) {
    apingaction = new pingaction();
    recpt.add_action (apingaction);
  }
  
   if (aIsPersistentAction == NULL) {
	// server.exe is persistent, so passing enum value isPersistent
    aIsPersistentAction = new ispersistentaction(isPersistent); 
    recpt.add_action (aIsPersistentAction);
  }
  
  if (atipaction == NULL) {
    atipaction = new tipaction();
    recpt.add_action (atipaction);
  }
  
  if (aqueryaction == NULL) {
    aqueryaction = new queryaction();
	aqueryaction->set_userdb(udb);
    aqueryaction->set_receptionist (&recpt);
    recpt.add_action (aqueryaction);
  }
  
#if defined(USE_SQLITE)
  if (asqlqueryaction == NULL) {
    asqlqueryaction = new sqlqueryaction();
    asqlqueryaction->set_receptionist (&recpt);
    recpt.add_action (asqlqueryaction);
  }
#endif

  if (adocumentaction == NULL) {
    adocumentaction = new documentaction();
    adocumentaction->set_receptionist (&recpt);
    recpt.add_action (adocumentaction);
  }
  
  if (ausersaction == NULL) {
    ausersaction = new usersaction();
    ausersaction->set_userdb(udb);
    recpt.add_action (ausersaction);
  }

  if (anextlinkaction == NULL) {
    anextlinkaction = new extlinkaction();
    recpt.add_action (anextlinkaction);
  }

  if (acollectoraction == NULL) {
    acollectoraction = new collectoraction();
    acollectoraction->set_receptionist (&recpt);
    recpt.add_action (acollectoraction);
  }
  
  if (aauthenaction == NULL) {
    aauthenaction = new authenaction();
    aauthenaction->set_userdb(udb);
    aauthenaction->set_keydb(kdb);
    aauthenaction->set_receptionist(&recpt);
    recpt.add_action (aauthenaction);
  }

#ifdef ENABLE_MGPP
  if (aphindaction == NULL) {
    aphindaction = new phindaction();
    recpt.add_action (aphindaction);
  }
#endif
  
  if (aconfigaction == NULL) {
    aconfigaction = new configaction();
    aconfigaction->set_receptionist(&recpt);
    recpt.add_action (aconfigaction);
  }
  
  if (adynamicclassifieraction == NULL) {
    adynamicclassifieraction = new dynamicclassifieraction();
    adynamicclassifieraction->set_receptionist(&recpt);
    recpt.add_action (adynamicclassifieraction);
  }


  // list of browsers
  if (avlistbrowserclass == NULL) {
    avlistbrowserclass = new vlistbrowserclass();
    recpt.add_browser (avlistbrowserclass);
    recpt.setdefaultbrowser ("VList");
  }

  if (ahlistbrowserclass == NULL) {
    ahlistbrowserclass = new hlistbrowserclass();
    recpt.add_browser (ahlistbrowserclass);
  }

  if (adatelistbrowserclass == NULL) {
    adatelistbrowserclass = new datelistbrowserclass();
    recpt.add_browser (adatelistbrowserclass);
  }

  if (ainvbrowserclass == NULL) {
    ainvbrowserclass = new invbrowserclass();
    recpt.add_browser (ainvbrowserclass);
  }

  if (apagedbrowserclass == NULL) {
    apagedbrowserclass = new pagedbrowserclass();
    recpt.add_browser (apagedbrowserclass);
  }

  if (ahtmlbrowserclass == NULL) {
    ahtmlbrowserclass = new htmlbrowserclass();
    recpt.add_browser (ahtmlbrowserclass);
  }

  if (aphindbrowserclass == NULL) {
    aphindbrowserclass = new phindbrowserclass();;
    recpt.add_browser (aphindbrowserclass);
  }
  
  // set defaults
  recpt.configure ("gsdlhome", gsdl_gsdlhome);
  recpt.configure ("collecthome", gsdl_collecthome);
  recpt.configure ("gdbmhome", gsdl_dbhome);
  recpt.configure ("collection", collection);

  int maxrequests = 1;

  // configure collections (and receptionist) with collectinfo stuff
  // different from the default
  colinfo_tmap::const_iterator this_info = translated_collectinfo.begin();
  colinfo_tmap::const_iterator end_info = translated_collectinfo.end();

  while (this_info != end_info) {
    text_tarray tmpconf;
    tmpconf.push_back ((*this_info).first);
    tmpconf.push_back ((*this_info).second.gsdl_gsdlhome);
    tmpconf.push_back ((*this_info).second.gsdl_collecthome);
    tmpconf.push_back ((*this_info).second.gsdl_dbhome);
    recpt.configure ("collectinfo", tmpconf);
    ++this_info;
  }

  // read in config files of each dbhome (in no particular order)
  // those read in last will override those read earlier
  // collections being used together in this way should be 
  // careful not to have main.cfg files that might 
  // interfere with each other.
  text_tset::const_iterator thome = dbhomes.begin();
  text_tset::const_iterator ehome = dbhomes.end();
  text_tset::const_iterator tchome = clhomes.begin(); // collecthome companion for dbhome

  while (thome != ehome) {
    if (!main_cfg_read (recpt, *thome, *tchome, collection)) {
      // couldn't find the main configuration file
      page_errormaincfg (*thome, collection);
      return 0;
    }
    ++thome;
    ++tchome;
  }

  // w32server relies on gwcgi being set to "gsdl", and httpweb to web
  recpt.configure ("gwcgi", "gsdl");
  recpt.configure ("httpweb", "web");

  // initialise the library software
  if (!recpt.init(cerr)) {
    // an error occurred during the initialisation
    page_errorinit(gsdl_gsdlhome);
    return 0;
  }

  // get memory information
  MEMORYSTATUS memstatus;
  memstatus.dwLength = sizeof(MEMORYSTATUS);
  GlobalMemoryStatus(&memstatus);
  baseavailvirtual = memstatus.dwAvailVirtual; // save for later comparison

  return 1;
}


static void rememberpref (const text_t &tailstr) {
  gsdl_enterlib = tailstr;
}


static void send_file_from_disk(text_t filename,
                                RequestInfoT *RInfo,
                                RequestFieldsT *RFields) {
  
  // select appropriate mime type from file extension
  text_t ext;
  text_t::const_iterator end = filename.end();
  text_t::const_iterator it = filename.begin();
  text_t::const_iterator lastdot = end;
  while ((it = findchar(it, end, '.')) != end) {
    lastdot = it;
    ++it;
  }
  if (lastdot < end) ext = substr(lastdot+1, end);

  text_t mime = "unknown";
  int len = ext.size();
  if (len == 2) {
    if ((ext[0] == 'p' || ext[0] == 'P') &&
	(ext[1] == 's' || ext[1] == 'S'))
      mime = "application/postscript";
  }  else if (len == 3) {
    if((ext[0] == 'c' || ext[0] == 'C') &&
	(ext[1] == 's' || ext[1] == 'S') &&
	(ext[2] == 's' || ext[2] == 'S')){
	  mime = "text/css";
    } else if ((ext[0] == 'g' || ext[0] == 'G') &&
	(ext[1] == 'i' || ext[1] == 'I') &&
	(ext[2] == 'f' || ext[2] == 'F')) {
      mime = "image/gif";
    } else if ((ext[0] == 'j' || ext[0] == 'J') &&
	       (ext[1] == 'p' || ext[1] == 'P') &&
	       (ext[2] == 'g' || ext[2] == 'G')) {
      mime = "image/jpeg";
    } else if ((ext[0] == 'h' || ext[0] == 'H') &&
	       (ext[1] == 't' || ext[1] == 'T') &&
	       (ext[2] == 'm' || ext[2] == 'M')) {
      mime = "text/html";
    } else if ((ext[0] == 'p' || ext[0] == 'P') &&
	       (ext[1] == 'd' || ext[1] == 'D') &&
	       (ext[2] == 'f' || ext[2] == 'F')) {
      mime = "application/pdf";
    } else if ((ext[0] == 'p' || ext[0] == 'P') &&
	       (ext[1] == 'n' || ext[1] == 'N') &&
	       (ext[2] == 'g' || ext[2] == 'G')) {
      mime = "image/png";
    } else if ((ext[0] == 'd' || ext[0] == 'D') &&
	       (ext[1] == 'o' || ext[1] == 'O') &&
	       (ext[2] == 'c' || ext[2] == 'C')) {
      mime = "application/msword";
    } else if ((ext[0] == 'r' || ext[0] == 'R') &&
	       (ext[1] == 't' || ext[1] == 'T') &&
	       (ext[2] == 'f' || ext[2] == 'F')) {
      mime = "application/rtf";
    } else if ((ext[0] == 'x' || ext[0] == 'X') &&
	       (ext[1] == 'l' || ext[1] == 'L') &&
	       (ext[2] == 's' || ext[2] == 'S')) {
      mime = "application/vnd.ms-excel";
    } else if ((ext[0] == 'p' || ext[0] == 'P') &&
	       (ext[1] == 'p' || ext[1] == 'P') &&
	       (ext[2] == 'T' || ext[2] == 'T')) {
      mime = "application/vnd.ms-powerpoint";
    } else if ((ext[0] == 'm' || ext[0] == 'M') &&
	       (ext[1] == 'p' || ext[1] == 'P') &&
	       (ext[2] == '3')) {
      mime = "audio/mpeg";
    }
  } else if (len == 4) {
    if ((ext[0] == 'j' || ext[0] == 'J') &&
	(ext[1] == 'p' || ext[1] == 'P') &&
	(ext[2] == 'e' || ext[2] == 'E') &&
	(ext[3] == 'g' || ext[3] == 'G')) {
      mime = "image/jpeg";
    } else if ((ext[0] == 'h' || ext[0] == 'H') &&
	       (ext[1] == 't' || ext[1] == 'T') &&
	       (ext[2] == 'm' || ext[2] == 'M') &&
	       (ext[3] == 'l' || ext[3] == 'L')) {
      mime = "text/html";
    }
  }
	
  // try to open the file
  //cerr << "*** filename = " << filename.getcstr() << endl;
  //cerr << "**** collect_home = " << current_collecthome.getcstr() << endl;

  if (filename.size()>=9) {
    text_t prefix = substr(filename.begin(),filename.begin()+9);
    //cerr << "**** prefix = " << prefix.getcstr() << endl;

    text_t tail = substr(filename.begin()+9,filename.end());
    //cerr << "**** tail = " << tail.getcstr() << endl;

    if (prefix == "/collect/") {
      filename = filename_cat (current_collecthome, tail);
    }
    else {
      filename = filename_cat (current_gsdlhome, filename);
    }
  }

  cerr << "#### filename = " << filename.getcstr() << endl;

  /* Feb 2002 - handle files with spaces in their name. */
  text_t::iterator space_start;
  space_start=findword(filename.begin(),filename.end(),"%20");
  while (space_start != filename.end()) {
    // we found a space...
    text_t::iterator after_space=space_start+3;
    text_t new_filename=substr(filename.begin(), space_start);
    new_filename += " ";
    new_filename += substr(after_space,filename.end());
    filename=new_filename;
    space_start=findword(filename.begin(),filename.end(),"%20");
  }

  char *filenamec = filename.getcstr();
  TranslateEscapeString(filenamec);  // Resolve any %xx values
  FILE *thefile = fopen(filenamec, "rb");
  delete []filenamec;
  if (thefile == NULL) {
    log_message("file not found\n");
    send_retrieve_error(404, "File not found",
			"Could not find the local file requested", RInfo);
    return;
  }
  int nr;
  char buffer[2048];
  // send back required information
  char *mimec = mime.getcstr();
  if (send_header(mimec, RInfo) >= 0) {
    if (RFields->MethodStr != "HEAD") {
      for (;;) {
        nr = fread(buffer, 1, 2048, thefile);
        if (nr <= 0) break;
        if (SendData(RInfo->ClientSocket,
                     (BYTE *)buffer, nr,
		     RInfo->ThreadNum) < 0) break;        
      }
    }
  }
  delete []mimec;
  fclose(thefile);
}

static void handle_library_request(const text_t &argstr, RequestInfoT *RInfo,
				   RequestFieldsT *RequestFields) {

  // parse the cgi arguments and produce the resulting page if there
  // have been no errors so far
  cgiargsclass args;
  fileupload_tmap fileuploads;
  text_tmap empty; // don't use this (it's for fastcgi on unix)
  if (!recpt.parse_cgi_args (argstr, fileuploads, args, cerr, empty)) {
    page_errorparseargs(gsdl_gsdlhome);
    return;
  } 

  colinfo_tmap::const_iterator it = translated_collectinfo.find (args["c"]);
  if (it != translated_collectinfo.end()) {
    current_gsdlhome = (*it).second.gsdl_gsdlhome;
    current_collecthome = (*it).second.gsdl_collecthome;
  } else {
    current_gsdlhome = gsdl_gsdlhome;
    current_collecthome = gsdl_collecthome;
  }

  // produce cgi header
  response_t response;
  text_t response_data;

  recpt.get_cgihead_info (args, response, response_data, cerr, empty);

  if (response == location) {
    // location response
    response_data = "@" + recpt.expandmacros (response_data, args, cerr);
    char *response_data_c = response_data.getcstr();
    send_header(response_data_c, RInfo);
    delete []response_data_c;
    return;
  } else if (response == content) {
    // content response
    char *response_data_c = response_data.getcstr();
    if (send_header(response_data_c, RInfo) < 0) {
      delete []response_data_c;
      return;
    }
    delete []response_data_c;
  }
  else if (response == undecided_location) {
    // We know this is a relocation request but at the moment we don't know exactly where to
    // Just output the start of the header and wait until later to output the target location
    // Used for the "I'm feeling lucky" functionality
    cout << "HTTP/1.0 302 Relocation\r\n";
    cout << "Server: GSDL\r\n";
    cout << "Content-type: text/html \r\n";
  }
  else {
    // unknown response
    cerr << "Error: get_cgihead_info returned an unknown response type.\n";
    return;
  }

  textstream.tsbreset();
  textstream.setrequestinfo (RInfo);
  if (!recpt.produce_content (args, cout, cerr)) {
    page_errorcgipage(gsdl_gsdlhome);
    return;
  } 
  recpt.log_cgi_args (args, cerr, empty);

  cout << flush;
  cerr << flush;
  
  ++libaccessnum;
}

static void handle_server_request(text_t &tailstr,
				  RequestInfoT *RequestInfo,
				  RequestFieldsT *RequestFields) {
  
  text_t argstr;

  // do any url adjustments necessary
  if (tailstr.empty() || tailstr == "/") {
    tailstr = "/gsdl";
  }

  text_t::const_iterator begin = tailstr.begin();
  text_t::const_iterator end = tailstr.end();
  
  // test to see if this is a library request or a local
  // file request
  if ((tailstr == "/gsdl") ||
      ((tailstr.size() > 5) && (substr(begin, begin+6) == "/gsdl?"))) {

    // library request
    
    // argstr is the bit after the '?'
    if (tailstr != "/gsdl") {
      argstr = substr(begin+6, end);
    }

    // log the difference in access times
    DWORD thislibaccesstime = GetTickCount();
    if (gsdl_keep_log || gsdl_show_console) {
      char logstr[256];
      sprintf(logstr, "DELTA LIB ACCESS TIME: %i\n", (int)(thislibaccesstime - lastlibaccesstime));
      log_message (logstr);
    }
    lastlibaccesstime = thislibaccesstime;
    
    // log this request
    if (gsdl_keep_log || gsdl_show_console) {
      text_t logstr = "LOCAL LIB: " + tailstr + "\n";
      char *logstrc = logstr.getcstr();
      log_message (logstrc);
      delete []logstrc;
    }
    
    handle_library_request (argstr, RequestInfo, RequestFields);
    
    // remember the preferences
    // rememberpref (tailstr);
    
    // log memory information
    if (gsdl_keep_log || gsdl_show_console) {
      MEMORYSTATUS memstatus;
      memstatus.dwLength = sizeof(MEMORYSTATUS);
      GlobalMemoryStatus(&memstatus);
      char logstr[256];
      sprintf (logstr, "BDELTA AVAIL VIRTUAL: %i K\n", 
	       (int)((baseavailvirtual - memstatus.dwAvailVirtual)/1024));
      log_message (logstr);
    }
    
  } else {
    // local file
    if (gsdl_keep_log || gsdl_show_console) {
      text_t logstr = "LOCAL FILE: " + tailstr + "\n";
      char *logstrc = logstr.getcstr();
      log_message (logstrc);
      delete []logstrc;
    }
    send_file_from_disk (tailstr, RequestInfo, RequestFields);
  }
}

int ExamineURIStr(text_t &URIStr, RequestInfoT *RequestInfo,
		  RequestFieldsT *RequestFields)
{
  text_t protocol, machine, rest;
  int port;

  if (RequestFields->ContentLength > 0) {
    // POST data
    URIStr.push_back('?');
    for (int i = 0; i < RequestFields->ContentLength; ++i) {
      URIStr.push_back(RequestFields->Content[i]);
    }
  }
  
  if (parse_url(URIStr, protocol, machine, &port, rest)!=http_ok) {
    // Alter local file request to address 'gsdl'
    if (*(URIStr.begin()) != '/') URIStr = "http://gsdl/" + URIStr;
    else URIStr = "http://gsdl" + URIStr;
    parse_url(URIStr, protocol, machine, &port, rest);
  }

  if (machine == "gsdl") {
    // a local file request
    handle_server_request(rest, RequestInfo, RequestFields);
    
  } else {
    send_retrieve_error(404, "File not found",
			"Could not find the local file requested", RequestInfo);
  }
  
  return 1;
}
