httpsrvr.cxx
上传用户:hzhsqp
上传日期:2007-01-06
资源大小:1600k
文件大小:46k
源码类别:

IP电话/视频会议

开发平台:

Visual C++

  1. /*
  2.  * httpsrvr.cxx
  3.  *
  4.  * HTTP server classes.
  5.  *
  6.  * Portable Windows Library
  7.  *
  8.  * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
  9.  *
  10.  * The contents of this file are subject to the Mozilla Public License
  11.  * Version 1.0 (the "License"); you may not use this file except in
  12.  * compliance with the License. You may obtain a copy of the License at
  13.  * http://www.mozilla.org/MPL/
  14.  *
  15.  * Software distributed under the License is distributed on an "AS IS"
  16.  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  17.  * the License for the specific language governing rights and limitations
  18.  * under the License.
  19.  *
  20.  * The Original Code is Portable Windows Library.
  21.  *
  22.  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
  23.  *
  24.  * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
  25.  * All Rights Reserved.
  26.  *
  27.  * Contributor(s): ______________________________________.
  28.  *
  29.  * $Log: httpsrvr.cxx,v $
  30.  * Revision 1.32  2000/05/02 07:55:22  craigs
  31.  * Changed static PString to static const char * to avoid "memory leak"
  32.  *
  33.  * Revision 1.31  1999/05/13 04:04:04  robertj
  34.  * Fixed problem of initialised commandName in ConnectionInfo.
  35.  *
  36.  * Revision 1.30  1999/05/12 01:40:47  robertj
  37.  * Fixed "unknown" response codes being passed on when used with an "unknown" command.
  38.  *
  39.  * Revision 1.29  1999/05/11 12:23:22  robertj
  40.  * Fixed search for persistent connection to accept kee-alive on multile MIME fields.
  41.  *
  42.  * Revision 1.28  1999/05/04 15:26:01  robertj
  43.  * Improved HTTP/1.1 compatibility (pass through user commands).
  44.  * Fixed problems with quicktime installer.
  45.  *
  46.  * Revision 1.27  1999/04/24 11:50:11  robertj
  47.  * Changed HTTP command parser so will work if some idiot puts spaces in a URL.
  48.  *
  49.  * Revision 1.26  1999/04/21 01:58:08  robertj
  50.  * Fixed problem with reading data for request using second form of PHTTPRequestInfo constructor.
  51.  *
  52.  * Revision 1.25  1998/11/30 04:51:59  robertj
  53.  * New directory structure
  54.  *
  55.  * Revision 1.24  1998/11/14 01:11:38  robertj
  56.  * PPC linux GNU compatibility.
  57.  *
  58.  * Revision 1.23  1998/10/31 12:49:23  robertj
  59.  * Added read/write mutex to the HTTP space variable to avoid thread crashes.
  60.  *
  61.  * Revision 1.22  1998/10/25 01:02:41  craigs
  62.  * Added ability to specify per-directory authorisation for PHTTPDirectory
  63.  *
  64.  * Revision 1.21  1998/10/13 14:06:23  robertj
  65.  * Complete rewrite of memory leak detection code.
  66.  *
  67.  * Revision 1.20  1998/09/23 06:22:13  robertj
  68.  * Added open source copyright license.
  69.  *
  70.  * Revision 1.19  1998/08/06 00:54:22  robertj
  71.  * Fixed bug in sending empty files, caused endless wait in Netscape.
  72.  *
  73.  * Revision 1.18  1998/06/16 03:32:14  robertj
  74.  * Propagated persistence and proxy flags in new connection info instances.
  75.  *
  76.  * Revision 1.17  1998/04/01 01:55:16  robertj
  77.  * Fixed bug when serving HTTPFile that has zero bytes in it.
  78.  *
  79.  * Revision 1.16  1998/02/03 06:24:10  robertj
  80.  * Added local address and port to PHTTPRequest.
  81.  * Fixed bug in default entity length. should be read to EOF.
  82.  * Fixed OnError() so can detec HTML bosy tag with parameters.
  83.  *
  84.  * Revision 1.14  1998/01/26 00:42:19  robertj
  85.  * Added more information to PHTTPConnectionInfo.
  86.  * Made SetDefaultMIMEFields in HTTP Server not set those fields if already set.
  87.  *
  88.  * Revision 1.13  1997/10/30 10:22:04  robertj
  89.  * Added multiple user basic authorisation scheme.
  90.  *
  91.  * Revision 1.12  1997/10/03 13:39:25  robertj
  92.  * Fixed race condition on socket close in Select() function.
  93.  *
  94.  * Revision 1.12  1997/10/03 13:31:12  craigs
  95.  * Added ability to access client socket from within HTTP resources
  96.  *
  97.  * Revision 1.11  1997/08/04 10:44:36  robertj
  98.  * Improved receiving of a POST on a non-persistant connection, do not wait for EOF if have CRLF.
  99.  *
  100.  * Revision 1.10  1997/07/14 11:47:13  robertj
  101.  * Added "const" to numerous variables.
  102.  *
  103.  * Revision 1.9  1997/07/08 13:10:26  robertj
  104.  * Fixed bug in HTTP server where standard error text is not sent to remote client.
  105.  *
  106.  * Revision 1.8  1997/04/15 14:32:19  robertj
  107.  * Fixed case problem for HTTP version string.
  108.  *
  109.  * Revision 1.7  1997/03/20 13:01:32  robertj
  110.  * Fixed bug in proxy POST having unexpexted reset of connection.
  111.  *
  112.  * Revision 1.6  1997/02/09 04:09:30  robertj
  113.  * Fixed GCC warning
  114.  *
  115.  * Revision 1.5  1997/01/12 04:15:23  robertj
  116.  * Globalised MIME tag strings.
  117.  *
  118.  * Revision 1.4  1996/12/12 09:24:16  robertj
  119.  * Persistent proxy connection support (work in progress).
  120.  *
  121.  * Revision 1.3  1996/11/10 21:09:33  robertj
  122.  * Removed redundent GetSocket() call.
  123.  * Added flush of stream after processing request, important on persistent connections.
  124.  *
  125.  * Revision 1.2  1996/10/26 03:31:05  robertj
  126.  * Changed OnError so can pass in full HTML page as parameter.
  127.  *
  128.  * Revision 1.1  1996/09/14 13:02:18  robertj
  129.  * Initial revision
  130.  *
  131.  */
  132. #include <ptlib.h>
  133. #include <ptlib/sockets.h>
  134. #include <ptclib/http.h>
  135. #include <ctype.h>
  136. #define new PNEW
  137. // undefine to remove support for persistant connections
  138. #define HAS_PERSISTANCE
  139. // define to enable work-around for Netscape persistant connection bug
  140. // set to lifetime of suspect sockets (in seconds)
  141. #define STRANGE_NETSCAPE_BUG 3
  142. // maximum lifetime (in seconds) of persistant connections
  143. #define MAX_LIFETIME 30
  144. // maximum lifetime (in transactions) of persistant connections
  145. #define MAX_TRANSACTIONS 10
  146. // maximum delay between characters whilst reading a line of text
  147. #define READLINE_TIMEOUT 30
  148. //  filename to use for directory access directives
  149. static const char * accessFilename = "_access";
  150. //////////////////////////////////////////////////////////////////////////////
  151. // PHTTPSpace
  152. PHTTPSpace::PHTTPSpace()
  153. {
  154.   mutex = new PReadWriteMutex;
  155.   root = new Node(PString(), NULL);
  156. }
  157. void PHTTPSpace::DestroyContents()
  158. {
  159.   delete mutex;
  160.   delete root;
  161. }
  162. void PHTTPSpace::CloneContents(const PHTTPSpace * c)
  163. {
  164.   mutex = new PReadWriteMutex;
  165.   root = new Node(*c->root);
  166. }
  167. void PHTTPSpace::CopyContents(const PHTTPSpace & c)
  168. {
  169.   mutex = c.mutex;
  170.   root = c.root;
  171. }
  172. PHTTPSpace::Node::Node(const PString & nam, Node * parentNode)
  173.   : PString(nam)
  174. {
  175.   parent = parentNode;
  176.   resource = NULL;
  177. }
  178. PHTTPSpace::Node::~Node()
  179. {
  180.   delete resource;
  181. }
  182. BOOL PHTTPSpace::AddResource(PHTTPResource * res, AddOptions overwrite)
  183. {
  184.   PAssert(res != NULL, PInvalidParameter);
  185.   const PStringArray & path = res->GetURL().GetPath();
  186.   Node * node = root;
  187.   for (PINDEX i = 0; i < path.GetSize(); i++) {
  188.     if (path[i].IsEmpty())
  189.       break;
  190.     if (node->resource != NULL)
  191.       return FALSE;   // Already a resource in tree in partial path
  192.     PINDEX pos = node->children.GetValuesIndex(path[i]);
  193.     if (pos == P_MAX_INDEX)
  194.       pos = node->children.Append(new Node(path[i], node));
  195.     node = &node->children[pos];
  196.   }
  197.   if (!node->children.IsEmpty())
  198.     return FALSE;   // Already a resource in tree further down path.
  199.   if (overwrite == ErrorOnExist && node->resource != NULL)
  200.     return FALSE;   // Already a resource in tree at leaf
  201.   delete node->resource;
  202.   node->resource = res;
  203.   return TRUE;
  204. }
  205. BOOL PHTTPSpace::DelResource(const PURL & url)
  206. {
  207.   const PStringArray & path = url.GetPath();
  208.   Node * node = root;
  209.   for (PINDEX i = 0; i < path.GetSize(); i++) {
  210.     if (path[i].IsEmpty())
  211.       break;
  212.     PINDEX pos = node->children.GetValuesIndex(path[i]);
  213.     if (pos == P_MAX_INDEX)
  214.       return FALSE;
  215.     node = &node->children[pos];
  216.     if (node->resource != NULL)
  217.       return FALSE;
  218.   }
  219.   if (!node->children.IsEmpty())
  220.     return FALSE;   // Still a resource in tree further down path.
  221.   do {
  222.     Node * par = node->parent;
  223.     par->children.Remove(node);
  224.     node = par;
  225.   } while (node != NULL && node->children.IsEmpty());
  226.   return TRUE;
  227. }
  228. static const char * const HTMLIndexFiles[] = {
  229.   "Welcome.html", "welcome.html", "index.html",
  230.   "Welcome.htm",  "welcome.htm",  "index.htm"
  231. };
  232. PHTTPResource * PHTTPSpace::FindResource(const PURL & url)
  233. {
  234.   const PStringArray & path = url.GetPath();
  235.   Node * node = root;
  236.   PINDEX i;
  237.   for (i = 0; i < path.GetSize(); i++) {
  238.     if (path[i].IsEmpty())
  239.       break;
  240.     PINDEX pos = node->children.GetValuesIndex(path[i]);
  241.     if (pos == P_MAX_INDEX)
  242.       return NULL;
  243.     node = &node->children[pos];
  244.     if (node->resource != NULL)
  245.       return node->resource;
  246.   }
  247.   for (i = 0; i < PARRAYSIZE(HTMLIndexFiles); i++) {
  248.     PINDEX pos = node->children.GetValuesIndex(PString(HTMLIndexFiles[i]));
  249.     if (pos != P_MAX_INDEX)
  250.       return node->children[pos].resource;
  251.   }
  252.   return NULL;
  253. }
  254. //////////////////////////////////////////////////////////////////////////////
  255. // PHTTPServer
  256. PHTTPServer::PHTTPServer()
  257. {
  258.   Construct();
  259. }
  260. PHTTPServer::PHTTPServer(const PHTTPSpace & space)
  261.   : urlSpace(space)
  262. {
  263.   Construct();
  264. }
  265. void PHTTPServer::Construct()
  266. {
  267.   transactionCount = 0;
  268.   SetReadLineTimeout(PTimeInterval(0, READLINE_TIMEOUT));
  269. }
  270. BOOL PHTTPServer::ProcessCommand()
  271. {
  272.   PString args;
  273.   PINDEX cmd;
  274.   // if this is not the first command received by this socket, then set
  275.   // the read timeout appropriately.
  276.   if (transactionCount > 0) 
  277.     SetReadTimeout(nextTimeout);
  278.   // this will only return false upon timeout or completely invalid command
  279.   if (!ReadCommand(cmd, args))
  280.     return FALSE;
  281.   connectInfo.commandCode = (Commands)cmd;
  282.   if (cmd < NumCommands)
  283.     connectInfo.commandName = commandNames[cmd];
  284.   else {
  285.     PINDEX spacePos = args.Find(' ');
  286.     connectInfo.commandName = args.Left(spacePos);
  287.     args = args.Mid(spacePos);
  288.   }
  289.   // if no tokens, error
  290.   if (args.IsEmpty()) {
  291.     OnError(BadRequest, args, connectInfo);
  292.     return FALSE;
  293.   }
  294.   if (!connectInfo.Initialise(*this, args))
  295.       return FALSE;
  296.   // now that we've decided we did receive a HTTP request, increment the
  297.   // count of transactions
  298.   transactionCount++;
  299.   nextTimeout.SetInterval(MAX_LIFETIME*1000);
  300.   PIPSocket * socket = GetSocket();
  301.   WORD myPort = (WORD)(socket != NULL ? socket->GetPort() : 80);
  302.   // the URL that comes with Connect requests is not quite kosher, so 
  303.   // mangle it into a proper URL and do NOT close the connection.
  304.   // for all other commands, close the read connection if not persistant
  305.   if (cmd == CONNECT) 
  306.     connectInfo.url = "https://" + args;
  307.   else {
  308.     connectInfo.url = args;
  309.     if (connectInfo.url.GetPort() == 0)
  310.       connectInfo.url.SetPort(myPort);
  311.   }
  312.   BOOL persist;
  313.   // If the incoming URL is of a proxy type then call OnProxy() which will
  314.   // probably just go OnError(). Even if a full URL is provided in the
  315.   // command we should check to see if it is a local server request and process
  316.   // it anyway even though we are not a proxy. The usage of GetHostName()
  317.   // below are to catch every way of specifying the host (name, alias, any of
  318.   // several IP numbers etc).
  319.   const PURL & url = connectInfo.GetURL();
  320.   if (url.GetScheme() != "http" ||
  321.       (url.GetPort() != 0 && url.GetPort() != myPort) ||
  322.       (!url.GetHostName() && !PIPSocket::IsLocalHost(url.GetHostName())))
  323.     persist = OnProxy(connectInfo);
  324.   else {
  325.     PString entityBody = ReadEntityBody();
  326.     // Handle the local request
  327.     PStringToString postData;
  328.     switch (cmd) {
  329.       case GET :
  330.         persist = OnGET(url, connectInfo.GetMIME(), connectInfo);
  331.         break;
  332.       case HEAD :
  333.         persist = OnHEAD(url, connectInfo.GetMIME(), connectInfo);
  334.         break;
  335.       case POST :
  336.         PURL::SplitQueryVars(entityBody, postData);
  337.         persist = OnPOST(url, connectInfo.GetMIME(), postData, connectInfo);
  338.         break;
  339.       case P_MAX_INDEX:
  340.       default:
  341.         persist = OnUnknown(args, connectInfo);
  342.     }
  343.   }
  344.   flush();
  345.   // if the function just indicated that the connection is to persist,
  346.   // and so did the client, then return TRUE. Note that all of the OnXXXX
  347.   // routines above must make sure that their return value is FALSE if
  348.   // if there was no ContentLength field in the response. This ensures that
  349.   // we always close the socket so the client will get the correct end of file
  350.   if (persist &&
  351.       connectInfo.IsPersistant() &&
  352.       transactionCount < MAX_TRANSACTIONS)
  353.     return TRUE;
  354. //  if (connectInfo.IsPersistant())
  355. //    PError << "Server: connection persistance end" << endl;
  356.   // close the output stream now and return FALSE
  357.   Shutdown(ShutdownWrite);
  358.   return FALSE;
  359. }
  360. PString PHTTPServer::ReadEntityBody()
  361. {
  362.   if (connectInfo.GetMajorVersion() < 1)
  363.     return PString();
  364.   PString entityBody;
  365.   long contentLength = connectInfo.GetEntityBodyLength();
  366.   // a content length of > 0 means read explicit length
  367.   // a content length of < 0 means read until EOF
  368.   // a content length of 0 means read nothing
  369.   int count = 0;
  370.   if (contentLength > 0) {
  371.     entityBody = ReadString((PINDEX)contentLength);
  372.   } else if (contentLength == -2) {
  373.     ReadLine(entityBody, FALSE);
  374.   } else if (contentLength < 0) {
  375.     while (Read(entityBody.GetPointer(count+1000)+count, 1000))
  376.       count += GetLastReadCount();
  377.     entityBody.SetSize(count+1);
  378.   }
  379.   // close the connection, if not persistant
  380.   if (!connectInfo.IsPersistant()) {
  381.     PIPSocket * socket = GetSocket();
  382.     if (socket != NULL)
  383.       socket->Shutdown(PIPSocket::ShutdownRead);
  384.   }
  385.   return entityBody;
  386. }
  387. PString PHTTPServer::GetServerName() const
  388. {
  389.   return "PWLib-HTTP-Server/1.0 PWLib/1.0";
  390. }
  391. void PHTTPServer::SetURLSpace(const PHTTPSpace & space)
  392. {
  393.   urlSpace = space;
  394. }
  395. BOOL PHTTPServer::OnGET(const PURL & url,
  396.                    const PMIMEInfo & info,
  397.          const PHTTPConnectionInfo & connectInfo)
  398. {
  399.   urlSpace.StartRead();
  400.   PHTTPResource * resource = urlSpace.FindResource(url);
  401.   if (resource == NULL) {
  402.     urlSpace.EndRead();
  403.     return OnError(NotFound, url.AsString(), connectInfo);
  404.   }
  405.   BOOL retval = resource->OnGET(*this, url, info, connectInfo);
  406.   urlSpace.EndRead();
  407.   return retval;
  408. }
  409. BOOL PHTTPServer::OnHEAD(const PURL & url,
  410.                     const PMIMEInfo & info,
  411.           const PHTTPConnectionInfo & connectInfo)
  412. {
  413.   urlSpace.StartRead();
  414.   PHTTPResource * resource = urlSpace.FindResource(url);
  415.   if (resource == NULL) {
  416.     urlSpace.EndRead();
  417.     return OnError(NotFound, url.AsString(), connectInfo);
  418.   }
  419.   BOOL retval = resource->OnHEAD(*this, url, info, connectInfo);
  420.   urlSpace.EndRead();
  421.   return retval;
  422. }
  423. BOOL PHTTPServer::OnPOST(const PURL & url,
  424.                     const PMIMEInfo & info,
  425.               const PStringToString & data,
  426.           const PHTTPConnectionInfo & connectInfo)
  427. {
  428.   urlSpace.StartRead();
  429.   PHTTPResource * resource = urlSpace.FindResource(url);
  430.   if (resource == NULL) {
  431.     urlSpace.EndRead();
  432.     return OnError(NotFound, url.AsString(), connectInfo);
  433.   }
  434.   BOOL retval = resource->OnPOST(*this, url, info, data, connectInfo);
  435.   urlSpace.EndRead();
  436.   return retval;
  437. }
  438. BOOL PHTTPServer::OnProxy(const PHTTPConnectionInfo & connectInfo)
  439. {
  440.   return OnError(BadGateway, "Proxy not implemented.", connectInfo) &&
  441.          connectInfo.GetCommandCode() != CONNECT;
  442. }
  443. struct httpStatusCodeStruct {
  444.   const char * text;
  445.   int  code;
  446.   BOOL allowedBody;
  447.   int  majorVersion;
  448.   int  minorVersion;
  449. };
  450. static const httpStatusCodeStruct * GetStatusCodeStruct(int code)
  451. {
  452.   static const httpStatusCodeStruct httpStatusDefn[] = {
  453.     // First entry MUST be InternalServerError
  454.     { "Internal Server Error",         PHTTP::InternalServerError, 1 },
  455.     { "OK",                            PHTTP::OK, 1 },
  456.     { "Unauthorised",                  PHTTP::UnAuthorised, 1 },
  457.     { "Forbidden",                     PHTTP::Forbidden, 1 },
  458.     { "Not Found",                     PHTTP::NotFound, 1 },
  459.     { "Not Modified",                  PHTTP::NotModified },
  460.     { "No Content",                    PHTTP::NoContent },
  461.     { "Bad Gateway",                   PHTTP::BadGateway, 1 },
  462.     { "Bad Request",                   PHTTP::BadRequest, 1 },
  463.     { "Continue",                      PHTTP::Continue, 1, 1, 1 },
  464.     { "Switching Protocols",           PHTTP::SwitchingProtocols, 1, 1, 1 },
  465.     { "Created",                       PHTTP::Created, 1 },
  466.     { "Accepted",                      PHTTP::Accepted, 1 },
  467.     { "Non-Authoritative Information", PHTTP::NonAuthoritativeInformation, 1, 1, 1 },
  468.     { "Reset Content",                 PHTTP::ResetContent, 0, 1, 1 },
  469.     { "Partial Content",               PHTTP::PartialContent, 1, 1, 1 },
  470.     { "Multiple Choices",              PHTTP::MultipleChoices, 1, 1, 1 },
  471.     { "Moved Permanently",             PHTTP::MovedPermanently, 1 },
  472.     { "Moved Temporarily",             PHTTP::MovedTemporarily, 1 },
  473.     { "See Other",                     PHTTP::SeeOther, 1, 1, 1 },
  474.     { "Use Proxy",                     PHTTP::UseProxy, 1, 1, 1 },
  475.     { "Payment Required",              PHTTP::PaymentRequired, 1, 1, 1 },
  476.     { "Method Not Allowed",            PHTTP::MethodNotAllowed, 1, 1, 1 },
  477.     { "None Acceptable",               PHTTP::NoneAcceptable, 1, 1, 1 },
  478.     { "Proxy Authetication Required",  PHTTP::ProxyAuthenticationRequired, 1, 1, 1 },
  479.     { "Request Timeout",               PHTTP::RequestTimeout, 1, 1, 1 },
  480.     { "Conflict",                      PHTTP::Conflict, 1, 1, 1 },
  481.     { "Gone",                          PHTTP::Gone, 1, 1, 1 },
  482.     { "Length Required",               PHTTP::LengthRequired, 1, 1, 1 },
  483.     { "Unless True",                   PHTTP::UnlessTrue, 1, 1, 1 },
  484.     { "Not Implemented",               PHTTP::NotImplemented, 1 },
  485.     { "Service Unavailable",           PHTTP::ServiceUnavailable, 1, 1, 1 },
  486.     { "Gateway Timeout",               PHTTP::GatewayTimeout, 1, 1, 1 }
  487.   };
  488.   // make sure the error code is valid
  489.   for (PINDEX i = 0; i < PARRAYSIZE(httpStatusDefn); i++)
  490.     if (code == httpStatusDefn[i].code)
  491.       return &httpStatusDefn[i];
  492.   return httpStatusDefn;
  493. }
  494. void PHTTPServer::StartResponse(StatusCode code,
  495.                                 PMIMEInfo & headers,
  496.                                 long bodySize)
  497. {
  498.   if (connectInfo.majorVersion < 1) 
  499.     return;
  500.   httpStatusCodeStruct dummyInfo;
  501.   const httpStatusCodeStruct * statusInfo;
  502.   if (connectInfo.commandCode < NumCommands)
  503.     statusInfo = GetStatusCodeStruct(code);
  504.   else {
  505.     dummyInfo.text = "";
  506.     dummyInfo.code = code;
  507.     dummyInfo.allowedBody = TRUE;
  508.     dummyInfo.majorVersion = connectInfo.majorVersion;
  509.     dummyInfo.minorVersion = connectInfo.minorVersion;
  510.     statusInfo = &dummyInfo;
  511.   }
  512.   // output the command line
  513.   *this << "HTTP/" << connectInfo.majorVersion << '.' << connectInfo.minorVersion
  514.         << ' ' << statusInfo->code << ' ' << statusInfo->text << "rn";
  515.   // output the headers. But don't put in ContentLength if the bodysize is zero
  516.   // because that can be confused by some browsers as meaning there is no body length.
  517.   if (bodySize > 0 && !headers.Contains(ContentLengthTag))
  518.     headers.SetAt(ContentLengthTag, PString(PString::Unsigned, (PINDEX)bodySize));
  519.   *this << setfill('r') << headers;
  520. #ifdef STRANGE_NETSCAPE_BUG
  521.   // The following is a work around for a strange bug in Netscape where it
  522.   // locks up when a persistent connection is made and data less than 1k
  523.   // (including MIME headers) is sent. Go figure....
  524.   if (bodySize < 1024 && connectInfo.GetMIME()(UserAgentTag).Find("Mozilla/2.0") != P_MAX_INDEX)
  525.     nextTimeout.SetInterval(STRANGE_NETSCAPE_BUG*1000);
  526. #endif
  527. }
  528. void PHTTPServer::SetDefaultMIMEInfo(PMIMEInfo & info,
  529.                      const PHTTPConnectionInfo & connectInfo)
  530. {
  531.   PTime now;
  532.   if (!info.Contains(DateTag))
  533.     info.SetAt(DateTag, now.AsString(PTime::RFC1123, PTime::GMT));
  534.   if (!info.Contains(MIMEVersionTag))
  535.     info.SetAt(MIMEVersionTag, "1.0");
  536.   if (!info.Contains(ServerTag))
  537.     info.SetAt(ServerTag, GetServerName());
  538.   if (connectInfo.IsPersistant()) {
  539.     if (connectInfo.IsProxyConnection())
  540. //{      PError << "Server: setting proxy persistant response" << endl;
  541.       info.SetAt(ProxyConnectionTag, KeepAliveTag);
  542. //    }
  543.     else
  544. //{      PError << "Server: setting direct persistant response" << endl;
  545.       info.SetAt(ConnectionTag, KeepAliveTag);
  546. //    }
  547.   }
  548. }
  549. BOOL PHTTPServer::OnUnknown(const PCaselessString & cmd, 
  550.                         const PHTTPConnectionInfo & connectInfo)
  551. {
  552.   return OnError(NotImplemented, cmd, connectInfo);
  553. }
  554. BOOL PHTTPServer::OnError(StatusCode code,
  555.              const PCaselessString & extra,
  556.          const PHTTPConnectionInfo & connectInfo)
  557. {
  558.   const httpStatusCodeStruct * statusInfo = GetStatusCodeStruct(code);
  559.   if (!connectInfo.IsCompatible(statusInfo->majorVersion, statusInfo->minorVersion))
  560.     statusInfo = GetStatusCodeStruct((code/100)*100);
  561.   PMIMEInfo headers;
  562.   SetDefaultMIMEInfo(headers, connectInfo);
  563.   if (!statusInfo->allowedBody) {
  564.     StartResponse(code, headers, 0);
  565.     return statusInfo->code == OK;
  566.   }
  567.   PString reply;
  568.   if (extra.Find("<body") != P_MAX_INDEX)
  569.     reply = extra;
  570.   else {
  571.     PHTML html;
  572.     html << PHTML::Title()
  573.          << statusInfo->code
  574.          << ' '
  575.          << statusInfo->text
  576.          << PHTML::Body()
  577.          << PHTML::Heading(1)
  578.          << statusInfo->code
  579.          << ' '
  580.          << statusInfo->text
  581.          << PHTML::Heading(1)
  582.          << extra
  583.          << PHTML::Body();
  584.     reply = html;
  585.   }
  586.   headers.SetAt(ContentTypeTag, "text/html");
  587.   StartResponse(code, headers, reply.GetLength());
  588.   WriteString(reply);
  589.   return statusInfo->code == OK;
  590. }
  591. //////////////////////////////////////////////////////////////////////////////
  592. // PHTTPSimpleAuth
  593. void PHTTPAuthority::DecodeBasicAuthority(const PString & authInfo,
  594.                                           PString & username,
  595.                                           PString & password)
  596. {
  597.   PString decoded;
  598.   if (authInfo(0, 5) *= "Basic ")
  599.     decoded = PBase64::Decode(authInfo(6, P_MAX_INDEX));
  600.   else
  601.     decoded = PBase64::Decode(authInfo);
  602.   PINDEX colonPos = decoded.Find(':');
  603.   if (colonPos == P_MAX_INDEX) {
  604.     username = decoded;
  605.     password = PString();
  606.   }
  607.   else {
  608.     username = decoded.Left(colonPos).Trim();
  609.     password = decoded.Mid(colonPos+1).Trim();
  610.   }
  611. }
  612. BOOL PHTTPAuthority::IsActive() const
  613. {
  614.   return TRUE;
  615. }
  616. //////////////////////////////////////////////////////////////////////////////
  617. // PHTTPSimpleAuth
  618. PHTTPSimpleAuth::PHTTPSimpleAuth(const PString & realm_,
  619.                                  const PString & username_,
  620.                                  const PString & password_)
  621.   : realm(realm_), username(username_), password(password_)
  622. {
  623.   PAssert(!realm, "Must have a realm!");
  624. }
  625. PObject * PHTTPSimpleAuth::Clone() const
  626. {
  627.   return new PHTTPSimpleAuth(realm, username, password);
  628. }
  629. BOOL PHTTPSimpleAuth::IsActive() const
  630. {
  631.   return !username || !password;
  632. }
  633. PString PHTTPSimpleAuth::GetRealm(const PHTTPRequest &) const
  634. {
  635.   return realm;
  636. }
  637. BOOL PHTTPSimpleAuth::Validate(const PHTTPRequest &,
  638.                                const PString & authInfo) const
  639. {
  640.   PString user, pass;
  641.   DecodeBasicAuthority(authInfo, user, pass);
  642.   return username == user && password == pass;
  643. }
  644. //////////////////////////////////////////////////////////////////////////////
  645. // PHTTPMultiSimpAuth
  646. PHTTPMultiSimpAuth::PHTTPMultiSimpAuth(const PString & realm_)
  647.   : realm(realm_)
  648. {
  649.   PAssert(!realm, "Must have a realm!");
  650. }
  651. PHTTPMultiSimpAuth::PHTTPMultiSimpAuth(const PString & realm_,
  652.                                        const PStringToString & users_)
  653.   : realm(realm_), users(users_)
  654. {
  655.   PAssert(!realm, "Must have a realm!");
  656. }
  657. PObject * PHTTPMultiSimpAuth::Clone() const
  658. {
  659.   return new PHTTPMultiSimpAuth(realm, users);
  660. }
  661. BOOL PHTTPMultiSimpAuth::IsActive() const
  662. {
  663.   return !users.IsEmpty();
  664. }
  665. PString PHTTPMultiSimpAuth::GetRealm(const PHTTPRequest &) const
  666. {
  667.   return realm;
  668. }
  669. BOOL PHTTPMultiSimpAuth::Validate(const PHTTPRequest &,
  670.                                   const PString & authInfo) const
  671. {
  672.   PString user, pass;
  673.   DecodeBasicAuthority(authInfo, user, pass);
  674.   return users.Contains(user) && users[user] == pass;
  675. }
  676. void PHTTPMultiSimpAuth::AddUser(const PString & username, const PString & password)
  677. {
  678.   users.SetAt(username, password);
  679. }
  680. //////////////////////////////////////////////////////////////////////////////
  681. // PHTTPRequest
  682. PHTTPRequest::PHTTPRequest(const PURL & u, const PMIMEInfo & iM, PHTTPServer & server)
  683.   : url(u), inMIME(iM), origin(0), localAddr(0), localPort(0)
  684. {
  685.   code        = PHTTP::OK;
  686.   contentSize = 0;
  687.   PIPSocket * socket = server.GetSocket();
  688.   if (socket != NULL) {
  689.     socket->GetPeerAddress(origin);
  690.     socket->GetLocalAddress(localAddr, localPort);
  691.   }
  692. }
  693. //////////////////////////////////////////////////////////////////////////////
  694. // PHTTPConnectionInfo
  695. PHTTPConnectionInfo::PHTTPConnectionInfo()
  696. {
  697.   commandCode       = PHTTP::NumCommands;
  698.   majorVersion      = 0;
  699.   minorVersion      = 9;
  700.   isPersistant      = FALSE;
  701.   isProxyConnection = FALSE;
  702.   entityBodyLength  = -1;
  703. }
  704. BOOL PHTTPConnectionInfo::Initialise(PHTTPServer & server, PString & args)
  705. {
  706.   // if only one argument, then it must be a version 0.9 simple request
  707.   PINDEX lastSpacePos = args.FindLast(' ');
  708.   static const PCaselessString httpId = "HTTP/";
  709.   if (lastSpacePos == P_MAX_INDEX || httpId != args(lastSpacePos+1, lastSpacePos+5)) {
  710.     majorVersion = 0;
  711.     minorVersion = 9;
  712.     return TRUE;
  713.   }
  714.   // otherwise, attempt to extract a version number
  715.   PCaselessString verStr = args.Mid(lastSpacePos + 6);
  716.   PINDEX dotPos = verStr.Find('.');
  717.   if (dotPos == 0 || dotPos >= verStr.GetLength()) {
  718.     server.OnError(PHTTP::BadRequest, "Malformed version number: " + verStr, *this);
  719.     return FALSE;
  720.   }
  721.   // should actually check if the text contains only digits, but the
  722.   // chances of matching everything else and it not being a valid number
  723.   // are pretty small, so don't bother
  724.   majorVersion = (int)verStr.Left(dotPos).AsInteger();
  725.   minorVersion = (int)verStr.Mid(dotPos+1).AsInteger();
  726.   args.Delete(lastSpacePos, P_MAX_INDEX);
  727.   // build our connection info reading MIME info until an empty line is
  728.   // received, or EOF
  729.   if (!mimeInfo.Read(server))
  730.     return FALSE;
  731.   isPersistant      = FALSE;
  732. #ifndef HAS_PERSISTANCE
  733.   isProxyConnection = FALSE;
  734. #else
  735.   PString str;
  736.   // check for Proxy-Connection and Connection strings
  737.   isProxyConnection = mimeInfo.HasKey(PHTTP::ProxyConnectionTag);
  738.   if (isProxyConnection)
  739.     str = mimeInfo[PHTTP::ProxyConnectionTag];
  740.   else if (mimeInfo.HasKey(PHTTP::ConnectionTag))
  741.     str = mimeInfo[PHTTP::ConnectionTag];
  742.   // get any connection options
  743.   if (!str) {
  744.     PStringArray tokens = str.Tokenise(", rn", FALSE);
  745.     for (PINDEX z = 0; !isPersistant && z < tokens.GetSize(); z++)
  746.       isPersistant = isPersistant || (tokens[z] *= PHTTP::KeepAliveTag);
  747.   }
  748. #endif
  749. //    if (connectInfo.IsPersistant()) {
  750. //      if (connectInfo.IsProxyConnection())
  751. //        PError << "Server: Persistant proxy connection received" << endl;
  752. //      else
  753. //        PError << "Server: Persistant direct connection received" << endl;
  754. //    }
  755.   // If the protocol is version 1.0 or greater, there is MIME info, and the
  756.   // prescence of a an entity body is signalled by the inclusion of
  757.   // Content-Length header. If the protocol is less than version 1.0, then 
  758.   // there is no entity body!
  759.   // if the client specified a persistant connection, then use the
  760.   // ContentLength field. If there is no content length field, then
  761.   // assume a ContentLength of zero and close the connection.
  762.   // The spec actually says to read until end of file in this case,
  763.   // but Netscape hangs if this is done.
  764.   // If the client didn't specify a persistant connection, then use the
  765.   // ContentLength if there is one or read until end of file if there isn't
  766.   if (!isPersistant)
  767.     entityBodyLength = mimeInfo.GetInteger(PHTTP::ContentLengthTag,
  768.                                            (commandCode == PHTTP::POST) ? -2 : 0);
  769.   else {
  770.     entityBodyLength = mimeInfo.GetInteger(PHTTP::ContentLengthTag, -1);
  771.     if (entityBodyLength < 0) {
  772. //        PError << "Server: persistant connection has no content length" << endl;
  773.       entityBodyLength = 0;
  774.       mimeInfo.SetAt(PHTTP::ContentLengthTag, "0");
  775.     }
  776.   }
  777.   return TRUE;
  778. }
  779. void PHTTPConnectionInfo::SetMIME(const PString & tag, const PString & value)
  780. {
  781.   mimeInfo.MakeUnique();
  782.   mimeInfo.SetAt(tag, value);
  783. }
  784. void PHTTPConnectionInfo::SetPersistance(BOOL newPersist)
  785. {
  786. #ifdef HAS_PERSISTANCE
  787.   isPersistant = newPersist;
  788. #else
  789.   isPersistant = FALSE;
  790. #endif
  791. }
  792. BOOL PHTTPConnectionInfo::IsCompatible(int major, int minor) const
  793. {
  794.   if (minor == 0 && major == 0)
  795.     return TRUE;
  796.   else
  797.     return (majorVersion > major) ||
  798.            ((majorVersion == major) && (minorVersion >= minor));
  799. }
  800. //////////////////////////////////////////////////////////////////////////////
  801. // PHTTPResource
  802. PHTTPResource::PHTTPResource(const PURL & url)
  803.   : baseURL(url)
  804. {
  805.   authority = NULL;
  806.   hitCount = 0;
  807. }
  808. PHTTPResource::PHTTPResource(const PURL & url, const PHTTPAuthority & auth)
  809.   : baseURL(url)
  810. {
  811.   authority = (PHTTPAuthority *)auth.Clone();
  812.   hitCount = 0;
  813. }
  814. PHTTPResource::PHTTPResource(const PURL & url, const PString & type)
  815.   : baseURL(url), contentType(type)
  816. {
  817.   authority = NULL;
  818.   hitCount = 0;
  819. }
  820. PHTTPResource::PHTTPResource(const PURL & url,
  821.                              const PString & type,
  822.                              const PHTTPAuthority & auth)
  823.   : baseURL(url), contentType(type)
  824. {
  825.   authority = (PHTTPAuthority *)auth.Clone();
  826.   hitCount = 0;
  827. }
  828. PHTTPResource::~PHTTPResource()
  829. {
  830.   delete authority;
  831. }
  832. BOOL PHTTPResource::OnGET(PHTTPServer & server,
  833.                            const PURL & url,
  834.                       const PMIMEInfo & info,
  835.             const PHTTPConnectionInfo & connectInfo)
  836. {
  837.   return OnGETOrHEAD(server, url, info, connectInfo, TRUE);
  838. }
  839. BOOL PHTTPResource::OnHEAD(PHTTPServer & server,
  840.                            const PURL & url,
  841.                       const PMIMEInfo & info,
  842.             const PHTTPConnectionInfo & connectInfo)
  843. {
  844.   return OnGETOrHEAD(server, url, info, connectInfo, FALSE);
  845. }
  846. BOOL PHTTPResource::OnGETOrHEAD(PHTTPServer & server,
  847.                            const PURL & url,
  848.                       const PMIMEInfo & info,
  849.             const PHTTPConnectionInfo & connectInfo,
  850.                                    BOOL isGET)
  851. {
  852.   if (isGET && info.Contains(PHTTP::IfModifiedSinceTag) &&
  853.                            !IsModifiedSince(PTime(info[PHTTP::IfModifiedSinceTag]))) 
  854.     return server.OnError(PHTTP::NotModified, url.AsString(), connectInfo);
  855.   PHTTPRequest * request = CreateRequest(url, info, server);
  856.   BOOL retVal = TRUE;
  857.   if (CheckAuthority(server, *request, connectInfo)) {
  858.     retVal = FALSE;
  859.     server.SetDefaultMIMEInfo(request->outMIME, connectInfo);
  860.     PTime expiryDate;
  861.     if (GetExpirationDate(expiryDate))
  862.       request->outMIME.SetAt(PHTTP::ExpiresTag,
  863.                               expiryDate.AsString(PTime::RFC1123, PTime::GMT));
  864.     if (!LoadHeaders(*request)) 
  865.       retVal = server.OnError(request->code, url.AsString(), connectInfo);
  866.     else if (!isGET)
  867.       retVal = request->outMIME.Contains(PHTTP::ContentLengthTag);
  868.     else {
  869.       hitCount++;
  870.       retVal = OnGETData(server, url, connectInfo, *request);
  871.     }
  872.   }
  873.   delete request;
  874.   return retVal;
  875. }
  876. BOOL PHTTPResource::OnGETData(PHTTPServer & server,
  877.                                const PURL & /*url*/,
  878.                 const PHTTPConnectionInfo & /*connectInfo*/,
  879.                              PHTTPRequest & request)
  880. {
  881.   if (!request.outMIME.Contains(PHTTP::ContentTypeTag) && !contentType)
  882.     request.outMIME.SetAt(PHTTP::ContentTypeTag, contentType);
  883.   PCharArray data;
  884.   if (LoadData(request, data)) {
  885.     server.StartResponse(request.code, request.outMIME, request.contentSize);
  886.     do {
  887.       server.Write(data, data.GetSize());
  888.       data.SetSize(0);
  889.     } while (LoadData(request, data));
  890.   }
  891.   else
  892.     server.StartResponse(request.code, request.outMIME, data.GetSize());
  893.   server.Write(data, data.GetSize());
  894.   return request.outMIME.Contains(PHTTP::ContentLengthTag);
  895. }
  896. BOOL PHTTPResource::OnPOST(PHTTPServer & server,
  897.                             const PURL & url,
  898.                        const PMIMEInfo & info,
  899.                  const PStringToString & data,
  900.              const PHTTPConnectionInfo & connectInfo)
  901. {
  902.   PHTTPRequest * request = CreateRequest(url, info, server);
  903.   BOOL persist = TRUE;
  904.   if (CheckAuthority(server, *request, connectInfo)) {
  905.     PHTML msg;
  906.     persist = Post(*request, data, msg);
  907.     if (msg.IsEmpty())
  908.       persist = server.OnError(request->code, "", connectInfo) && persist;
  909.     else {
  910.       if (msg.Is(PHTML::InBody))
  911.         msg << PHTML::Body();
  912.       request->outMIME.SetAt(PHTTP::ContentTypeTag, "text/html");
  913.       PINDEX len = msg.GetLength();
  914.       server.StartResponse(request->code, request->outMIME, len);
  915.       persist = server.Write((const char *)msg, len) && persist;
  916.     }
  917.   }
  918.   delete request;
  919.   return persist;
  920. }
  921. BOOL PHTTPResource::CheckAuthority(PHTTPServer & server,
  922.                             const PHTTPRequest & request,
  923.                      const PHTTPConnectionInfo & connectInfo)
  924. {
  925.   if (authority == NULL)
  926.     return TRUE;
  927.   return CheckAuthority(*authority, server, request, connectInfo);
  928. }
  929.     
  930.     
  931. BOOL PHTTPResource::CheckAuthority(PHTTPAuthority & authority,
  932.                                       PHTTPServer & server,
  933.                                const PHTTPRequest & request,
  934.                         const PHTTPConnectionInfo & connectInfo)
  935. {
  936.   if (!authority.IsActive())
  937.     return TRUE;
  938.   // if this is an authorisation request...
  939.   if (request.inMIME.Contains(PHTTP::AuthorizationTag) &&
  940.       authority.Validate(request, request.inMIME[PHTTP::AuthorizationTag]))
  941.     return TRUE;
  942.   // it must be a request for authorisation
  943.   PMIMEInfo headers;
  944.   server.SetDefaultMIMEInfo(headers, connectInfo);
  945.   headers.SetAt(PHTTP::WWWAuthenticateTag,
  946.                        "Basic realm="" + authority.GetRealm(request) + """);
  947.   headers.SetAt(PHTTP::ContentTypeTag, "text/html");
  948.   const httpStatusCodeStruct * statusInfo =
  949.                                GetStatusCodeStruct(PHTTP::UnAuthorised);
  950.   PHTML reply;
  951.   reply << PHTML::Title()
  952.         << statusInfo->code
  953.         << ' '
  954.         << statusInfo->text
  955.         << PHTML::Body()
  956.         << PHTML::Heading(1)
  957.         << statusInfo->code
  958.         << ' '
  959.         << statusInfo->text
  960.         << PHTML::Heading(1)
  961.         << "Your request cannot be authorised because it requires authentication."
  962.         << PHTML::Paragraph()
  963.         << "This may be because you entered an incorrect username or password, "
  964.         << "or because your browser is not performing Basic authentication."
  965.         << PHTML::Body();
  966.   server.StartResponse(PHTTP::UnAuthorised, headers, reply.GetLength());
  967.   server.WriteString(reply);
  968.   return FALSE;
  969. }
  970. void PHTTPResource::SetAuthority(const PHTTPAuthority & auth)
  971. {
  972.   delete authority;
  973.   authority = (PHTTPAuthority *)auth.Clone();
  974. }
  975. void PHTTPResource::ClearAuthority()
  976. {
  977.   delete authority;
  978.   authority = NULL;
  979. }
  980. BOOL PHTTPResource::IsModifiedSince(const PTime &)
  981. {
  982.   return TRUE;
  983. }
  984. BOOL PHTTPResource::GetExpirationDate(PTime &)
  985. {
  986.   return FALSE;
  987. }
  988. PHTTPRequest * PHTTPResource::CreateRequest(const PURL & url,
  989.                                             const PMIMEInfo & inMIME,
  990.             PHTTPServer & socket)
  991. {
  992.   return new PHTTPRequest(url, inMIME, socket);
  993. }
  994. BOOL PHTTPResource::LoadData(PHTTPRequest & request, PCharArray & data)
  995. {
  996.   PString text = LoadText(request);
  997.   OnLoadedText(request, text);
  998.   text.SetSize(text.GetLength());  // Lose the trailing ''
  999.   data = text;
  1000.   return FALSE;
  1001. }
  1002. PString PHTTPResource::LoadText(PHTTPRequest &)
  1003. {
  1004.   PAssertAlways(PUnimplementedFunction);
  1005.   return PString();
  1006. }
  1007. void PHTTPResource::OnLoadedText(PHTTPRequest &, PString &)
  1008. {
  1009.   // Do nothing
  1010. }
  1011. BOOL PHTTPResource::Post(PHTTPRequest & request,
  1012.                          const PStringToString &,
  1013.                          PHTML & msg)
  1014. {
  1015.   request.code = PHTTP::MethodNotAllowed;
  1016.   msg = "Error in POST";
  1017.   msg << "Post to this resource is not allowed" << PHTML::Body();
  1018.   return TRUE;
  1019. }
  1020. //////////////////////////////////////////////////////////////////////////////
  1021. // PHTTPString
  1022. PHTTPString::PHTTPString(const PURL & url)
  1023.   : PHTTPResource(url, "text/html")
  1024. {
  1025. }
  1026. PHTTPString::PHTTPString(const PURL & url,
  1027.                          const PHTTPAuthority & auth)
  1028.   : PHTTPResource(url, "text/html", auth)
  1029. {
  1030. }
  1031. PHTTPString::PHTTPString(const PURL & url, const PString & str)
  1032.   : PHTTPResource(url, "text/html"), string(str)
  1033. {
  1034. }
  1035. PHTTPString::PHTTPString(const PURL & url,
  1036.                          const PString & str,
  1037.                          const PString & type)
  1038.   : PHTTPResource(url, type), string(str)
  1039. {
  1040. }
  1041. PHTTPString::PHTTPString(const PURL & url,
  1042.                          const PString & str,
  1043.                          const PHTTPAuthority & auth)
  1044.   : PHTTPResource(url, "text/html", auth), string(str)
  1045. {
  1046. }
  1047. PHTTPString::PHTTPString(const PURL & url,
  1048.                          const PString & str,
  1049.                          const PString & type,
  1050.                          const PHTTPAuthority & auth)
  1051.   : PHTTPResource(url, type, auth), string(str)
  1052. {
  1053. }
  1054. BOOL PHTTPString::LoadHeaders(PHTTPRequest & request)
  1055. {
  1056.   request.contentSize = string.GetLength();
  1057.   return TRUE;
  1058. }
  1059. PString PHTTPString::LoadText(PHTTPRequest &)
  1060. {
  1061.   return string;
  1062. }
  1063. //////////////////////////////////////////////////////////////////////////////
  1064. // PHTTPFile
  1065. PHTTPFile::PHTTPFile(const PURL & url, int)
  1066.   : PHTTPResource(url)
  1067. {
  1068. }
  1069. PHTTPFile::PHTTPFile(const PString & filename)
  1070.   : PHTTPResource(filename), filePath(filename)
  1071. {
  1072.   SetContentType(PMIMEInfo::GetContentType(filePath.GetType()));
  1073. }
  1074. PHTTPFile::PHTTPFile(const PString & filename, const PHTTPAuthority & auth)
  1075.   : PHTTPResource(filename, auth), filePath(filename)
  1076. {
  1077. }
  1078. PHTTPFile::PHTTPFile(const PURL & url, const PFilePath & path)
  1079.   : PHTTPResource(url), filePath(path)
  1080. {
  1081.   SetContentType(PMIMEInfo::GetContentType(path.GetType()));
  1082. }
  1083. PHTTPFile::PHTTPFile(const PURL & url,
  1084.                      const PFilePath & path,
  1085.                      const PString & type)
  1086.   : PHTTPResource(url, type), filePath(path)
  1087. {
  1088. }
  1089. PHTTPFile::PHTTPFile(const PURL & url,
  1090.                      const PFilePath & path,
  1091.                      const PHTTPAuthority & auth)
  1092.   : PHTTPResource(url, PString(), auth), filePath(path)
  1093. {
  1094.   SetContentType(PMIMEInfo::GetContentType(filePath.GetType()));
  1095. }
  1096. PHTTPFile::PHTTPFile(const PURL & url,
  1097.                      const PFilePath & path,
  1098.                      const PString & type,
  1099.                      const PHTTPAuthority & auth)
  1100.   : PHTTPResource(url, type, auth), filePath(path)
  1101. {
  1102. }
  1103. PHTTPFileRequest::PHTTPFileRequest(const PURL & url,
  1104.                                    const PMIMEInfo & inMIME,
  1105.                                    PHTTPServer & server)
  1106.   : PHTTPRequest(url, inMIME, server)
  1107. {
  1108. }
  1109. PHTTPRequest * PHTTPFile::CreateRequest(const PURL & url,
  1110.                                         const PMIMEInfo & inMIME,
  1111.         PHTTPServer & server)
  1112. {
  1113.   return new PHTTPFileRequest(url, inMIME, server);
  1114. }
  1115. BOOL PHTTPFile::LoadHeaders(PHTTPRequest & request)
  1116. {
  1117.   PFile & file = ((PHTTPFileRequest&)request).file;
  1118.   if (!file.Open(filePath, PFile::ReadOnly)) {
  1119.     request.code = PHTTP::NotFound;
  1120.     return FALSE;
  1121.   }
  1122.   request.contentSize = file.GetLength();
  1123.   return TRUE;
  1124. }
  1125. BOOL PHTTPFile::LoadData(PHTTPRequest & request, PCharArray & data)
  1126. {
  1127.   if (contentType(0, 4) == "text/")
  1128.     return PHTTPResource::LoadData(request, data);
  1129.   PFile & file = ((PHTTPFileRequest&)request).file;
  1130.   PAssert(file.IsOpen(), PLogicError);
  1131.   PINDEX count = file.GetLength() - file.GetPosition();
  1132.   if (count > 10000)
  1133.     count = 10000;
  1134.   if (count > 0)
  1135.     PAssert(file.Read(data.GetPointer(count), count), PLogicError);
  1136.   if (!file.IsEndOfFile())
  1137.     return TRUE;
  1138.   file.Close();
  1139.   return FALSE;
  1140. }
  1141. PString PHTTPFile::LoadText(PHTTPRequest & request)
  1142. {
  1143.   PFile & file = ((PHTTPFileRequest&)request).file;
  1144.   PAssert(file.IsOpen(), PLogicError);
  1145.   PINDEX count = file.GetLength();
  1146.   PString text;
  1147.   if (count > 0)
  1148.     PAssert(file.Read(text.GetPointer(count+1), count), PLogicError);
  1149.   PAssert(file.Close(), PLogicError);
  1150.   return text;
  1151. }
  1152. //////////////////////////////////////////////////////////////////////////////
  1153. // PHTTPDirectory
  1154. PHTTPDirectory::PHTTPDirectory(const PURL & url, const PDirectory & dir)
  1155.   : PHTTPFile(url, 0), basePath(dir), allowDirectoryListing(TRUE)
  1156. {
  1157. }
  1158. PHTTPDirectory::PHTTPDirectory(const PURL & url,
  1159.                                const PDirectory & dir,
  1160.                                const PHTTPAuthority & auth)
  1161.   : PHTTPFile(url, PString(), auth), basePath(dir), allowDirectoryListing(TRUE)
  1162. {
  1163. }
  1164. PHTTPDirRequest::PHTTPDirRequest(const PURL & url,
  1165.                                  const PMIMEInfo & inMIME,
  1166.                  PHTTPServer & server)
  1167.   : PHTTPFileRequest(url, inMIME, server)
  1168. {
  1169. }
  1170. PHTTPRequest * PHTTPDirectory::CreateRequest(const PURL & url,
  1171.                                         const PMIMEInfo & inMIME,
  1172.         PHTTPServer & socket)
  1173. {
  1174.   return new PHTTPDirRequest(url, inMIME, socket);
  1175. }
  1176. void PHTTPDirectory::EnableAuthorisation(const PString & realm)
  1177. {
  1178.   authorisationRealm = realm;
  1179. }
  1180. BOOL PHTTPDirectory::FindAuthorisations(const PDirectory & dir, PString & realm, PStringToString & authorisations)
  1181. {
  1182.   PFilePath fn = dir + accessFilename;
  1183.   PTextFile file;
  1184.   BOOL first = TRUE;
  1185.   if (file.Open(fn, PFile::ReadOnly)) {
  1186.     PString line;
  1187.     while (file.ReadLine(line)) {
  1188.       if (first) {
  1189.         realm = line.Trim();
  1190.         first = FALSE;
  1191.       } else {
  1192.         PStringArray tokens = line.Tokenise(':');
  1193.         if (tokens.GetSize() > 1)
  1194.           authorisations.SetAt(tokens[0].Trim(), tokens[1].Trim());
  1195.       }
  1196.     }
  1197.     return TRUE;
  1198.   }
  1199.     
  1200.   if (dir.IsRoot() || (dir == basePath))
  1201.     return FALSE;
  1202.   return FindAuthorisations(dir.GetParent(), realm, authorisations);
  1203. }
  1204. BOOL PHTTPDirectory::CheckAuthority(PHTTPServer & server,
  1205.                              const PHTTPRequest & request,
  1206.                       const PHTTPConnectionInfo & conInfo)
  1207. {
  1208.   PFilePath & realPath = ((PHTTPDirRequest&)request).realPath;
  1209.   // construct the real path name
  1210.   const PStringArray & path = request.url.GetPath();
  1211.   realPath = basePath;
  1212.   PINDEX i;
  1213.   for (i = baseURL.GetPath().GetSize(); i < path.GetSize()-1; i++)
  1214.     realPath += path[i] + PDIR_SEPARATOR;
  1215.   // append the last path element
  1216.   if (i < path.GetSize())
  1217.     realPath += path[i];
  1218.   // if access control is enabled, then search parent directories for password files
  1219.   PStringToString authorisations;
  1220.   PString newRealm;
  1221.   if (authorisationRealm.IsEmpty() ||
  1222.       !FindAuthorisations(realPath.GetDirectory(), newRealm, authorisations) ||
  1223.       authorisations.GetSize() == 0)
  1224.     return TRUE;
  1225.   PHTTPMultiSimpAuth authority(newRealm, authorisations);
  1226.   return PHTTPResource::CheckAuthority(authority, server, request, conInfo);
  1227. }
  1228. BOOL PHTTPDirectory::LoadHeaders(PHTTPRequest & request)
  1229. {
  1230.   PFilePath & realPath = ((PHTTPDirRequest&)request).realPath;
  1231.     
  1232.   // if not able to obtain resource information, then consider the resource "not found"
  1233.   PFileInfo info;
  1234.   if (!PFile::GetInfo(realPath, info)) {
  1235.     request.code = PHTTP::NotFound;
  1236.     return FALSE;
  1237.   }
  1238.   // if the resource is a file, and the file can't be opened, then return "not found"
  1239.   PFile & file = ((PHTTPDirRequest&)request).file;
  1240.   if (info.type != PFileInfo::SubDirectory) {
  1241.     if (!file.Open(realPath, PFile::ReadOnly) ||
  1242.         (!authorisationRealm.IsEmpty() && realPath.GetFileName() == accessFilename)) {
  1243.       request.code = PHTTP::NotFound;
  1244.       return FALSE;
  1245.     }
  1246.   } 
  1247.   // resource is a directory - if index files disabled, then return "not found"
  1248.   else if (!allowDirectoryListing) {
  1249.     request.code = PHTTP::NotFound;
  1250.     return FALSE;
  1251.   }
  1252.   // else look for index files
  1253.   else {
  1254.     PINDEX i;
  1255.     for (i = 0; i < PARRAYSIZE(HTMLIndexFiles); i++)
  1256.       if (file.Open(realPath +
  1257.                           PDIR_SEPARATOR + HTMLIndexFiles[i], PFile::ReadOnly))
  1258.         break;
  1259.   }
  1260.   // open the file and return information
  1261.   PString & fakeIndex = ((PHTTPDirRequest&)request).fakeIndex;
  1262.   if (file.IsOpen()) {
  1263.     contentType = PMIMEInfo::GetContentType(file.GetFilePath().GetType());
  1264.     request.contentSize = file.GetLength();
  1265.     fakeIndex = PString();
  1266.     return TRUE;
  1267.   }
  1268.   // construct a directory listing
  1269.   contentType = "text/html";
  1270.   PHTML reply("Directory of " + request.url.AsString());
  1271.   PDirectory dir = realPath;
  1272.   if (dir.Open()) {
  1273.     do {
  1274.       const char * imgName;
  1275.       if (dir.IsSubDir())
  1276.         imgName = "internal-gopher-menu";
  1277.       else if (PMIMEInfo::GetContentType(
  1278.                     PFilePath(dir.GetEntryName()).GetType())(0,4) == "text/")
  1279.         imgName = "internal-gopher-text";
  1280.       else
  1281.         imgName = "internal-gopher-unknown";
  1282.       reply << PHTML::Image(imgName) << ' '
  1283.             << PHTML::HotLink(realPath.GetFileName()+'/'+dir.GetEntryName())
  1284.             << dir.GetEntryName()
  1285.             << PHTML::HotLink()
  1286.             << PHTML::BreakLine();
  1287.     } while (dir.Next());
  1288.   }
  1289.   reply << PHTML::Body();
  1290.   fakeIndex = reply;
  1291.   return TRUE;
  1292. }
  1293. PString PHTTPDirectory::LoadText(PHTTPRequest & request)
  1294. {
  1295.   PString & fakeIndex = ((PHTTPDirRequest&)request).fakeIndex;
  1296.   if (fakeIndex.IsEmpty())
  1297.     return PHTTPFile::LoadText(request);
  1298.   return fakeIndex;
  1299. }
  1300. // End Of File ///////////////////////////////////////////////////////////////