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.33 2003/04/23 07:00:15 rogerh
28 * Fix the encoding checking. the find_ip sample program now works again
30 * Revision 1.32 2003/01/28 06:48:35 robertj
31 * Added https support to PHTTPClient (if #define P_SSL availbel).
33 * Revision 1.31 2002/12/03 22:38:35 robertj
34 * Removed get document that just returns a content length as the chunked
35 * transfer encoding makes this very dangerous.
36 * Added GetTextDocument() to get a URL content into a PString.
37 * Added a version pf PostData() that gets the reponse content into a PString.
38 * Added ReadContentBody() that takes a PString, not just PBYTEArray.
39 * Fixed bug where conten-encoding must be checked even if there is a
40 * full content length MIME field.
42 * Revision 1.30 2002/11/06 22:47:25 robertj
43 * Fixed header comment (copyright etc)
45 * Revision 1.29 2002/10/10 04:43:44 robertj
46 * VxWorks port, thanks Martijn Roest
48 * Revision 1.28 2002/05/28 01:41:50 robertj
49 * Fixed bug in reading chunked data, thanks David Iodice
51 * Revision 1.27 2001/10/30 07:02:28 robertj
52 * Fixed problem with bad servers causing endless loops in client.
54 * Revision 1.26 2001/10/03 00:26:34 robertj
55 * Upgraded client to HTTP/1.1 and for chunked mode entity bodies.
57 * Revision 1.25 2001/09/28 08:55:15 robertj
58 * More changes to support restartable PHTTPClient
60 * Revision 1.24 2001/09/28 00:43:47 robertj
61 * Added automatic setting of some outward MIME fields.
62 * Added "user agent" string field for automatic inclusion.
63 * Added function to read the contents of the HTTP request.
64 * Added "restarting" of connection if lost persistence.
66 * Revision 1.23 2001/09/11 03:27:46 robertj
67 * Improved error processing on high level protocol failures, usually
68 * caused by unexpected shut down of a socket.
70 * Revision 1.22 2001/09/10 02:51:23 robertj
71 * Major change to fix problem with error codes being corrupted in a
72 * PChannel when have simultaneous reads and writes in threads.
74 * Revision 1.21 2001/02/22 05:27:14 robertj
75 * Added "nicer" version of GetDocument in HTTP client class.
77 * Revision 1.20 1999/05/13 04:59:24 robertj
78 * Increased amount of buffering on output request write.
80 * Revision 1.19 1999/05/11 12:23:52 robertj
81 * Fixed bug introduced in last revision to have arbitrary HTTP commands, missing CRLF.
83 * Revision 1.18 1999/05/04 15:26:01 robertj
84 * Improved HTTP/1.1 compatibility (pass through user commands).
85 * Fixed problems with quicktime installer.
87 * Revision 1.17 1998/11/30 04:51:55 robertj
88 * New directory structure
90 * Revision 1.16 1998/09/23 06:22:09 robertj
91 * Added open source copyright license.
93 * Revision 1.15 1998/07/24 06:57:21 robertj
94 * Fixed error returned on illegal URL passed to unopened socket.
95 * Changed PostData function so just has string for data instead of dictionary.
97 * Revision 1.14 1998/06/16 03:32:56 robertj
98 * Changed TCP connection shutdown to be parameterised.
100 * Revision 1.13 1998/06/13 15:03:58 robertj
101 * More conditions for NOT shutting down write.
103 * Revision 1.12 1998/06/13 12:28:04 robertj
104 * Added shutdown to client command if no content length specified.
106 * Revision 1.11 1998/04/14 03:42:41 robertj
107 * Fixed error code propagation in HTTP client.
109 * Revision 1.10 1998/02/03 06:27:10 robertj
110 * Fixed propagation of error codes, especially EOF.
111 * Fixed writing to some CGI scripts that require CRLF outside of byte count.
113 * Revision 1.9 1998/01/26 00:39:00 robertj
114 * Added function to allow HTTPClient to automatically connect if URL has hostname.
115 * Fixed incorrect return values on HTTPClient GetDocument(), Post etc functions.
117 * Revision 1.8 1997/06/12 12:33:35 robertj
118 * Fixed bug where mising MIME fields is regarded as an eror.
120 * Revision 1.7 1997/03/31 08:26:58 robertj
121 * GNU compiler compatibilty.
123 * Revision 1.6 1997/03/28 04:40:46 robertj
124 * Fixed bug in Post function doing wrong command.
126 * Revision 1.5 1997/03/18 22:04:03 robertj
127 * Fix bug for binary POST commands.
129 * Revision 1.4 1996/12/21 01:26:21 robertj
130 * Fixed bug in persistent connections when server closes socket during command.
132 * Revision 1.3 1996/12/12 09:24:44 robertj
133 * Persistent connection support.
135 * Revision 1.2 1996/10/08 13:12:03 robertj
136 * Fixed bug in HTTP/0.9 response, first 5 character not put back properly.
138 * Revision 1.1 1996/09/14 13:02:18 robertj
141 * Revision 1.37 1996/08/25 09:37:41 robertj
142 * Added function to detect "local" host name.
143 * Fixed printing of trailing '/' in empty URL, is distinction between with and without.
145 * Revision 1.36 1996/08/22 13:22:26 robertj
146 * Fixed bug in print of URLs, extra @ signs.
148 * Revision 1.35 1996/08/19 13:42:40 robertj
149 * Fixed errors in URL parsing and display.
150 * Fixed "Forbidden" problem out of HTTP authorisation system.
151 * Fixed authorisation so if have no user/password on basic authentication, does not require it.
153 * Revision 1.34 1996/07/27 04:13:47 robertj
154 * Fixed use of HTTP proxy on non-persistent connections.
156 * Revision 1.33 1996/07/15 10:37:20 robertj
157 * Improved proxy "self" detection (especially localhost).
159 * Revision 1.32 1996/06/28 13:20:24 robertj
160 * Modified HTTPAuthority so gets PHTTPReqest (mainly for URL) passed in.
161 * Moved HTTP form resource to another compilation module.
162 * Fixed memory leak in POST command.
164 * Revision 1.31 1996/06/10 10:00:00 robertj
165 * Added global function for query parameters parsing.
167 * Revision 1.30 1996/06/07 13:52:23 robertj
168 * Added PUT to HTTP proxy FTP. Necessitating redisign of entity body processing.
170 * Revision 1.29 1996/06/05 12:33:04 robertj
171 * Fixed bug in parsing URL with no path, is NOT absolute!
173 * Revision 1.28 1996/05/30 10:07:26 robertj
174 * Fixed bug in version number checking of return code compatibility.
176 * Revision 1.27 1996/05/26 03:46:42 robertj
177 * Compatibility to GNU 2.7.x
179 * Revision 1.26 1996/05/23 10:02:13 robertj
180 * Added common function for GET and HEAD commands.
181 * Fixed status codes to be the actual status code instead of sequential enum.
182 * This fixed some problems with proxy pass through of status codes.
183 * Fixed bug in URL parsing of username and passwords.
185 * Revision 1.19.1.1 1996/04/17 11:08:22 craigs
186 * New version by craig pending confirmation by robert
188 * Revision 1.19 1996/04/05 01:46:30 robertj
189 * Assured PSocket::Write always writes the number of bytes specified, no longer need write loops.
190 * Added workaraound for NT Netscape Navigator bug with persistent connections.
192 * Revision 1.18 1996/03/31 09:05:07 robertj
195 * Revision 1.17 1996/03/17 05:48:07 robertj
196 * Fixed host name print out of URLs.
197 * Added hit count to PHTTPResource.
199 * Revision 1.16 1996/03/16 05:00:26 robertj
200 * Added ParseReponse() for splitting reponse line into code and info.
201 * Added client side support for HTTP socket.
202 * Added hooks for proxy support in HTTP socket.
203 * Added translation type to TranslateString() to accommodate query variables.
204 * Defaulted scheme field in URL to "http".
205 * Inhibited output of port field on string conversion of URL according to scheme.
207 * Revision 1.15 1996/03/11 10:29:50 robertj
208 * Fixed bug in help image HTML.
210 * Revision 1.14 1996/03/10 13:15:24 robertj
211 * Redesign to make resources thread safe.
213 * Revision 1.13 1996/03/02 03:27:37 robertj
214 * Added function to translate a string to a form suitable for inclusion in a URL.
215 * Added radio button and selection boxes to HTTP form resource.
216 * Fixed bug in URL parsing, losing first / if hostname specified.
218 * Revision 1.12 1996/02/25 11:14:24 robertj
219 * Radio button support for forms.
221 * Revision 1.11 1996/02/25 03:10:34 robertj
222 * Removed pass through HTTP resource.
223 * Fixed PHTTPConfig resource to use correct name for config key.
225 * Revision 1.10 1996/02/19 13:48:28 robertj
226 * Put multiple uses of literal strings into const variables.
227 * Fixed URL parsing so that the unmangling of strings occurs correctly.
228 * Moved nested classes from PHTTPForm.
229 * Added overwrite option to AddResource().
230 * Added get/set string to PHTTPString resource.
232 * Revision 1.9 1996/02/13 13:09:17 robertj
233 * Added extra parameters to callback function in PHTTPResources, required
234 * by descendants to make informed decisions on data being loaded.
236 * Revision 1.8 1996/02/08 12:26:29 robertj
237 * Redesign of resource callback mechanism.
238 * Added new resource types for HTML data entry forms.
240 * Revision 1.7 1996/02/03 11:33:19 robertj
241 * Changed RadCmd() so can distinguish between I/O error and unknown command.
243 * Revision 1.6 1996/02/03 11:11:49 robertj
244 * Numerous bug fixes.
245 * Added expiry date and ismodifiedsince support.
247 * Revision 1.5 1996/01/30 23:32:40 robertj
250 * Revision 1.4 1996/01/28 14:19:09 robertj
251 * Split HTML into separate source file.
252 * Beginning of pass through resource type.
253 * Changed PCharArray in OnLoadData to PString for convenience in mangling data.
254 * Made PHTTPSpace return standard page on selection of partial path.
256 * Revision 1.3 1996/01/28 02:49:16 robertj
257 * Further implementation.
259 * Revision 1.2 1996/01/26 02:24:30 robertj
260 * Further implemetation.
262 * Revision 1.1 1996/01/23 13:04:32 robertj
268 #include <ptlib/sockets.h>
269 #include <ptclib/http.h>
272 #include <ptclib/pssl.h>
278 //////////////////////////////////////////////////////////////////////////////
281 PHTTPClient::PHTTPClient()
286 PHTTPClient::PHTTPClient(const PString
& userAgent
)
287 : userAgentName(userAgent
)
292 int PHTTPClient::ExecuteCommand(Commands cmd
,
295 const PString
& dataBody
,
296 PMIMEInfo
& replyMime
,
299 return ExecuteCommand(commandNames
[cmd
], url
, outMIME
, dataBody
, replyMime
, persist
);
303 int PHTTPClient::ExecuteCommand(const PString
& cmdName
,
306 const PString
& dataBody
,
307 PMIMEInfo
& replyMime
,
310 if (!outMIME
.Contains(DateTag
))
311 outMIME
.SetAt(DateTag
, PTime().AsString());
313 if (!userAgentName
&& !outMIME
.Contains(UserAgentTag
))
314 outMIME
.SetAt(UserAgentTag
, userAgentName
);
317 outMIME
.SetAt(ConnectionTag
, KeepAliveTag
);
319 for (PINDEX retry
= 0; retry
< 3; retry
++) {
320 if (!AssureConnect(url
, outMIME
))
323 if (!WriteCommand(cmdName
, url
.AsString(PURL::URIOnly
), outMIME
, dataBody
)) {
324 lastResponseCode
= -1;
325 lastResponseInfo
= GetErrorText(LastWriteError
);
329 // If not persisting need to shut down write so other end stops reading
331 Shutdown(ShutdownWrite
);
333 // Await a response, if all OK exit loop
334 if (ReadResponse(replyMime
))
337 // If not persisting, we have no oppurtunity to write again, just error out
341 // If have had a failure to read a response but there was no error then
342 // we have a shutdown socket probably due to a lack of persistence so ...
343 if (GetErrorCode(LastReadError
) != NoError
)
346 // ... we close the channel and allow AssureConnet() to reopen it.
350 return lastResponseCode
;
354 BOOL
PHTTPClient::WriteCommand(Commands cmd
,
357 const PString
& dataBody
)
359 return WriteCommand(commandNames
[cmd
], url
, outMIME
, dataBody
);
363 BOOL
PHTTPClient::WriteCommand(const PString
& cmdName
,
366 const PString
& dataBody
)
368 PINDEX len
= dataBody
.GetSize()-1;
369 if (!outMIME
.Contains(ContentLengthTag
))
370 outMIME
.SetInteger(ContentLengthTag
, len
);
372 if (cmdName
.IsEmpty())
377 *this << ' ' << url
<< " HTTP/1.1\r\n"
378 << setfill('\r') << outMIME
;
380 return Write((const char *)dataBody
, len
);
384 BOOL
PHTTPClient::ReadResponse(PMIMEInfo
& replyMIME
)
386 PString http
= ReadString(7);
390 if (http
.Find("HTTP/") == P_MAX_INDEX
) {
391 lastResponseCode
= PHTTP::RequestOK
;
392 lastResponseInfo
= "HTTP/0.9";
398 else if (http
[0] == '\r' && http
[1] == '\n')
401 if (PHTTP::ReadResponse())
402 if (replyMIME
.Read(*this))
406 lastResponseCode
= -1;
407 if (GetErrorCode(LastReadError
) != NoError
)
408 lastResponseInfo
= GetErrorText(LastReadError
);
410 lastResponseInfo
= "Premature shutdown";
411 SetErrorValues(ProtocolFailure
, 0, LastReadError
);
418 BOOL
PHTTPClient::ReadContentBody(PMIMEInfo
& replyMIME
, PString
& body
)
420 BOOL ok
= InternalReadContentBody(replyMIME
, body
);
421 body
.SetSize(body
.GetSize()+1);
426 BOOL
PHTTPClient::ReadContentBody(PMIMEInfo
& replyMIME
, PBYTEArray
& body
)
428 return InternalReadContentBody(replyMIME
, body
);
432 BOOL
PHTTPClient::InternalReadContentBody(PMIMEInfo
& replyMIME
, PAbstractArray
& body
)
434 PCaselessString encoding
= replyMIME(TransferEncodingTag
);
436 if (encoding
!= ChunkedTag
) {
437 if (replyMIME
.Contains(ContentLengthTag
)) {
438 PINDEX length
= replyMIME
.GetInteger(ContentLengthTag
);
439 body
.SetSize(length
);
440 return ReadBlock(body
.GetPointer(), length
);
443 if (!(encoding
.IsEmpty())) {
444 lastResponseCode
= -1;
445 lastResponseInfo
= "Unknown Transfer-Encoding extension";
449 // Must be raw, read to end file variety
450 static const PINDEX ChunkSize
= 2048;
451 PINDEX bytesRead
= 0;
452 while (ReadBlock((char *)body
.GetPointer(bytesRead
+ChunkSize
)+bytesRead
, ChunkSize
))
453 bytesRead
+= GetLastReadCount();
455 body
.SetSize(bytesRead
+ GetLastReadCount());
456 return GetErrorCode(LastReadError
) == NoError
;
459 // HTTP1.1 chunked format
460 PINDEX bytesRead
= 0;
462 // Read chunk length line
463 PString chunkLengthLine
;
464 if (!ReadLine(chunkLengthLine
))
467 // A zero length chunk is end of output
468 PINDEX chunkLength
= chunkLengthLine
.AsUnsigned(16);
469 if (chunkLength
== 0)
473 if (!ReadBlock((char *)body
.GetPointer(bytesRead
+chunkLength
)+bytesRead
, chunkLength
))
475 bytesRead
+= chunkLength
;
477 // Read the trailing CRLF
478 if (!ReadLine(chunkLengthLine
))
485 if (!ReadLine(footer
))
487 } while (replyMIME
.AddMIME(footer
));
493 BOOL
PHTTPClient::GetTextDocument(const PURL
& url
,
497 PMIMEInfo outMIME
, replyMIME
;
498 if (!GetDocument(url
, outMIME
, replyMIME
, persist
))
501 return ReadContentBody(replyMIME
, document
);
505 BOOL
PHTTPClient::GetDocument(const PURL
& url
,
507 PMIMEInfo
& replyMIME
,
510 return ExecuteCommand(GET
, url
, outMIME
, PString(), replyMIME
, persist
) == RequestOK
;
514 BOOL
PHTTPClient::GetHeader(const PURL
& url
,
516 PMIMEInfo
& replyMIME
,
519 return ExecuteCommand(HEAD
, url
, outMIME
, PString(), replyMIME
, persist
) == RequestOK
;
523 BOOL
PHTTPClient::PostData(const PURL
& url
,
525 const PString
& data
,
526 PMIMEInfo
& replyMIME
,
529 PString dataBody
= data
;
530 if (!outMIME
.Contains(ContentTypeTag
)) {
531 outMIME
.SetAt(ContentTypeTag
, "application/x-www-form-urlencoded");
532 dataBody
+= "\r\n"; // Add CRLF for compatibility with some CGI servers.
535 return ExecuteCommand(POST
, url
, outMIME
, data
, replyMIME
, persist
) == RequestOK
;
539 BOOL
PHTTPClient::PostData(const PURL
& url
,
541 const PString
& data
,
542 PMIMEInfo
& replyMIME
,
546 if (!PostData(url
, outMIME
, data
, replyMIME
, persist
))
549 return ReadContentBody(replyMIME
, body
);
553 BOOL
PHTTPClient::AssureConnect(const PURL
& url
, PMIMEInfo
& outMIME
)
555 PString host
= url
.GetHostName();
557 // Is not open or other end shut down, restablish connection
559 if (host
.IsEmpty()) {
560 lastResponseCode
= BadRequest
;
561 lastResponseInfo
= "No host specified";
562 return SetErrorValues(ProtocolFailure
, 0, LastReadError
);
566 if (url
.GetScheme() == "https") {
567 PTCPSocket
* tcp
= new PTCPSocket(url
.GetPort());
568 tcp
->SetReadTimeout(readTimeout
);
569 if (!tcp
->Connect(host
)) {
570 lastResponseCode
= -2;
571 lastResponseInfo
= tcp
->GetErrorText();
576 PSSLChannel
* ssl
= new PSSLChannel
;
577 if (!ssl
->Connect(tcp
)) {
578 lastResponseCode
= -2;
579 lastResponseInfo
= ssl
->GetErrorText();
585 lastResponseCode
= -2;
586 lastResponseInfo
= GetErrorText();
593 if (!Connect(host
, url
.GetPort())) {
594 lastResponseCode
= -2;
595 lastResponseInfo
= GetErrorText();
600 // Have connection, so fill in the required MIME fields
601 static char HostTag
[] = "Host";
602 if (!outMIME
.Contains(HostTag
)) {
604 outMIME
.SetAt(HostTag
, host
);
606 PIPSocket
* sock
= GetSocket();
608 outMIME
.SetAt(HostTag
, sock
->GetHostName());
616 // End Of File ///////////////////////////////////////////////////////////////