Added IsSupportingRTP function to simplify detecting when STUN supports RTP
[pwlib.git] / src / ptclib / httpsrvr.cxx
blob1ab6124b83e08fa96591076f603e84eb5b8f07b5
1 /*
2 * httpsrvr.cxx
4 * HTTP server classes.
6 * Portable Windows Library
8 * Copyright (c) 1993-2002 Equivalence Pty. Ltd.
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/
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.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
26 * $Log$
27 * Revision 1.47 2004/02/03 09:37:20 rjongbloed
28 * Added check to text files via the type extension, thanks David Parr
30 * Revision 1.46 2003/03/19 01:55:26 robertj
31 * Fixed bugs in deleteing HTTP resources from server, thanks Diego Tártara
33 * Revision 1.45 2002/11/06 22:47:25 robertj
34 * Fixed header comment (copyright etc)
36 * Revision 1.44 2002/10/10 04:43:44 robertj
37 * VxWorks port, thanks Martijn Roest
39 * Revision 1.43 2002/10/02 08:54:01 craigs
40 * Added support for XMLRPC server
42 * Revision 1.42 2002/08/27 23:49:08 robertj
43 * Fixed security hole where possible to get any file on disk when using
44 * PHTTPDirectory HTTP resource.
46 * Revision 1.41 2002/07/17 08:43:52 robertj
47 * Fixed closing of html msg on generated post output.
49 * Revision 1.40 2002/05/08 05:38:54 robertj
50 * Added PHTTPTailFile resource to do a unix 'tail -f' of a file.
52 * Revision 1.39 2002/04/12 08:15:23 robertj
53 * Fixed warning on older GNU compilers, also guarantees numeric output.
55 * Revision 1.38 2001/10/31 01:37:13 robertj
56 * Fixed deleting of object added to http name space if add fails.
57 * Changes to support HTTP v1.1 chunked transfer encoding.
59 * Revision 1.37 2001/09/28 00:45:27 robertj
60 * Removed HasKey() as is confusing due to ancestor Contains().
62 * Revision 1.36 2001/06/01 07:28:23 craigs
63 * Added handling for binary data in multi-part MIME fields
65 * Revision 1.35 2001/03/14 01:49:54 craigs
66 * Added ability to handle multi-part form POST commands
68 * Revision 1.34 2001/01/15 06:17:56 robertj
69 * Set HTTP resource members to private to assure are not modified by
70 * dscendents in non-threadsafe manner.
72 * Revision 1.33 2000/09/04 03:57:58 robertj
73 * Added ability to change the persistent connection parameters (timeout etc).
75 * Revision 1.32 2000/05/02 07:55:22 craigs
76 * Changed static PString to static const char * to avoid "memory leak"
78 * Revision 1.31 1999/05/13 04:04:04 robertj
79 * Fixed problem of initialised commandName in ConnectionInfo.
81 * Revision 1.30 1999/05/12 01:40:47 robertj
82 * Fixed "unknown" response codes being passed on when used with an "unknown" command.
84 * Revision 1.29 1999/05/11 12:23:22 robertj
85 * Fixed search for persistent connection to accept kee-alive on multile MIME fields.
87 * Revision 1.28 1999/05/04 15:26:01 robertj
88 * Improved HTTP/1.1 compatibility (pass through user commands).
89 * Fixed problems with quicktime installer.
91 * Revision 1.27 1999/04/24 11:50:11 robertj
92 * Changed HTTP command parser so will work if some idiot puts spaces in a URL.
94 * Revision 1.26 1999/04/21 01:58:08 robertj
95 * Fixed problem with reading data for request using second form of PHTTPRequestInfo constructor.
97 * Revision 1.25 1998/11/30 04:51:59 robertj
98 * New directory structure
100 * Revision 1.24 1998/11/14 01:11:38 robertj
101 * PPC linux GNU compatibility.
103 * Revision 1.23 1998/10/31 12:49:23 robertj
104 * Added read/write mutex to the HTTP space variable to avoid thread crashes.
106 * Revision 1.22 1998/10/25 01:02:41 craigs
107 * Added ability to specify per-directory authorisation for PHTTPDirectory
109 * Revision 1.21 1998/10/13 14:06:23 robertj
110 * Complete rewrite of memory leak detection code.
112 * Revision 1.20 1998/09/23 06:22:13 robertj
113 * Added open source copyright license.
115 * Revision 1.19 1998/08/06 00:54:22 robertj
116 * Fixed bug in sending empty files, caused endless wait in Netscape.
118 * Revision 1.18 1998/06/16 03:32:14 robertj
119 * Propagated persistence and proxy flags in new connection info instances.
121 * Revision 1.17 1998/04/01 01:55:16 robertj
122 * Fixed bug when serving HTTPFile that has zero bytes in it.
124 * Revision 1.16 1998/02/03 06:24:10 robertj
125 * Added local address and port to PHTTPRequest.
126 * Fixed bug in default entity length. should be read to EOF.
127 * Fixed OnError() so can detec HTML bosy tag with parameters.
129 * Revision 1.14 1998/01/26 00:42:19 robertj
130 * Added more information to PHTTPConnectionInfo.
131 * Made SetDefaultMIMEFields in HTTP Server not set those fields if already set.
133 * Revision 1.13 1997/10/30 10:22:04 robertj
134 * Added multiple user basic authorisation scheme.
136 * Revision 1.12 1997/10/03 13:39:25 robertj
137 * Fixed race condition on socket close in Select() function.
139 * Revision 1.12 1997/10/03 13:31:12 craigs
140 * Added ability to access client socket from within HTTP resources
142 * Revision 1.11 1997/08/04 10:44:36 robertj
143 * Improved receiving of a POST on a non-persistant connection, do not wait for EOF if have CRLF.
145 * Revision 1.10 1997/07/14 11:47:13 robertj
146 * Added "const" to numerous variables.
148 * Revision 1.9 1997/07/08 13:10:26 robertj
149 * Fixed bug in HTTP server where standard error text is not sent to remote client.
151 * Revision 1.8 1997/04/15 14:32:19 robertj
152 * Fixed case problem for HTTP version string.
154 * Revision 1.7 1997/03/20 13:01:32 robertj
155 * Fixed bug in proxy POST having unexpexted reset of connection.
157 * Revision 1.6 1997/02/09 04:09:30 robertj
158 * Fixed GCC warning
160 * Revision 1.5 1997/01/12 04:15:23 robertj
161 * Globalised MIME tag strings.
163 * Revision 1.4 1996/12/12 09:24:16 robertj
164 * Persistent proxy connection support (work in progress).
166 * Revision 1.3 1996/11/10 21:09:33 robertj
167 * Removed redundent GetSocket() call.
168 * Added flush of stream after processing request, important on persistent connections.
170 * Revision 1.2 1996/10/26 03:31:05 robertj
171 * Changed OnError so can pass in full HTML page as parameter.
173 * Revision 1.1 1996/09/14 13:02:18 robertj
174 * Initial revision
178 #include <ptlib.h>
179 #include <ptlib/sockets.h>
180 #include <ptclib/http.h>
181 #include <ctype.h>
183 #define new PNEW
186 // define to enable work-around for Netscape persistant connection bug
187 // set to lifetime of suspect sockets (in seconds)
188 #define STRANGE_NETSCAPE_BUG 3
190 // maximum delay between characters whilst reading a line of text
191 #define READLINE_TIMEOUT 30
193 #define DEFAULT_PERSIST_TIMEOUT 30
194 #define DEFAULT_PERSIST_TRANSATIONS 10
196 // filename to use for directory access directives
197 static const char * accessFilename = "_access";
200 //////////////////////////////////////////////////////////////////////////////
201 // PHTTPSpace
203 PHTTPSpace::PHTTPSpace()
205 mutex = new PReadWriteMutex;
206 root = new Node(PString(), NULL);
210 void PHTTPSpace::DestroyContents()
212 delete mutex;
213 delete root;
217 void PHTTPSpace::CloneContents(const PHTTPSpace * c)
219 mutex = new PReadWriteMutex;
220 root = new Node(*c->root);
224 void PHTTPSpace::CopyContents(const PHTTPSpace & c)
226 mutex = c.mutex;
227 root = c.root;
231 PHTTPSpace::Node::Node(const PString & nam, Node * parentNode)
232 : PString(nam)
234 parent = parentNode;
235 resource = NULL;
239 PHTTPSpace::Node::~Node()
241 delete resource;
245 BOOL PHTTPSpace::AddResource(PHTTPResource * res, AddOptions overwrite)
247 PAssert(res != NULL, PInvalidParameter);
248 const PStringArray & path = res->GetURL().GetPath();
249 Node * node = root;
250 for (PINDEX i = 0; i < path.GetSize(); i++) {
251 if (path[i].IsEmpty())
252 break;
254 if (node->resource != NULL) {
255 delete res;
256 return FALSE; // Already a resource in tree in partial path
259 PINDEX pos = node->children.GetValuesIndex(path[i]);
260 if (pos == P_MAX_INDEX)
261 pos = node->children.Append(new Node(path[i], node));
263 node = &node->children[pos];
266 if (!node->children.IsEmpty()) {
267 delete res;
268 return FALSE; // Already a resource in tree further down path.
271 if (overwrite == ErrorOnExist && node->resource != NULL) {
272 delete res;
273 return FALSE; // Already a resource in tree at leaf
276 delete node->resource;
277 node->resource = res;
279 return TRUE;
283 BOOL PHTTPSpace::DelResource(const PURL & url)
285 const PStringArray & path = url.GetPath();
286 Node * node = root;
287 for (PINDEX i = 0; i < path.GetSize(); i++) {
288 if (path[i].IsEmpty())
289 break;
291 PINDEX pos = node->children.GetValuesIndex(path[i]);
292 if (pos == P_MAX_INDEX)
293 return FALSE;
295 node = &node->children[pos];
297 // If have resource and not last node, then trying to remove something
298 // further down the tree than a leaf node.
299 if (node->resource != NULL && i < (path.GetSize()-1))
300 return FALSE;
303 if (!node->children.IsEmpty())
304 return FALSE; // Still a resource in tree further down path.
306 if (node->parent != NULL) {
307 do {
308 Node * par = node->parent;
309 par->children.Remove(node);
310 node = par;
311 } while (node != NULL && node->children.IsEmpty());
314 return TRUE;
318 static const char * const HTMLIndexFiles[] = {
319 "Welcome.html", "welcome.html", "index.html",
320 "Welcome.htm", "welcome.htm", "index.htm"
323 PHTTPResource * PHTTPSpace::FindResource(const PURL & url)
325 const PStringArray & path = url.GetPath();
327 Node * node = root;
328 PINDEX i;
329 for (i = 0; i < path.GetSize(); i++) {
330 if (path[i].IsEmpty())
331 break;
333 PINDEX pos = node->children.GetValuesIndex(path[i]);
334 if (pos == P_MAX_INDEX)
335 return NULL;
337 node = &node->children[pos];
339 if (node->resource != NULL)
340 return node->resource;
343 for (i = 0; i < PARRAYSIZE(HTMLIndexFiles); i++) {
344 PINDEX pos = node->children.GetValuesIndex(PString(HTMLIndexFiles[i]));
345 if (pos != P_MAX_INDEX)
346 return node->children[pos].resource;
349 return NULL;
353 //////////////////////////////////////////////////////////////////////////////
354 // PHTTPServer
356 PHTTPServer::PHTTPServer()
358 Construct();
362 PHTTPServer::PHTTPServer(const PHTTPSpace & space)
363 : urlSpace(space)
365 Construct();
369 void PHTTPServer::Construct()
371 transactionCount = 0;
372 SetReadLineTimeout(PTimeInterval(0, READLINE_TIMEOUT));
375 void PHTTPConnectionInfo::DecodeMultipartFormInfo(const PString & type, const PString & entityBody)
377 // remove trailing ","
378 PINDEX pos = type.Find(",");
379 if (pos == P_MAX_INDEX) {
380 pos = type.Find(";");
381 if (pos == P_MAX_INDEX)
382 return;
384 PString seperator = type.Mid(pos+1).Trim();
386 // remove "boundary"
387 pos = seperator.Find("boundary");
388 if (pos == P_MAX_INDEX)
389 return;
390 seperator = seperator.Mid(8).Trim();
392 // remove "="
393 pos = seperator.Find("=");
394 if (pos == P_MAX_INDEX)
395 return;
396 seperator = seperator.Mid(1).Trim();
398 // seperators have a "--" according to RFC 1521
399 seperator = PString("--") + seperator;
401 PINDEX sepLen = seperator.GetLength();
402 const char * sep = (const char *)seperator;
404 // split body into parts, assuming binary data
405 const char * body = (const char *)entityBody;
406 PINDEX entityOffs = 0;
407 PINDEX entityLen = entityBody.GetSize()-1;
409 BOOL ignore = TRUE;
410 BOOL last = FALSE;
412 PMultipartFormInfo * info = NULL;
414 while (!last && (entityOffs < entityLen)) {
416 // find end of part
417 PINDEX partStart = entityOffs;
418 PINDEX partLen;
419 BOOL foundSep = FALSE;
421 // collect length of part until seperator
422 for (partLen = 0; (partStart + partLen) < entityLen; partLen++) {
423 if ((partLen >= sepLen) && (memcmp(body + partStart + partLen - sepLen, sep, sepLen) == 0)) {
424 foundSep = TRUE;
425 break;
429 // move entity ptr to the end of the part
430 entityOffs = partStart + partLen;
432 // if no seperator found, then this is the last part
433 // otherwise, look for "--" trailer on seperator and remove CRLF
434 if (!foundSep)
435 last = TRUE;
436 else {
437 partLen -= sepLen;
439 // determine if this is the last block
440 if (((entityOffs + 2) <= entityLen) && (body[entityOffs] == '-') && (body[entityOffs+1] == '-')) {
441 last = TRUE;
442 entityOffs += 2;
445 // remove crlf
446 if (((entityOffs + 2) <= entityLen) && (body[entityOffs] == '\r') && (body[entityOffs+1] == '\n'))
447 entityOffs += 2;
450 // ignore everything up to the first seperator,
451 // then adjust seperator to include leading CRLF
452 if (ignore) {
453 ignore = FALSE;
454 seperator = PString("\r\n") + seperator;
455 sepLen = seperator.GetLength();
456 sep = (const char *)seperator;
457 continue;
460 // extract the MIME header, by looking for a double CRLF
461 PINDEX ptr;
462 PINDEX nlCount = 0;
463 for (ptr = partStart;(ptr < (partStart + partLen)) && (nlCount < 2); ptr++) {
464 if (body[ptr] == '\r') {
465 nlCount++;
466 if ((ptr < entityLen-1) && (body[ptr+1] == '\n'))
467 ptr++;
468 } else
469 nlCount = 0;
472 // create the new part info
473 info = new PMultipartFormInfo;
475 // read MIME information
476 PStringStream strm(PString(body + partStart, ptr - partStart));
477 info->mime.ReadFrom(strm);
479 // save the entity body, being careful of binary files
480 int savedLen = partStart + partLen - ptr;
481 char * saved = info->body.GetPointer(savedLen + 1);
482 memcpy(saved, body + ptr, savedLen);
483 saved[savedLen] = '\0';
485 // add the data to the array
486 multipartFormInfoArray.Append(info);
487 info = NULL;
490 #if 0
491 // ignore until first separator
492 do {
493 data >> line;
494 if (line.IsEmpty())
495 return;
496 } while (line.Find(sep) != 0);
498 PMultipartFormInfo * info = NULL;
500 // read form parts
501 while (data.good() && (line.Right(2) != "--")) {
503 info = new PMultipartFormInfo;
505 // read MIME information
506 info->mime.ReadFrom(data);
508 // get the content type
509 PString type = info->mime(PHTTP::ContentTypeTag);
511 // check the encoding
512 PString encoding = info->mime("Content-Transfer-Encoding");
514 // accumulate text until another seperator or end of data
515 PString & buf = info->body;
516 PINDEX len = 0;
517 buf.SetSize(len+1);
518 buf[0] = '\0';
519 PINDEX sepLen = sep.GetLength();
520 const char * sepPtr = (const char *)sep;
521 while (data.good()) {
522 buf.SetSize(len);
523 data >> buf[len++];
524 if ((len >= sepLen) && (memcmp(((const char *)buf) + len - sepLen, sepPtr, sepLen) == 0)) {
525 char ch;
526 data >> ch;
527 if (ch != 0x0d)
528 data.putback(ch);
529 else {
530 data >> ch;
531 if (ch != 0x0a)
532 data.putback(ch);
534 len -= sepLen;
535 break;
538 buf.SetSize(len+1);
539 buf[len] = '\0';
542 while (data.good()) {
543 data >> line;
544 if (line.Find(sep) == 0)
545 break;
546 info->body += line + "\n";
550 multipartFormInfoArray.Append(info);
551 info = NULL;
553 #endif
556 BOOL PHTTPServer::ProcessCommand()
558 PString args;
559 PINDEX cmd;
561 // if this is not the first command received by this socket, then set
562 // the read timeout appropriately.
563 if (transactionCount > 0)
564 SetReadTimeout(nextTimeout);
566 // this will only return false upon timeout or completely invalid command
567 if (!ReadCommand(cmd, args))
568 return FALSE;
570 connectInfo.commandCode = (Commands)cmd;
571 if (cmd < NumCommands)
572 connectInfo.commandName = commandNames[cmd];
573 else {
574 PINDEX spacePos = args.Find(' ');
575 connectInfo.commandName = args.Left(spacePos);
576 args = args.Mid(spacePos);
579 // if no tokens, error
580 if (args.IsEmpty()) {
581 OnError(BadRequest, args, connectInfo);
582 return FALSE;
585 if (!connectInfo.Initialise(*this, args))
586 return FALSE;
588 // now that we've decided we did receive a HTTP request, increment the
589 // count of transactions
590 transactionCount++;
591 nextTimeout = connectInfo.GetPersistenceTimeout();
593 PIPSocket * socket = GetSocket();
594 WORD myPort = (WORD)(socket != NULL ? socket->GetPort() : 80);
596 // the URL that comes with Connect requests is not quite kosher, so
597 // mangle it into a proper URL and do NOT close the connection.
598 // for all other commands, close the read connection if not persistant
599 if (cmd == CONNECT)
600 connectInfo.url = "https://" + args;
601 else {
602 connectInfo.url = args;
603 if (connectInfo.url.GetPort() == 0)
604 connectInfo.url.SetPort(myPort);
607 BOOL persist;
609 // make sure the form info is reset for each new operation
610 connectInfo.ResetMultipartFormInfo();
612 // If the incoming URL is of a proxy type then call OnProxy() which will
613 // probably just go OnError(). Even if a full URL is provided in the
614 // command we should check to see if it is a local server request and process
615 // it anyway even though we are not a proxy. The usage of GetHostName()
616 // below are to catch every way of specifying the host (name, alias, any of
617 // several IP numbers etc).
618 const PURL & url = connectInfo.GetURL();
619 if (url.GetScheme() != "http" ||
620 (url.GetPort() != 0 && url.GetPort() != myPort) ||
621 (!url.GetHostName() && !PIPSocket::IsLocalHost(url.GetHostName())))
622 persist = OnProxy(connectInfo);
623 else {
624 connectInfo.entityBody = ReadEntityBody();
626 // Handle the local request
627 PStringToString postData;
628 switch (cmd) {
629 case GET :
630 persist = OnGET(url, connectInfo.GetMIME(), connectInfo);
631 break;
633 case HEAD :
634 persist = OnHEAD(url, connectInfo.GetMIME(), connectInfo);
635 break;
637 case POST :
639 // check for multi-part form POSTs
640 PString postType = (connectInfo.GetMIME())(ContentTypeTag);
641 if (postType.Find("multipart/form-data") == 0)
642 connectInfo.DecodeMultipartFormInfo(postType, connectInfo.entityBody);
643 else // if (postType *= "x-www-form-urlencoded)
644 PURL::SplitQueryVars(connectInfo.entityBody, postData);
646 persist = OnPOST(url, connectInfo.GetMIME(), postData, connectInfo);
647 break;
649 case P_MAX_INDEX:
650 default:
651 persist = OnUnknown(args, connectInfo);
655 flush();
657 // if the function just indicated that the connection is to persist,
658 // and so did the client, then return TRUE. Note that all of the OnXXXX
659 // routines above must make sure that their return value is FALSE if
660 // if there was no ContentLength field in the response. This ensures that
661 // we always close the socket so the client will get the correct end of file
662 if (persist && connectInfo.IsPersistant()) {
663 unsigned max = connectInfo.GetPersistenceMaximumTransations();
664 if (max == 0 || transactionCount < max)
665 return TRUE;
668 PTRACE(5, "HTTPServer\tConnection end: " << connectInfo.IsPersistant());
670 // close the output stream now and return FALSE
671 Shutdown(ShutdownWrite);
672 return FALSE;
676 PString PHTTPServer::ReadEntityBody()
678 if (connectInfo.GetMajorVersion() < 1)
679 return PString();
681 PString entityBody;
682 long contentLength = connectInfo.GetEntityBodyLength();
683 // a content length of > 0 means read explicit length
684 // a content length of < 0 means read until EOF
685 // a content length of 0 means read nothing
686 int count = 0;
687 if (contentLength > 0) {
688 entityBody = ReadString((PINDEX)contentLength);
689 } else if (contentLength == -2) {
690 ReadLine(entityBody, FALSE);
691 } else if (contentLength < 0) {
692 while (Read(entityBody.GetPointer(count+1000)+count, 1000))
693 count += GetLastReadCount();
694 entityBody.SetSize(count+1);
697 // close the connection, if not persistant
698 if (!connectInfo.IsPersistant()) {
699 PIPSocket * socket = GetSocket();
700 if (socket != NULL)
701 socket->Shutdown(PIPSocket::ShutdownRead);
704 return entityBody;
708 PString PHTTPServer::GetServerName() const
710 return "PWLib-HTTP-Server/1.0 PWLib/1.0";
714 void PHTTPServer::SetURLSpace(const PHTTPSpace & space)
716 urlSpace = space;
720 BOOL PHTTPServer::OnGET(const PURL & url,
721 const PMIMEInfo & info,
722 const PHTTPConnectionInfo & connectInfo)
724 urlSpace.StartRead();
725 PHTTPResource * resource = urlSpace.FindResource(url);
726 if (resource == NULL) {
727 urlSpace.EndRead();
728 return OnError(NotFound, url.AsString(), connectInfo);
731 BOOL retval = resource->OnGET(*this, url, info, connectInfo);
732 urlSpace.EndRead();
733 return retval;
737 BOOL PHTTPServer::OnHEAD(const PURL & url,
738 const PMIMEInfo & info,
739 const PHTTPConnectionInfo & connectInfo)
741 urlSpace.StartRead();
742 PHTTPResource * resource = urlSpace.FindResource(url);
743 if (resource == NULL) {
744 urlSpace.EndRead();
745 return OnError(NotFound, url.AsString(), connectInfo);
748 BOOL retval = resource->OnHEAD(*this, url, info, connectInfo);
749 urlSpace.EndRead();
750 return retval;
754 BOOL PHTTPServer::OnPOST(const PURL & url,
755 const PMIMEInfo & info,
756 const PStringToString & data,
757 const PHTTPConnectionInfo & connectInfo)
759 urlSpace.StartRead();
760 PHTTPResource * resource = urlSpace.FindResource(url);
761 if (resource == NULL) {
762 urlSpace.EndRead();
763 return OnError(NotFound, url.AsString(), connectInfo);
766 BOOL retval = resource->OnPOST(*this, url, info, data, connectInfo);
767 urlSpace.EndRead();
768 return retval;
772 BOOL PHTTPServer::OnProxy(const PHTTPConnectionInfo & connectInfo)
774 return OnError(BadGateway, "Proxy not implemented.", connectInfo) &&
775 connectInfo.GetCommandCode() != CONNECT;
779 struct httpStatusCodeStruct {
780 const char * text;
781 int code;
782 BOOL allowedBody;
783 int majorVersion;
784 int minorVersion;
787 static const httpStatusCodeStruct * GetStatusCodeStruct(int code)
789 static const httpStatusCodeStruct httpStatusDefn[] = {
790 // First entry MUST be InternalServerError
791 { "Internal Server Error", PHTTP::InternalServerError, 1 },
792 { "OK", PHTTP::RequestOK, 1 },
793 { "Unauthorised", PHTTP::UnAuthorised, 1 },
794 { "Forbidden", PHTTP::Forbidden, 1 },
795 { "Not Found", PHTTP::NotFound, 1 },
796 { "Not Modified", PHTTP::NotModified },
797 { "No Content", PHTTP::NoContent },
798 { "Bad Gateway", PHTTP::BadGateway, 1 },
799 { "Bad Request", PHTTP::BadRequest, 1 },
800 { "Continue", PHTTP::Continue, 1, 1, 1 },
801 { "Switching Protocols", PHTTP::SwitchingProtocols, 1, 1, 1 },
802 { "Created", PHTTP::Created, 1 },
803 { "Accepted", PHTTP::Accepted, 1 },
804 { "Non-Authoritative Information", PHTTP::NonAuthoritativeInformation, 1, 1, 1 },
805 { "Reset Content", PHTTP::ResetContent, 0, 1, 1 },
806 { "Partial Content", PHTTP::PartialContent, 1, 1, 1 },
807 { "Multiple Choices", PHTTP::MultipleChoices, 1, 1, 1 },
808 { "Moved Permanently", PHTTP::MovedPermanently, 1 },
809 { "Moved Temporarily", PHTTP::MovedTemporarily, 1 },
810 { "See Other", PHTTP::SeeOther, 1, 1, 1 },
811 { "Use Proxy", PHTTP::UseProxy, 1, 1, 1 },
812 { "Payment Required", PHTTP::PaymentRequired, 1, 1, 1 },
813 { "Method Not Allowed", PHTTP::MethodNotAllowed, 1, 1, 1 },
814 { "None Acceptable", PHTTP::NoneAcceptable, 1, 1, 1 },
815 { "Proxy Authetication Required", PHTTP::ProxyAuthenticationRequired, 1, 1, 1 },
816 { "Request Timeout", PHTTP::RequestTimeout, 1, 1, 1 },
817 { "Conflict", PHTTP::Conflict, 1, 1, 1 },
818 { "Gone", PHTTP::Gone, 1, 1, 1 },
819 { "Length Required", PHTTP::LengthRequired, 1, 1, 1 },
820 { "Unless True", PHTTP::UnlessTrue, 1, 1, 1 },
821 { "Not Implemented", PHTTP::NotImplemented, 1 },
822 { "Service Unavailable", PHTTP::ServiceUnavailable, 1, 1, 1 },
823 { "Gateway Timeout", PHTTP::GatewayTimeout, 1, 1, 1 }
826 // make sure the error code is valid
827 for (PINDEX i = 0; i < PARRAYSIZE(httpStatusDefn); i++)
828 if (code == httpStatusDefn[i].code)
829 return &httpStatusDefn[i];
831 return httpStatusDefn;
835 BOOL PHTTPServer::StartResponse(StatusCode code,
836 PMIMEInfo & headers,
837 long bodySize)
839 if (connectInfo.majorVersion < 1)
840 return FALSE;
842 httpStatusCodeStruct dummyInfo;
843 const httpStatusCodeStruct * statusInfo;
844 if (connectInfo.commandCode < NumCommands)
845 statusInfo = GetStatusCodeStruct(code);
846 else {
847 dummyInfo.text = "";
848 dummyInfo.code = code;
849 dummyInfo.allowedBody = TRUE;
850 dummyInfo.majorVersion = connectInfo.majorVersion;
851 dummyInfo.minorVersion = connectInfo.minorVersion;
852 statusInfo = &dummyInfo;
855 // output the command line
856 *this << "HTTP/" << connectInfo.majorVersion << '.' << connectInfo.minorVersion
857 << ' ' << statusInfo->code << ' ' << statusInfo->text << "\r\n";
859 BOOL chunked = FALSE;
861 // If do not have user set content length, decide if we should add one
862 if (!headers.Contains(ContentLengthTag)) {
863 if (connectInfo.minorVersion < 1) {
864 // v1.0 client, don't put in ContentLength if the bodySize is zero because
865 // that can be confused by some browsers as meaning there is no body length.
866 if (bodySize > 0)
867 headers.SetAt(ContentLengthTag, bodySize);
869 else {
870 // v1.1 or later, see if will use chunked output
871 chunked = bodySize == P_MAX_INDEX;
872 if (chunked)
873 headers.SetAt(TransferEncodingTag, ChunkedTag);
874 else if (bodySize >= 0 && bodySize < P_MAX_INDEX)
875 headers.SetAt(ContentLengthTag, bodySize);
879 *this << setfill('\r') << headers;
881 #ifdef STRANGE_NETSCAPE_BUG
882 // The following is a work around for a strange bug in Netscape where it
883 // locks up when a persistent connection is made and data less than 1k
884 // (including MIME headers) is sent. Go figure....
885 if (bodySize < 1024 && connectInfo.GetMIME()(UserAgentTag).Find("Mozilla/2.0") != P_MAX_INDEX)
886 nextTimeout.SetInterval(STRANGE_NETSCAPE_BUG*1000);
887 #endif
889 return chunked;
893 void PHTTPServer::SetDefaultMIMEInfo(PMIMEInfo & info,
894 const PHTTPConnectionInfo & connectInfo)
896 PTime now;
897 if (!info.Contains(DateTag))
898 info.SetAt(DateTag, now.AsString(PTime::RFC1123, PTime::GMT));
899 if (!info.Contains(MIMEVersionTag))
900 info.SetAt(MIMEVersionTag, "1.0");
901 if (!info.Contains(ServerTag))
902 info.SetAt(ServerTag, GetServerName());
904 if (connectInfo.IsPersistant()) {
905 if (connectInfo.IsProxyConnection()) {
906 PTRACE(5, "HTTPServer\tSetting proxy persistant response");
907 info.SetAt(ProxyConnectionTag, KeepAliveTag);
909 else {
910 PTRACE(5, "HTTPServer\tSetting direct persistant response");
911 info.SetAt(ConnectionTag, KeepAliveTag);
918 BOOL PHTTPServer::OnUnknown(const PCaselessString & cmd,
919 const PHTTPConnectionInfo & connectInfo)
921 return OnError(NotImplemented, cmd, connectInfo);
925 BOOL PHTTPServer::OnError(StatusCode code,
926 const PCaselessString & extra,
927 const PHTTPConnectionInfo & connectInfo)
929 const httpStatusCodeStruct * statusInfo = GetStatusCodeStruct(code);
931 if (!connectInfo.IsCompatible(statusInfo->majorVersion, statusInfo->minorVersion))
932 statusInfo = GetStatusCodeStruct((code/100)*100);
934 PMIMEInfo headers;
935 SetDefaultMIMEInfo(headers, connectInfo);
937 if (!statusInfo->allowedBody) {
938 StartResponse(code, headers, 0);
939 return statusInfo->code == RequestOK;
942 PString reply;
943 if (extra.Find("<body") != P_MAX_INDEX)
944 reply = extra;
945 else {
946 PHTML html;
947 html << PHTML::Title()
948 << statusInfo->code
949 << ' '
950 << statusInfo->text
951 << PHTML::Body()
952 << PHTML::Heading(1)
953 << statusInfo->code
954 << ' '
955 << statusInfo->text
956 << PHTML::Heading(1)
957 << extra
958 << PHTML::Body();
959 reply = html;
962 headers.SetAt(ContentTypeTag, "text/html");
963 StartResponse(code, headers, reply.GetLength());
964 WriteString(reply);
965 return statusInfo->code == RequestOK;
969 //////////////////////////////////////////////////////////////////////////////
970 // PHTTPSimpleAuth
972 void PHTTPAuthority::DecodeBasicAuthority(const PString & authInfo,
973 PString & username,
974 PString & password)
976 PString decoded;
977 if (authInfo(0, 5) *= "Basic ")
978 decoded = PBase64::Decode(authInfo(6, P_MAX_INDEX));
979 else
980 decoded = PBase64::Decode(authInfo);
982 PINDEX colonPos = decoded.Find(':');
983 if (colonPos == P_MAX_INDEX) {
984 username = decoded;
985 password = PString();
987 else {
988 username = decoded.Left(colonPos).Trim();
989 password = decoded.Mid(colonPos+1).Trim();
994 BOOL PHTTPAuthority::IsActive() const
996 return TRUE;
1000 //////////////////////////////////////////////////////////////////////////////
1001 // PHTTPSimpleAuth
1003 PHTTPSimpleAuth::PHTTPSimpleAuth(const PString & realm_,
1004 const PString & username_,
1005 const PString & password_)
1006 : realm(realm_), username(username_), password(password_)
1008 PAssert(!realm, "Must have a realm!");
1012 PObject * PHTTPSimpleAuth::Clone() const
1014 return new PHTTPSimpleAuth(realm, username, password);
1018 BOOL PHTTPSimpleAuth::IsActive() const
1020 return !username || !password;
1024 PString PHTTPSimpleAuth::GetRealm(const PHTTPRequest &) const
1026 return realm;
1030 BOOL PHTTPSimpleAuth::Validate(const PHTTPRequest &,
1031 const PString & authInfo) const
1033 PString user, pass;
1034 DecodeBasicAuthority(authInfo, user, pass);
1035 return username == user && password == pass;
1039 //////////////////////////////////////////////////////////////////////////////
1040 // PHTTPMultiSimpAuth
1042 PHTTPMultiSimpAuth::PHTTPMultiSimpAuth(const PString & realm_)
1043 : realm(realm_)
1045 PAssert(!realm, "Must have a realm!");
1049 PHTTPMultiSimpAuth::PHTTPMultiSimpAuth(const PString & realm_,
1050 const PStringToString & users_)
1051 : realm(realm_), users(users_)
1053 PAssert(!realm, "Must have a realm!");
1057 PObject * PHTTPMultiSimpAuth::Clone() const
1059 return new PHTTPMultiSimpAuth(realm, users);
1063 BOOL PHTTPMultiSimpAuth::IsActive() const
1065 return !users.IsEmpty();
1069 PString PHTTPMultiSimpAuth::GetRealm(const PHTTPRequest &) const
1071 return realm;
1075 BOOL PHTTPMultiSimpAuth::Validate(const PHTTPRequest &,
1076 const PString & authInfo) const
1078 PString user, pass;
1079 DecodeBasicAuthority(authInfo, user, pass);
1080 return users.Contains(user) && users[user] == pass;
1084 void PHTTPMultiSimpAuth::AddUser(const PString & username, const PString & password)
1086 users.SetAt(username, password);
1090 //////////////////////////////////////////////////////////////////////////////
1091 // PHTTPRequest
1093 PHTTPRequest::PHTTPRequest(const PURL & _url,
1094 const PMIMEInfo & _mime,
1095 const PMultipartFormInfoArray & _multipartFormInfo,
1096 PHTTPServer & _server)
1097 : server(_server),
1098 url(_url),
1099 inMIME(_mime),
1100 multipartFormInfo(_multipartFormInfo),
1101 origin(0),
1102 localAddr(0),
1103 localPort(0)
1105 code = PHTTP::RequestOK;
1106 contentSize = P_MAX_INDEX;
1108 PIPSocket * socket = server.GetSocket();
1109 if (socket != NULL) {
1110 socket->GetPeerAddress(origin);
1111 socket->GetLocalAddress(localAddr, localPort);
1116 //////////////////////////////////////////////////////////////////////////////
1117 // PHTTPConnectionInfo
1119 PHTTPConnectionInfo::PHTTPConnectionInfo()
1120 : persistenceTimeout(0, DEFAULT_PERSIST_TIMEOUT) // maximum lifetime (in seconds) of persistant connections
1122 // maximum lifetime (in transactions) of persistant connections
1123 persistenceMaximum = DEFAULT_PERSIST_TRANSATIONS;
1125 commandCode = PHTTP::NumCommands;
1127 majorVersion = 0;
1128 minorVersion = 9;
1130 isPersistant = FALSE;
1131 wasPersistant = FALSE;
1132 isProxyConnection = FALSE;
1134 entityBodyLength = -1;
1136 multipartFormInfoArray.AllowDeleteObjects();
1140 BOOL PHTTPConnectionInfo::Initialise(PHTTPServer & server, PString & args)
1142 // if only one argument, then it must be a version 0.9 simple request
1143 PINDEX lastSpacePos = args.FindLast(' ');
1144 static const PCaselessString httpId = "HTTP/";
1145 if (lastSpacePos == P_MAX_INDEX || httpId != args(lastSpacePos+1, lastSpacePos+5)) {
1146 majorVersion = 0;
1147 minorVersion = 9;
1148 return TRUE;
1151 // otherwise, attempt to extract a version number
1152 PCaselessString verStr = args.Mid(lastSpacePos + 6);
1153 PINDEX dotPos = verStr.Find('.');
1154 if (dotPos == 0 || dotPos >= verStr.GetLength()) {
1155 server.OnError(PHTTP::BadRequest, "Malformed version number: " + verStr, *this);
1156 return FALSE;
1159 // should actually check if the text contains only digits, but the
1160 // chances of matching everything else and it not being a valid number
1161 // are pretty small, so don't bother
1162 majorVersion = (int)verStr.Left(dotPos).AsInteger();
1163 minorVersion = (int)verStr.Mid(dotPos+1).AsInteger();
1164 args.Delete(lastSpacePos, P_MAX_INDEX);
1166 // build our connection info reading MIME info until an empty line is
1167 // received, or EOF
1168 if (!mimeInfo.Read(server))
1169 return FALSE;
1171 wasPersistant = isPersistant;
1172 isPersistant = FALSE;
1174 PString str;
1176 // check for Proxy-Connection and Connection strings
1177 isProxyConnection = mimeInfo.Contains(PHTTP::ProxyConnectionTag);
1178 if (isProxyConnection)
1179 str = mimeInfo[PHTTP::ProxyConnectionTag];
1180 else if (mimeInfo.Contains(PHTTP::ConnectionTag))
1181 str = mimeInfo[PHTTP::ConnectionTag];
1183 // get any connection options
1184 if (!str) {
1185 PStringArray tokens = str.Tokenise(", \r\n", FALSE);
1186 for (PINDEX z = 0; !isPersistant && z < tokens.GetSize(); z++)
1187 isPersistant = isPersistant || (tokens[z] *= PHTTP::KeepAliveTag);
1190 // If the protocol is version 1.0 or greater, there is MIME info, and the
1191 // prescence of a an entity body is signalled by the inclusion of
1192 // Content-Length header. If the protocol is less than version 1.0, then
1193 // there is no entity body!
1195 // if the client specified a persistant connection, then use the
1196 // ContentLength field. If there is no content length field, then
1197 // assume a ContentLength of zero and close the connection.
1198 // The spec actually says to read until end of file in this case,
1199 // but Netscape hangs if this is done.
1200 // If the client didn't specify a persistant connection, then use the
1201 // ContentLength if there is one or read until end of file if there isn't
1202 if (!isPersistant)
1203 entityBodyLength = mimeInfo.GetInteger(PHTTP::ContentLengthTag,
1204 (commandCode == PHTTP::POST) ? -2 : 0);
1205 else {
1206 entityBodyLength = mimeInfo.GetInteger(PHTTP::ContentLengthTag, -1);
1207 if (entityBodyLength < 0) {
1208 PTRACE(5, "HTTPServer\tPersistant connection has no content length");
1209 entityBodyLength = 0;
1210 mimeInfo.SetAt(PHTTP::ContentLengthTag, "0");
1214 return TRUE;
1218 void PHTTPConnectionInfo::SetMIME(const PString & tag, const PString & value)
1220 mimeInfo.MakeUnique();
1221 mimeInfo.SetAt(tag, value);
1225 BOOL PHTTPConnectionInfo::IsCompatible(int major, int minor) const
1227 if (minor == 0 && major == 0)
1228 return TRUE;
1229 else
1230 return (majorVersion > major) ||
1231 ((majorVersion == major) && (minorVersion >= minor));
1235 //////////////////////////////////////////////////////////////////////////////
1236 // PHTTPResource
1238 PHTTPResource::PHTTPResource(const PURL & url)
1239 : baseURL(url)
1241 authority = NULL;
1242 hitCount = 0;
1246 PHTTPResource::PHTTPResource(const PURL & url, const PHTTPAuthority & auth)
1247 : baseURL(url)
1249 authority = (PHTTPAuthority *)auth.Clone();
1250 hitCount = 0;
1254 PHTTPResource::PHTTPResource(const PURL & url, const PString & type)
1255 : baseURL(url), contentType(type)
1257 authority = NULL;
1258 hitCount = 0;
1262 PHTTPResource::PHTTPResource(const PURL & url,
1263 const PString & type,
1264 const PHTTPAuthority & auth)
1265 : baseURL(url), contentType(type)
1267 authority = (PHTTPAuthority *)auth.Clone();
1268 hitCount = 0;
1272 PHTTPResource::~PHTTPResource()
1274 delete authority;
1278 BOOL PHTTPResource::OnGET(PHTTPServer & server,
1279 const PURL & url,
1280 const PMIMEInfo & info,
1281 const PHTTPConnectionInfo & connectInfo)
1283 return OnGETOrHEAD(server, url, info, connectInfo, TRUE);
1287 BOOL PHTTPResource::OnHEAD(PHTTPServer & server,
1288 const PURL & url,
1289 const PMIMEInfo & info,
1290 const PHTTPConnectionInfo & connectInfo)
1292 return OnGETOrHEAD(server, url, info, connectInfo, FALSE);
1296 BOOL PHTTPResource::OnGETOrHEAD(PHTTPServer & server,
1297 const PURL & url,
1298 const PMIMEInfo & info,
1299 const PHTTPConnectionInfo & connectInfo,
1300 BOOL isGET)
1302 // Nede to split songle if into 2 so the Tornado compiler won't end with
1303 // 'internal compiler error'
1304 if (isGET && info.Contains(PHTTP::IfModifiedSinceTag))
1305 if (!IsModifiedSince(PTime(info[PHTTP::IfModifiedSinceTag])))
1306 return server.OnError(PHTTP::NotModified, url.AsString(), connectInfo);
1308 PHTTPRequest * request = CreateRequest(url,
1309 info,
1310 connectInfo.GetMultipartFormInfo(),
1311 server);
1313 BOOL retVal = TRUE;
1314 if (CheckAuthority(server, *request, connectInfo)) {
1315 retVal = FALSE;
1316 server.SetDefaultMIMEInfo(request->outMIME, connectInfo);
1318 PTime expiryDate;
1319 if (GetExpirationDate(expiryDate))
1320 request->outMIME.SetAt(PHTTP::ExpiresTag,
1321 expiryDate.AsString(PTime::RFC1123, PTime::GMT));
1323 if (!LoadHeaders(*request))
1324 retVal = server.OnError(request->code, url.AsString(), connectInfo);
1325 else if (!isGET)
1326 retVal = request->outMIME.Contains(PHTTP::ContentLengthTag);
1327 else {
1328 hitCount++;
1329 retVal = OnGETData(server, url, connectInfo, *request);
1333 delete request;
1334 return retVal;
1338 BOOL PHTTPResource::OnGETData(PHTTPServer & /*server*/,
1339 const PURL & /*url*/,
1340 const PHTTPConnectionInfo & /*connectInfo*/,
1341 PHTTPRequest & request)
1343 SendData(request);
1344 return request.outMIME.Contains(PHTTP::ContentLengthTag) ||
1345 request.outMIME.Contains(PHTTP::TransferEncodingTag);
1349 BOOL PHTTPResource::OnPOST(PHTTPServer & server,
1350 const PURL & url,
1351 const PMIMEInfo & info,
1352 const PStringToString & data,
1353 const PHTTPConnectionInfo & connectInfo)
1355 PHTTPRequest * request = CreateRequest(url,
1356 info,
1357 connectInfo.GetMultipartFormInfo(),
1358 server);
1360 request->entityBody = connectInfo.GetEntityBody();
1362 BOOL persist = TRUE;
1363 if (CheckAuthority(server, *request, connectInfo)) {
1364 server.SetDefaultMIMEInfo(request->outMIME, connectInfo);
1365 persist = OnPOSTData(*request, data);
1366 if (request->code != PHTTP::RequestOK)
1367 persist = server.OnError(request->code, "", connectInfo) && persist;
1370 delete request;
1371 return persist;
1375 BOOL PHTTPResource::OnPOSTData(PHTTPRequest & request,
1376 const PStringToString & data)
1378 PHTML msg;
1379 BOOL persist = Post(request, data, msg);
1381 if (msg.Is(PHTML::InBody))
1382 msg << PHTML::Body();
1384 if (request.code != PHTTP::RequestOK)
1385 return persist;
1387 if (msg.IsEmpty())
1388 msg << PHTML::Title() << (unsigned)PHTTP::RequestOK << " OK" << PHTML::Body()
1389 << PHTML::Heading(1) << (unsigned)PHTTP::RequestOK << " OK" << PHTML::Heading(1)
1390 << PHTML::Body();
1392 request.outMIME.SetAt(PHTTP::ContentTypeTag, "text/html");
1394 PINDEX len = msg.GetLength();
1395 request.server.StartResponse(request.code, request.outMIME, len);
1396 return request.server.Write((const char *)msg, len) && persist;
1400 BOOL PHTTPResource::CheckAuthority(PHTTPServer & server,
1401 const PHTTPRequest & request,
1402 const PHTTPConnectionInfo & connectInfo)
1404 if (authority == NULL)
1405 return TRUE;
1407 return CheckAuthority(*authority, server, request, connectInfo);
1411 BOOL PHTTPResource::CheckAuthority(PHTTPAuthority & authority,
1412 PHTTPServer & server,
1413 const PHTTPRequest & request,
1414 const PHTTPConnectionInfo & connectInfo)
1416 if (!authority.IsActive())
1417 return TRUE;
1420 // if this is an authorisation request...
1421 if (request.inMIME.Contains(PHTTP::AuthorizationTag) &&
1422 authority.Validate(request, request.inMIME[PHTTP::AuthorizationTag]))
1423 return TRUE;
1425 // it must be a request for authorisation
1426 PMIMEInfo headers;
1427 server.SetDefaultMIMEInfo(headers, connectInfo);
1428 headers.SetAt(PHTTP::WWWAuthenticateTag,
1429 "Basic realm=\"" + authority.GetRealm(request) + "\"");
1430 headers.SetAt(PHTTP::ContentTypeTag, "text/html");
1432 const httpStatusCodeStruct * statusInfo =
1433 GetStatusCodeStruct(PHTTP::UnAuthorised);
1435 PHTML reply;
1436 reply << PHTML::Title()
1437 << statusInfo->code
1438 << ' '
1439 << statusInfo->text
1440 << PHTML::Body()
1441 << PHTML::Heading(1)
1442 << statusInfo->code
1443 << ' '
1444 << statusInfo->text
1445 << PHTML::Heading(1)
1446 << "Your request cannot be authorised because it requires authentication."
1447 << PHTML::Paragraph()
1448 << "This may be because you entered an incorrect username or password, "
1449 << "or because your browser is not performing Basic authentication."
1450 << PHTML::Body();
1452 server.StartResponse(PHTTP::UnAuthorised, headers, reply.GetLength());
1453 server.WriteString(reply);
1455 return FALSE;
1459 void PHTTPResource::SetAuthority(const PHTTPAuthority & auth)
1461 delete authority;
1462 authority = (PHTTPAuthority *)auth.Clone();
1466 void PHTTPResource::ClearAuthority()
1468 delete authority;
1469 authority = NULL;
1473 BOOL PHTTPResource::IsModifiedSince(const PTime &)
1475 return TRUE;
1479 BOOL PHTTPResource::GetExpirationDate(PTime &)
1481 return FALSE;
1485 PHTTPRequest * PHTTPResource::CreateRequest(const PURL & url,
1486 const PMIMEInfo & inMIME,
1487 const PMultipartFormInfoArray & multipartFormInfo,
1488 PHTTPServer & socket)
1490 return new PHTTPRequest(url, inMIME, multipartFormInfo, socket);
1494 static void WriteChunkedDataToServer(PHTTPServer & server, PCharArray & data)
1496 if (data.GetSize() == 0)
1497 return;
1499 server << data.GetSize() << "\r\n";
1500 server.Write(data, data.GetSize());
1501 server << "\r\n";
1502 data.SetSize(0);
1506 void PHTTPResource::SendData(PHTTPRequest & request)
1508 if (!request.outMIME.Contains(PHTTP::ContentTypeTag) && !contentType)
1509 request.outMIME.SetAt(PHTTP::ContentTypeTag, contentType);
1511 PCharArray data;
1512 if (LoadData(request, data)) {
1513 if (request.server.StartResponse(request.code, request.outMIME, request.contentSize)) {
1514 // Chunked transfer encoding
1515 request.outMIME.RemoveAll();
1516 do {
1517 WriteChunkedDataToServer(request.server, data);
1518 } while (LoadData(request, data));
1519 WriteChunkedDataToServer(request.server, data);
1520 request.server << "0\r\n" << request.outMIME;
1522 else {
1523 do {
1524 request.server.Write(data, data.GetSize());
1525 data.SetSize(0);
1526 } while (LoadData(request, data));
1527 request.server.Write(data, data.GetSize());
1530 else {
1531 request.server.StartResponse(request.code, request.outMIME, data.GetSize());
1532 request.server.Write(data, data.GetSize());
1537 BOOL PHTTPResource::LoadData(PHTTPRequest & request, PCharArray & data)
1539 PString text = LoadText(request);
1540 OnLoadedText(request, text);
1541 text.SetSize(text.GetLength()); // Lose the trailing '\0'
1542 data = text;
1543 return FALSE;
1547 PString PHTTPResource::LoadText(PHTTPRequest &)
1549 PAssertAlways(PUnimplementedFunction);
1550 return PString();
1554 void PHTTPResource::OnLoadedText(PHTTPRequest &, PString &)
1556 // Do nothing
1560 BOOL PHTTPResource::Post(PHTTPRequest & request,
1561 const PStringToString &,
1562 PHTML & msg)
1564 request.code = PHTTP::MethodNotAllowed;
1565 msg = "Error in POST";
1566 msg << "Post to this resource is not allowed" << PHTML::Body();
1567 return TRUE;
1571 //////////////////////////////////////////////////////////////////////////////
1572 // PHTTPString
1574 PHTTPString::PHTTPString(const PURL & url)
1575 : PHTTPResource(url, "text/html")
1580 PHTTPString::PHTTPString(const PURL & url,
1581 const PHTTPAuthority & auth)
1582 : PHTTPResource(url, "text/html", auth)
1587 PHTTPString::PHTTPString(const PURL & url, const PString & str)
1588 : PHTTPResource(url, "text/html"), string(str)
1593 PHTTPString::PHTTPString(const PURL & url,
1594 const PString & str,
1595 const PString & type)
1596 : PHTTPResource(url, type), string(str)
1601 PHTTPString::PHTTPString(const PURL & url,
1602 const PString & str,
1603 const PHTTPAuthority & auth)
1604 : PHTTPResource(url, "text/html", auth), string(str)
1609 PHTTPString::PHTTPString(const PURL & url,
1610 const PString & str,
1611 const PString & type,
1612 const PHTTPAuthority & auth)
1613 : PHTTPResource(url, type, auth), string(str)
1618 BOOL PHTTPString::LoadHeaders(PHTTPRequest & request)
1620 request.contentSize = string.GetLength();
1621 return TRUE;
1625 PString PHTTPString::LoadText(PHTTPRequest &)
1627 return string;
1631 //////////////////////////////////////////////////////////////////////////////
1632 // PHTTPFile
1634 PHTTPFile::PHTTPFile(const PURL & url, int)
1635 : PHTTPResource(url)
1640 PHTTPFile::PHTTPFile(const PString & filename)
1641 : PHTTPResource(filename, PMIMEInfo::GetContentType(PFilePath(filename).GetType())),
1642 filePath(filename)
1647 PHTTPFile::PHTTPFile(const PString & filename, const PHTTPAuthority & auth)
1648 : PHTTPResource(filename, auth), filePath(filename)
1653 PHTTPFile::PHTTPFile(const PURL & url, const PFilePath & path)
1654 : PHTTPResource(url, PMIMEInfo::GetContentType(path.GetType())),
1655 filePath(path)
1660 PHTTPFile::PHTTPFile(const PURL & url,
1661 const PFilePath & path,
1662 const PString & type)
1663 : PHTTPResource(url, type), filePath(path)
1668 PHTTPFile::PHTTPFile(const PURL & url,
1669 const PFilePath & path,
1670 const PHTTPAuthority & auth)
1671 : PHTTPResource(url, PMIMEInfo::GetContentType(path.GetType()), auth),
1672 filePath(path)
1677 PHTTPFile::PHTTPFile(const PURL & url,
1678 const PFilePath & path,
1679 const PString & type,
1680 const PHTTPAuthority & auth)
1681 : PHTTPResource(url, type, auth), filePath(path)
1686 PHTTPFileRequest::PHTTPFileRequest(const PURL & url,
1687 const PMIMEInfo & inMIME,
1688 const PMultipartFormInfoArray & multipartFormInfo,
1689 PHTTPServer & server)
1690 : PHTTPRequest(url, inMIME, multipartFormInfo, server)
1695 PHTTPRequest * PHTTPFile::CreateRequest(const PURL & url,
1696 const PMIMEInfo & inMIME,
1697 const PMultipartFormInfoArray & multipartFormInfo,
1698 PHTTPServer & server)
1700 return new PHTTPFileRequest(url, inMIME, multipartFormInfo, server);
1704 BOOL PHTTPFile::LoadHeaders(PHTTPRequest & request)
1706 PFile & file = ((PHTTPFileRequest&)request).file;
1708 if (!file.Open(filePath, PFile::ReadOnly)) {
1709 request.code = PHTTP::NotFound;
1710 return FALSE;
1713 request.contentSize = file.GetLength();
1714 return TRUE;
1718 BOOL PHTTPFile::LoadData(PHTTPRequest & request, PCharArray & data)
1720 PFile & file = ((PHTTPFileRequest&)request).file;
1722 PString contentType = GetContentType();
1723 if (contentType.IsEmpty())
1724 contentType = PMIMEInfo::GetContentType(file.GetFilePath().GetType());
1726 if (contentType(0, 4) *= "text/")
1727 return PHTTPResource::LoadData(request, data);
1729 PAssert(file.IsOpen(), PLogicError);
1731 PINDEX count = file.GetLength() - file.GetPosition();
1732 if (count > 10000)
1733 count = 10000;
1735 if (count > 0)
1736 PAssert(file.Read(data.GetPointer(count), count), PLogicError);
1738 if (!file.IsEndOfFile())
1739 return TRUE;
1741 file.Close();
1742 return FALSE;
1746 PString PHTTPFile::LoadText(PHTTPRequest & request)
1748 PFile & file = ((PHTTPFileRequest&)request).file;
1749 PAssert(file.IsOpen(), PLogicError);
1750 PINDEX count = file.GetLength();
1751 PString text;
1752 if (count > 0)
1753 PAssert(file.Read(text.GetPointer(count+1), count), PLogicError);
1754 PAssert(file.Close(), PLogicError);
1755 return text;
1759 //////////////////////////////////////////////////////////////////////////////
1760 // PHTTPTailFile
1762 PHTTPTailFile::PHTTPTailFile(const PString & filename)
1763 : PHTTPFile(filename)
1768 PHTTPTailFile::PHTTPTailFile(const PString & filename,
1769 const PHTTPAuthority & auth)
1770 : PHTTPFile(filename, auth)
1775 PHTTPTailFile::PHTTPTailFile(const PURL & url,
1776 const PFilePath & file)
1777 : PHTTPFile(url, file)
1782 PHTTPTailFile::PHTTPTailFile(const PURL & url,
1783 const PFilePath & file,
1784 const PString & contentType)
1785 : PHTTPFile(url, file, contentType)
1790 PHTTPTailFile::PHTTPTailFile(const PURL & url,
1791 const PFilePath & file,
1792 const PHTTPAuthority & auth)
1793 : PHTTPFile(url, file, auth)
1798 PHTTPTailFile::PHTTPTailFile(const PURL & url,
1799 const PFilePath & file,
1800 const PString & contentType,
1801 const PHTTPAuthority & auth)
1802 : PHTTPFile(url, file, contentType, auth)
1807 BOOL PHTTPTailFile::LoadHeaders(PHTTPRequest & request)
1809 if (!PHTTPFile::LoadHeaders(request))
1810 return FALSE;
1812 request.contentSize = P_MAX_INDEX;
1813 return TRUE;
1817 BOOL PHTTPTailFile::LoadData(PHTTPRequest & request, PCharArray & data)
1819 PFile & file = ((PHTTPFileRequest&)request).file;
1821 if (file.GetPosition() == 0)
1822 file.SetPosition(file.GetLength()-request.url.GetQueryVars()("offset", "10000").AsUnsigned());
1824 while (file.GetPosition() >= file.GetLength()) {
1825 if (!request.server.Write(NULL, 0))
1826 return FALSE;
1827 PThread::Sleep(200);
1830 PINDEX count = file.GetLength() - file.GetPosition();
1831 return file.Read(data.GetPointer(count), count);
1835 //////////////////////////////////////////////////////////////////////////////
1836 // PHTTPDirectory
1838 PHTTPDirectory::PHTTPDirectory(const PURL & url, const PDirectory & dir)
1839 : PHTTPFile(url, 0), basePath(dir), allowDirectoryListing(TRUE)
1844 PHTTPDirectory::PHTTPDirectory(const PURL & url,
1845 const PDirectory & dir,
1846 const PHTTPAuthority & auth)
1847 : PHTTPFile(url, PString(), auth), basePath(dir), allowDirectoryListing(TRUE)
1852 PHTTPDirRequest::PHTTPDirRequest(const PURL & url,
1853 const PMIMEInfo & inMIME,
1854 const PMultipartFormInfoArray & multipartFormInfo,
1855 PHTTPServer & server)
1856 : PHTTPFileRequest(url, inMIME, multipartFormInfo, server)
1861 PHTTPRequest * PHTTPDirectory::CreateRequest(const PURL & url,
1862 const PMIMEInfo & inMIME,
1863 const PMultipartFormInfoArray & multipartFormInfo,
1864 PHTTPServer & socket)
1866 PHTTPDirRequest * request = new PHTTPDirRequest(url, inMIME, multipartFormInfo, socket);
1868 const PStringArray & path = url.GetPath();
1869 request->realPath = basePath;
1870 PINDEX i;
1871 for (i = GetURL().GetPath().GetSize(); i < path.GetSize()-1; i++)
1872 request->realPath += path[i] + PDIR_SEPARATOR;
1874 // append the last path element
1875 if (i < path.GetSize())
1876 request->realPath += path[i];
1878 if (request->realPath.Find(basePath) != 0)
1879 request->realPath = basePath;
1881 return request;
1885 void PHTTPDirectory::EnableAuthorisation(const PString & realm)
1887 authorisationRealm = realm;
1891 BOOL PHTTPDirectory::FindAuthorisations(const PDirectory & dir, PString & realm, PStringToString & authorisations)
1893 PFilePath fn = dir + accessFilename;
1894 PTextFile file;
1895 BOOL first = TRUE;
1896 if (file.Open(fn, PFile::ReadOnly)) {
1897 PString line;
1898 while (file.ReadLine(line)) {
1899 if (first) {
1900 realm = line.Trim();
1901 first = FALSE;
1902 } else {
1903 PStringArray tokens = line.Tokenise(':');
1904 if (tokens.GetSize() > 1)
1905 authorisations.SetAt(tokens[0].Trim(), tokens[1].Trim());
1908 return TRUE;
1911 if (dir.IsRoot() || (dir == basePath))
1912 return FALSE;
1914 return FindAuthorisations(dir.GetParent(), realm, authorisations);
1917 BOOL PHTTPDirectory::CheckAuthority(PHTTPServer & server,
1918 const PHTTPRequest & request,
1919 const PHTTPConnectionInfo & conInfo)
1921 // if access control is enabled, then search parent directories for password files
1922 PStringToString authorisations;
1923 PString newRealm;
1924 if (authorisationRealm.IsEmpty() ||
1925 !FindAuthorisations(((PHTTPDirRequest&)request).realPath.GetDirectory(), newRealm, authorisations) ||
1926 authorisations.GetSize() == 0)
1927 return TRUE;
1929 PHTTPMultiSimpAuth authority(newRealm, authorisations);
1930 return PHTTPResource::CheckAuthority(authority, server, request, conInfo);
1933 BOOL PHTTPDirectory::LoadHeaders(PHTTPRequest & request)
1935 PFilePath & realPath = ((PHTTPDirRequest&)request).realPath;
1937 // if not able to obtain resource information, then consider the resource "not found"
1938 PFileInfo info;
1939 if (!PFile::GetInfo(realPath, info)) {
1940 request.code = PHTTP::NotFound;
1941 return FALSE;
1944 // if the resource is a file, and the file can't be opened, then return "not found"
1945 PFile & file = ((PHTTPDirRequest&)request).file;
1946 if (info.type != PFileInfo::SubDirectory) {
1947 if (!file.Open(realPath, PFile::ReadOnly) ||
1948 (!authorisationRealm.IsEmpty() && realPath.GetFileName() == accessFilename)) {
1949 request.code = PHTTP::NotFound;
1950 return FALSE;
1954 // resource is a directory - if index files disabled, then return "not found"
1955 else if (!allowDirectoryListing) {
1956 request.code = PHTTP::NotFound;
1957 return FALSE;
1960 // else look for index files
1961 else {
1962 PINDEX i;
1963 for (i = 0; i < PARRAYSIZE(HTMLIndexFiles); i++)
1964 if (file.Open(realPath +
1965 PDIR_SEPARATOR + HTMLIndexFiles[i], PFile::ReadOnly))
1966 break;
1969 // open the file and return information
1970 PString & fakeIndex = ((PHTTPDirRequest&)request).fakeIndex;
1971 if (file.IsOpen()) {
1972 request.outMIME.SetAt(PHTTP::ContentTypeTag,
1973 PMIMEInfo::GetContentType(file.GetFilePath().GetType()));
1974 request.contentSize = file.GetLength();
1975 fakeIndex = PString();
1976 return TRUE;
1979 // construct a directory listing
1980 request.outMIME.SetAt(PHTTP::ContentTypeTag, "text/html");
1981 PHTML reply("Directory of " + request.url.AsString());
1982 PDirectory dir = realPath;
1983 if (dir.Open()) {
1984 do {
1985 const char * imgName;
1986 if (dir.IsSubDir())
1987 imgName = "internal-gopher-menu";
1988 else if (PMIMEInfo::GetContentType(
1989 PFilePath(dir.GetEntryName()).GetType())(0,4) == "text/")
1990 imgName = "internal-gopher-text";
1991 else
1992 imgName = "internal-gopher-unknown";
1993 reply << PHTML::Image(imgName) << ' '
1994 << PHTML::HotLink(realPath.GetFileName()+'/'+dir.GetEntryName())
1995 << dir.GetEntryName()
1996 << PHTML::HotLink()
1997 << PHTML::BreakLine();
1998 } while (dir.Next());
2000 reply << PHTML::Body();
2001 fakeIndex = reply;
2003 return TRUE;
2007 PString PHTTPDirectory::LoadText(PHTTPRequest & request)
2009 PString & fakeIndex = ((PHTTPDirRequest&)request).fakeIndex;
2010 if (fakeIndex.IsEmpty())
2011 return PHTTPFile::LoadText(request);
2013 return fakeIndex;
2017 // End Of File ///////////////////////////////////////////////////////////////