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
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
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
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
179 #include <ptlib/sockets.h>
180 #include <ptclib/http.h>
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 //////////////////////////////////////////////////////////////////////////////
203 PHTTPSpace::PHTTPSpace()
205 mutex
= new PReadWriteMutex
;
206 root
= new Node(PString(), NULL
);
210 void PHTTPSpace::DestroyContents()
217 void PHTTPSpace::CloneContents(const PHTTPSpace
* c
)
219 mutex
= new PReadWriteMutex
;
220 root
= new Node(*c
->root
);
224 void PHTTPSpace::CopyContents(const PHTTPSpace
& c
)
231 PHTTPSpace::Node::Node(const PString
& nam
, Node
* parentNode
)
239 PHTTPSpace::Node::~Node()
245 BOOL
PHTTPSpace::AddResource(PHTTPResource
* res
, AddOptions overwrite
)
247 PAssert(res
!= NULL
, PInvalidParameter
);
248 const PStringArray
& path
= res
->GetURL().GetPath();
250 for (PINDEX i
= 0; i
< path
.GetSize(); i
++) {
251 if (path
[i
].IsEmpty())
254 if (node
->resource
!= NULL
) {
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()) {
268 return FALSE
; // Already a resource in tree further down path.
271 if (overwrite
== ErrorOnExist
&& node
->resource
!= NULL
) {
273 return FALSE
; // Already a resource in tree at leaf
276 delete node
->resource
;
277 node
->resource
= res
;
283 BOOL
PHTTPSpace::DelResource(const PURL
& url
)
285 const PStringArray
& path
= url
.GetPath();
287 for (PINDEX i
= 0; i
< path
.GetSize(); i
++) {
288 if (path
[i
].IsEmpty())
291 PINDEX pos
= node
->children
.GetValuesIndex(path
[i
]);
292 if (pos
== P_MAX_INDEX
)
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))
303 if (!node
->children
.IsEmpty())
304 return FALSE
; // Still a resource in tree further down path.
306 if (node
->parent
!= NULL
) {
308 Node
* par
= node
->parent
;
309 par
->children
.Remove(node
);
311 } while (node
!= NULL
&& node
->children
.IsEmpty());
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();
329 for (i
= 0; i
< path
.GetSize(); i
++) {
330 if (path
[i
].IsEmpty())
333 PINDEX pos
= node
->children
.GetValuesIndex(path
[i
]);
334 if (pos
== P_MAX_INDEX
)
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
;
353 //////////////////////////////////////////////////////////////////////////////
356 PHTTPServer::PHTTPServer()
362 PHTTPServer::PHTTPServer(const PHTTPSpace
& space
)
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
)
384 PString seperator
= type
.Mid(pos
+1).Trim();
387 pos
= seperator
.Find("boundary");
388 if (pos
== P_MAX_INDEX
)
390 seperator
= seperator
.Mid(8).Trim();
393 pos
= seperator
.Find("=");
394 if (pos
== P_MAX_INDEX
)
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;
412 PMultipartFormInfo
* info
= NULL
;
414 while (!last
&& (entityOffs
< entityLen
)) {
417 PINDEX partStart
= entityOffs
;
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)) {
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
439 // determine if this is the last block
440 if (((entityOffs
+ 2) <= entityLen
) && (body
[entityOffs
] == '-') && (body
[entityOffs
+1] == '-')) {
446 if (((entityOffs
+ 2) <= entityLen
) && (body
[entityOffs
] == '\r') && (body
[entityOffs
+1] == '\n'))
450 // ignore everything up to the first seperator,
451 // then adjust seperator to include leading CRLF
454 seperator
= PString("\r\n") + seperator
;
455 sepLen
= seperator
.GetLength();
456 sep
= (const char *)seperator
;
460 // extract the MIME header, by looking for a double CRLF
463 for (ptr
= partStart
;(ptr
< (partStart
+ partLen
)) && (nlCount
< 2); ptr
++) {
464 if (body
[ptr
] == '\r') {
466 if ((ptr
< entityLen
-1) && (body
[ptr
+1] == '\n'))
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
);
491 // ignore until first separator
496 } while (line
.Find(sep
) != 0);
498 PMultipartFormInfo
* info
= NULL
;
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
;
519 PINDEX sepLen
= sep
.GetLength();
520 const char * sepPtr
= (const char *)sep
;
521 while (data
.good()) {
524 if ((len
>= sepLen
) && (memcmp(((const char *)buf
) + len
- sepLen
, sepPtr
, sepLen
) == 0)) {
542 while (data.good()) {
544 if (line.Find(sep) == 0)
546 info->body += line + "\n";
550 multipartFormInfoArray
.Append(info
);
556 BOOL
PHTTPServer::ProcessCommand()
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
))
570 connectInfo
.commandCode
= (Commands
)cmd
;
571 if (cmd
< NumCommands
)
572 connectInfo
.commandName
= commandNames
[cmd
];
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
);
585 if (!connectInfo
.Initialise(*this, args
))
588 // now that we've decided we did receive a HTTP request, increment the
589 // count of transactions
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
600 connectInfo
.url
= "https://" + args
;
602 connectInfo
.url
= args
;
603 if (connectInfo
.url
.GetPort() == 0)
604 connectInfo
.url
.SetPort(myPort
);
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
);
624 connectInfo
.entityBody
= ReadEntityBody();
626 // Handle the local request
627 PStringToString postData
;
630 persist
= OnGET(url
, connectInfo
.GetMIME(), connectInfo
);
634 persist
= OnHEAD(url
, connectInfo
.GetMIME(), connectInfo
);
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
);
651 persist
= OnUnknown(args
, connectInfo
);
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
)
668 PTRACE(5, "HTTPServer\tConnection end: " << connectInfo
.IsPersistant());
670 // close the output stream now and return FALSE
671 Shutdown(ShutdownWrite
);
676 PString
PHTTPServer::ReadEntityBody()
678 if (connectInfo
.GetMajorVersion() < 1)
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
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();
701 socket
->Shutdown(PIPSocket::ShutdownRead
);
708 PString
PHTTPServer::GetServerName() const
710 return "PWLib-HTTP-Server/1.0 PWLib/1.0";
714 void PHTTPServer::SetURLSpace(const PHTTPSpace
& 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
) {
728 return OnError(NotFound
, url
.AsString(), connectInfo
);
731 BOOL retval
= resource
->OnGET(*this, url
, info
, connectInfo
);
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
) {
745 return OnError(NotFound
, url
.AsString(), connectInfo
);
748 BOOL retval
= resource
->OnHEAD(*this, url
, info
, connectInfo
);
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
) {
763 return OnError(NotFound
, url
.AsString(), connectInfo
);
766 BOOL retval
= resource
->OnPOST(*this, url
, info
, data
, connectInfo
);
772 BOOL
PHTTPServer::OnProxy(const PHTTPConnectionInfo
& connectInfo
)
774 return OnError(BadGateway
, "Proxy not implemented.", connectInfo
) &&
775 connectInfo
.GetCommandCode() != CONNECT
;
779 struct httpStatusCodeStruct
{
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
,
839 if (connectInfo
.majorVersion
< 1)
842 httpStatusCodeStruct dummyInfo
;
843 const httpStatusCodeStruct
* statusInfo
;
844 if (connectInfo
.commandCode
< NumCommands
)
845 statusInfo
= GetStatusCodeStruct(code
);
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.
867 headers
.SetAt(ContentLengthTag
, bodySize
);
870 // v1.1 or later, see if will use chunked output
871 chunked
= bodySize
== P_MAX_INDEX
;
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);
893 void PHTTPServer::SetDefaultMIMEInfo(PMIMEInfo
& info
,
894 const PHTTPConnectionInfo
& connectInfo
)
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
);
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);
935 SetDefaultMIMEInfo(headers
, connectInfo
);
937 if (!statusInfo
->allowedBody
) {
938 StartResponse(code
, headers
, 0);
939 return statusInfo
->code
== RequestOK
;
943 if (extra
.Find("<body") != P_MAX_INDEX
)
947 html
<< PHTML::Title()
962 headers
.SetAt(ContentTypeTag
, "text/html");
963 StartResponse(code
, headers
, reply
.GetLength());
965 return statusInfo
->code
== RequestOK
;
969 //////////////////////////////////////////////////////////////////////////////
972 void PHTTPAuthority::DecodeBasicAuthority(const PString
& authInfo
,
977 if (authInfo(0, 5) *= "Basic ")
978 decoded
= PBase64::Decode(authInfo(6, P_MAX_INDEX
));
980 decoded
= PBase64::Decode(authInfo
);
982 PINDEX colonPos
= decoded
.Find(':');
983 if (colonPos
== P_MAX_INDEX
) {
985 password
= PString();
988 username
= decoded
.Left(colonPos
).Trim();
989 password
= decoded
.Mid(colonPos
+1).Trim();
994 BOOL
PHTTPAuthority::IsActive() const
1000 //////////////////////////////////////////////////////////////////////////////
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
1030 BOOL
PHTTPSimpleAuth::Validate(const PHTTPRequest
&,
1031 const PString
& authInfo
) const
1034 DecodeBasicAuthority(authInfo
, user
, pass
);
1035 return username
== user
&& password
== pass
;
1039 //////////////////////////////////////////////////////////////////////////////
1040 // PHTTPMultiSimpAuth
1042 PHTTPMultiSimpAuth::PHTTPMultiSimpAuth(const PString
& 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
1075 BOOL
PHTTPMultiSimpAuth::Validate(const PHTTPRequest
&,
1076 const PString
& authInfo
) const
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 //////////////////////////////////////////////////////////////////////////////
1093 PHTTPRequest::PHTTPRequest(const PURL
& _url
,
1094 const PMIMEInfo
& _mime
,
1095 const PMultipartFormInfoArray
& _multipartFormInfo
,
1096 PHTTPServer
& _server
)
1100 multipartFormInfo(_multipartFormInfo
),
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
;
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)) {
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);
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
1168 if (!mimeInfo
.Read(server
))
1171 wasPersistant
= isPersistant
;
1172 isPersistant
= FALSE
;
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
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
1203 entityBodyLength
= mimeInfo
.GetInteger(PHTTP::ContentLengthTag
,
1204 (commandCode
== PHTTP::POST
) ? -2 : 0);
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");
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)
1230 return (majorVersion
> major
) ||
1231 ((majorVersion
== major
) && (minorVersion
>= minor
));
1235 //////////////////////////////////////////////////////////////////////////////
1238 PHTTPResource::PHTTPResource(const PURL
& url
)
1246 PHTTPResource::PHTTPResource(const PURL
& url
, const PHTTPAuthority
& auth
)
1249 authority
= (PHTTPAuthority
*)auth
.Clone();
1254 PHTTPResource::PHTTPResource(const PURL
& url
, const PString
& type
)
1255 : baseURL(url
), contentType(type
)
1262 PHTTPResource::PHTTPResource(const PURL
& url
,
1263 const PString
& type
,
1264 const PHTTPAuthority
& auth
)
1265 : baseURL(url
), contentType(type
)
1267 authority
= (PHTTPAuthority
*)auth
.Clone();
1272 PHTTPResource::~PHTTPResource()
1278 BOOL
PHTTPResource::OnGET(PHTTPServer
& server
,
1280 const PMIMEInfo
& info
,
1281 const PHTTPConnectionInfo
& connectInfo
)
1283 return OnGETOrHEAD(server
, url
, info
, connectInfo
, TRUE
);
1287 BOOL
PHTTPResource::OnHEAD(PHTTPServer
& server
,
1289 const PMIMEInfo
& info
,
1290 const PHTTPConnectionInfo
& connectInfo
)
1292 return OnGETOrHEAD(server
, url
, info
, connectInfo
, FALSE
);
1296 BOOL
PHTTPResource::OnGETOrHEAD(PHTTPServer
& server
,
1298 const PMIMEInfo
& info
,
1299 const PHTTPConnectionInfo
& connectInfo
,
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
,
1310 connectInfo
.GetMultipartFormInfo(),
1314 if (CheckAuthority(server
, *request
, connectInfo
)) {
1316 server
.SetDefaultMIMEInfo(request
->outMIME
, connectInfo
);
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
);
1326 retVal
= request
->outMIME
.Contains(PHTTP::ContentLengthTag
);
1329 retVal
= OnGETData(server
, url
, connectInfo
, *request
);
1338 BOOL
PHTTPResource::OnGETData(PHTTPServer
& /*server*/,
1339 const PURL
& /*url*/,
1340 const PHTTPConnectionInfo
& /*connectInfo*/,
1341 PHTTPRequest
& request
)
1344 return request
.outMIME
.Contains(PHTTP::ContentLengthTag
) ||
1345 request
.outMIME
.Contains(PHTTP::TransferEncodingTag
);
1349 BOOL
PHTTPResource::OnPOST(PHTTPServer
& server
,
1351 const PMIMEInfo
& info
,
1352 const PStringToString
& data
,
1353 const PHTTPConnectionInfo
& connectInfo
)
1355 PHTTPRequest
* request
= CreateRequest(url
,
1357 connectInfo
.GetMultipartFormInfo(),
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
;
1375 BOOL
PHTTPResource::OnPOSTData(PHTTPRequest
& request
,
1376 const PStringToString
& data
)
1379 BOOL persist
= Post(request
, data
, msg
);
1381 if (msg
.Is(PHTML::InBody
))
1382 msg
<< PHTML::Body();
1384 if (request
.code
!= PHTTP::RequestOK
)
1388 msg
<< PHTML::Title() << (unsigned)PHTTP::RequestOK
<< " OK" << PHTML::Body()
1389 << PHTML::Heading(1) << (unsigned)PHTTP::RequestOK
<< " OK" << PHTML::Heading(1)
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
)
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())
1420 // if this is an authorisation request...
1421 if (request
.inMIME
.Contains(PHTTP::AuthorizationTag
) &&
1422 authority
.Validate(request
, request
.inMIME
[PHTTP::AuthorizationTag
]))
1425 // it must be a request for authorisation
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
);
1436 reply
<< PHTML::Title()
1441 << PHTML::Heading(1)
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."
1452 server
.StartResponse(PHTTP::UnAuthorised
, headers
, reply
.GetLength());
1453 server
.WriteString(reply
);
1459 void PHTTPResource::SetAuthority(const PHTTPAuthority
& auth
)
1462 authority
= (PHTTPAuthority
*)auth
.Clone();
1466 void PHTTPResource::ClearAuthority()
1473 BOOL
PHTTPResource::IsModifiedSince(const PTime
&)
1479 BOOL
PHTTPResource::GetExpirationDate(PTime
&)
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)
1499 server
<< data
.GetSize() << "\r\n";
1500 server
.Write(data
, data
.GetSize());
1506 void PHTTPResource::SendData(PHTTPRequest
& request
)
1508 if (!request
.outMIME
.Contains(PHTTP::ContentTypeTag
) && !contentType
)
1509 request
.outMIME
.SetAt(PHTTP::ContentTypeTag
, contentType
);
1512 if (LoadData(request
, data
)) {
1513 if (request
.server
.StartResponse(request
.code
, request
.outMIME
, request
.contentSize
)) {
1514 // Chunked transfer encoding
1515 request
.outMIME
.RemoveAll();
1517 WriteChunkedDataToServer(request
.server
, data
);
1518 } while (LoadData(request
, data
));
1519 WriteChunkedDataToServer(request
.server
, data
);
1520 request
.server
<< "0\r\n" << request
.outMIME
;
1524 request
.server
.Write(data
, data
.GetSize());
1526 } while (LoadData(request
, data
));
1527 request
.server
.Write(data
, data
.GetSize());
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'
1547 PString
PHTTPResource::LoadText(PHTTPRequest
&)
1549 PAssertAlways(PUnimplementedFunction
);
1554 void PHTTPResource::OnLoadedText(PHTTPRequest
&, PString
&)
1560 BOOL
PHTTPResource::Post(PHTTPRequest
& request
,
1561 const PStringToString
&,
1564 request
.code
= PHTTP::MethodNotAllowed
;
1565 msg
= "Error in POST";
1566 msg
<< "Post to this resource is not allowed" << PHTML::Body();
1571 //////////////////////////////////////////////////////////////////////////////
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();
1625 PString
PHTTPString::LoadText(PHTTPRequest
&)
1631 //////////////////////////////////////////////////////////////////////////////
1634 PHTTPFile::PHTTPFile(const PURL
& url
, int)
1635 : PHTTPResource(url
)
1640 PHTTPFile::PHTTPFile(const PString
& filename
)
1641 : PHTTPResource(filename
, PMIMEInfo::GetContentType(PFilePath(filename
).GetType())),
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())),
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
),
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
;
1713 request
.contentSize
= file
.GetLength();
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();
1736 PAssert(file
.Read(data
.GetPointer(count
), count
), PLogicError
);
1738 if (!file
.IsEndOfFile())
1746 PString
PHTTPFile::LoadText(PHTTPRequest
& request
)
1748 PFile
& file
= ((PHTTPFileRequest
&)request
).file
;
1749 PAssert(file
.IsOpen(), PLogicError
);
1750 PINDEX count
= file
.GetLength();
1753 PAssert(file
.Read(text
.GetPointer(count
+1), count
), PLogicError
);
1754 PAssert(file
.Close(), PLogicError
);
1759 //////////////////////////////////////////////////////////////////////////////
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
))
1812 request
.contentSize
= P_MAX_INDEX
;
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))
1827 PThread::Sleep(200);
1830 PINDEX count
= file
.GetLength() - file
.GetPosition();
1831 return file
.Read(data
.GetPointer(count
), count
);
1835 //////////////////////////////////////////////////////////////////////////////
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
;
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
;
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
;
1896 if (file
.Open(fn
, PFile::ReadOnly
)) {
1898 while (file
.ReadLine(line
)) {
1900 realm
= line
.Trim();
1903 PStringArray tokens
= line
.Tokenise(':');
1904 if (tokens
.GetSize() > 1)
1905 authorisations
.SetAt(tokens
[0].Trim(), tokens
[1].Trim());
1911 if (dir
.IsRoot() || (dir
== basePath
))
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
;
1924 if (authorisationRealm
.IsEmpty() ||
1925 !FindAuthorisations(((PHTTPDirRequest
&)request
).realPath
.GetDirectory(), newRealm
, authorisations
) ||
1926 authorisations
.GetSize() == 0)
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"
1939 if (!PFile::GetInfo(realPath
, info
)) {
1940 request
.code
= PHTTP::NotFound
;
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
;
1954 // resource is a directory - if index files disabled, then return "not found"
1955 else if (!allowDirectoryListing
) {
1956 request
.code
= PHTTP::NotFound
;
1960 // else look for index files
1963 for (i
= 0; i
< PARRAYSIZE(HTMLIndexFiles
); i
++)
1964 if (file
.Open(realPath
+
1965 PDIR_SEPARATOR
+ HTMLIndexFiles
[i
], PFile::ReadOnly
))
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();
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
;
1985 const char * imgName
;
1987 imgName
= "internal-gopher-menu";
1988 else if (PMIMEInfo::GetContentType(
1989 PFilePath(dir
.GetEntryName()).GetType())(0,4) == "text/")
1990 imgName
= "internal-gopher-text";
1992 imgName
= "internal-gopher-unknown";
1993 reply
<< PHTML::Image(imgName
) << ' '
1994 << PHTML::HotLink(realPath
.GetFileName()+'/'+dir
.GetEntryName())
1995 << dir
.GetEntryName()
1997 << PHTML::BreakLine();
1998 } while (dir
.Next());
2000 reply
<< PHTML::Body();
2007 PString
PHTTPDirectory::LoadText(PHTTPRequest
& request
)
2009 PString
& fakeIndex
= ((PHTTPDirRequest
&)request
).fakeIndex
;
2010 if (fakeIndex
.IsEmpty())
2011 return PHTTPFile::LoadText(request
);
2017 // End Of File ///////////////////////////////////////////////////////////////