Added a parameter to semaphore constructor to avoid ambiguity
[pwlib.git] / src / ptclib / http.cxx
blob10a5f90183292d6188c06f19b50d884f72e3da2f
1 /*
2 * http.cxx
4 * HTTP ancestor class and common 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.96 2004/01/17 17:44:17 csoutheren
28 * Changed to use PString::MakeEmpty
30 * Revision 1.95 2003/11/18 09:22:17 csoutheren
31 * Fixed problems with PURL::OpenBrowser, thanks to David Parr
33 * Revision 1.94 2003/08/27 03:37:45 dereksmithies
34 * Fix initialization of pathStr so it really is empty. BIG thanks to Diego Tartara.
36 * Revision 1.93 2003/07/22 03:26:10 csoutheren
37 * Fixed problem with parsing default H323 addresses
39 * Revision 1.92 2003/06/23 15:31:40 ykiryanov
40 * Slightly changed call to ShellExecuteEx to make compatible with Win32
42 * Revision 1.91 2003/06/23 14:31:33 ykiryanov
43 * Modified for WinCE - used ShellExecuteEx instead of ShellExecute
45 * Revision 1.90 2003/06/05 00:15:54 rjongbloed
46 * Fixed callto bug created by previous patch.
48 * Revision 1.89 2003/06/04 01:42:05 rjongbloed
49 * Fixed h323 scheme, does not have a "password" field.
51 * Revision 1.88 2003/06/02 02:46:45 rjongbloed
52 * Fixed issue with callto URL parsing incorrect username field.
53 * Added automatic removal of illegal (though common) "//" in callto URL.
55 * Revision 1.87 2003/05/05 07:30:17 craigs
56 * Fixed problem with URLs that do not specify schemes
58 * Revision 1.86 2003/05/02 13:50:23 craigs
59 * Fixed a problem with callto:localhost
61 * Revision 1.85 2003/05/02 13:20:33 craigs
62 * Fixed callto problems
64 * Revision 1.84 2003/04/28 04:41:22 robertj
65 * Changed URL parsing so if a default scheme is present then explicit scheme
66 * must be "known" to avoid ambiguity with host:port parsing.
68 * Revision 1.83 2003/04/10 00:13:56 robertj
69 * Fixed correct decoding of user/password/host/port field, for non h323 schemes.
71 * Revision 1.82 2003/04/08 06:28:14 craigs
72 * Fixed introduced problem with HTTP server mistaking relative URLs for proxy requests
74 * Revision 1.81 2003/04/04 08:03:55 robertj
75 * Fixed special case of h323 URL default port changing depending on
76 * if it the host is an endpoint or gatekeeper.
78 * Revision 1.80 2003/04/04 05:18:08 robertj
79 * Added "callto", "tel" and fixed "h323" URL types.
81 * Revision 1.79 2002/12/02 00:17:03 robertj
82 * Fixed URL parsing/display problems with non-path URL type eg mailto
84 * Revision 1.78 2002/11/22 06:16:49 robertj
85 * Fixed usage of URI (relative http/https URL).
87 * Revision 1.77 2002/11/20 02:10:56 robertj
88 * Fixed some more realtive/absolute path issues.
90 * Revision 1.76 2002/11/20 01:01:49 robertj
91 * Fixed GNU compatibility
93 * Revision 1.75 2002/11/20 00:49:37 robertj
94 * Fixed correct interpretation of url re double slashes as per latest RFC,
95 * including file: mapping and relative paths. Probably still more to do.
97 * Revision 1.74 2002/11/19 22:45:03 robertj
98 * Fixed support for file: scheme under unix
100 * Revision 1.73 2002/11/19 10:36:50 robertj
101 * Added functions to set anf get "file:" URL. as PFilePath and do the right
102 * things with platform dependent directory components.
104 * Revision 1.72 2002/11/06 22:47:25 robertj
105 * Fixed header comment (copyright etc)
107 * Revision 1.71 2002/09/23 07:17:24 robertj
108 * Changes to allow winsock2 to be included.
110 * Revision 1.70 2002/08/28 08:06:11 craigs
111 * Fixed problem (again) with file:// URLs
113 * Revision 1.69 2002/08/28 05:11:23 craigs
114 * Fixed problem with file:// URLs
116 * Revision 1.68 2002/05/02 05:11:29 craigs
117 * Fixed problem with not translating + chars in URL query parameters
119 * Revision 1.67 2002/03/19 23:39:57 robertj
120 * Fixed string output to include PathOnly variant, lost in previous mod.
122 * Revision 1.66 2002/03/19 23:24:08 robertj
123 * Fixed problems with backward compatibility on parameters processing.
125 * Revision 1.65 2002/03/18 05:02:27 robertj
126 * Added functions to set component parts of URL.
127 * Fixed output of parameters when more than one ';' involved.
129 * Revision 1.64 2001/11/09 05:46:14 robertj
130 * Removed double slash on sip URL.
131 * Fixed extra : if have username but no password.
132 * Added h323: scheme
134 * Revision 1.63 2001/11/08 00:32:49 robertj
135 * Added parsing of ';' based parameter fields into string dictionary if there are multiple parameters, with '=' values.
137 * Revision 1.62 2001/10/31 01:33:07 robertj
138 * Added extra const for constant HTTP tag name strings.
140 * Revision 1.61 2001/10/03 00:26:34 robertj
141 * Upgraded client to HTTP/1.1 and for chunked mode entity bodies.
143 * Revision 1.60 2001/09/28 00:45:42 robertj
144 * Broke out internal static function for unstranslating URL strings.
146 * Revision 1.59 2001/07/16 00:43:06 craigs
147 * Added ability to parse other transport URLs
149 * Revision 1.58 2000/05/02 08:29:07 craigs
150 * Removed "memory leaks" caused by brain-dead GNU linker
152 * Revision 1.57 1999/05/11 12:24:18 robertj
153 * Fixed URL parser so leading blanks are ignored.
155 * Revision 1.56 1999/05/04 15:26:01 robertj
156 * Improved HTTP/1.1 compatibility (pass through user commands).
157 * Fixed problems with quicktime installer.
159 * Revision 1.55 1999/04/21 01:56:13 robertj
160 * Fixed problem with escape codes greater that %80
162 * Revision 1.54 1999/01/16 12:45:54 robertj
163 * Added RTSP schemes to URL's
165 * Revision 1.53 1998/11/30 05:38:15 robertj
166 * Moved PURL::Open() code to .cxx file to avoid linking unused code.
168 * Revision 1.52 1998/11/30 04:51:53 robertj
169 * New directory structure
171 * Revision 1.51 1998/09/23 06:22:07 robertj
172 * Added open source copyright license.
174 * Revision 1.50 1998/02/03 10:02:34 robertj
175 * Added ability to get scheme, host and port from URL as a string.
177 * Revision 1.49 1998/02/03 06:27:26 robertj
178 * Fixed URL encoding to be closer to RFC
180 * Revision 1.48 1998/01/26 02:49:16 robertj
181 * GNU support.
183 * Revision 1.47 1997/11/10 12:40:20 robertj
184 * Fixed illegal character set for URL's.
186 * Revision 1.46 1997/07/14 11:47:10 robertj
187 * Added "const" to numerous variables.
189 * Revision 1.45 1997/07/12 09:45:01 robertj
190 * Fixed bug when URL has + sign in somthing other than parameters.
192 * Revision 1.44 1997/06/06 08:54:47 robertj
193 * Allowed username/password on http scheme URL.
195 * Revision 1.43 1997/04/06 07:46:09 robertj
196 * Fixed bug where URL has more than special character ('?', '#' etc).
198 * Revision 1.42 1997/03/28 04:40:24 robertj
199 * Added tags for cookies.
201 * Revision 1.41 1997/03/18 22:03:44 robertj
202 * Fixed bug that incorrectly parses URL with double slashes.
204 * Revision 1.40 1997/02/14 13:55:44 robertj
205 * Fixed bug in URL for reproducing fields with special characters, must be escaped and weren't.
207 * Revision 1.39 1997/01/12 04:15:21 robertj
208 * Globalised MIME tag strings.
210 * Revision 1.38 1996/09/14 13:09:28 robertj
211 * Major upgrade:
212 * rearranged sockets to help support IPX.
213 * added indirect channel class and moved all protocols to descend from it,
214 * separating the protocol from the low level byte transport.
216 * Revision 1.37 1996/08/25 09:37:41 robertj
217 * Added function to detect "local" host name.
218 * Fixed printing of trailing '/' in empty URL, is distinction between with and without.
220 * Revision 1.36 1996/08/22 13:22:26 robertj
221 * Fixed bug in print of URLs, extra @ signs.
223 * Revision 1.35 1996/08/19 13:42:40 robertj
224 * Fixed errors in URL parsing and display.
225 * Fixed "Forbidden" problem out of HTTP authorisation system.
226 * Fixed authorisation so if have no user/password on basic authentication, does not require it.
228 * Revision 1.34 1996/07/27 04:13:47 robertj
229 * Fixed use of HTTP proxy on non-persistent connections.
231 * Revision 1.33 1996/07/15 10:37:20 robertj
232 * Improved proxy "self" detection (especially localhost).
234 * Revision 1.32 1996/06/28 13:20:24 robertj
235 * Modified HTTPAuthority so gets PHTTPReqest (mainly for URL) passed in.
236 * Moved HTTP form resource to another compilation module.
237 * Fixed memory leak in POST command.
239 * Revision 1.31 1996/06/10 10:00:00 robertj
240 * Added global function for query parameters parsing.
242 * Revision 1.30 1996/06/07 13:52:23 robertj
243 * Added PUT to HTTP proxy FTP. Necessitating redisign of entity body processing.
245 * Revision 1.29 1996/06/05 12:33:04 robertj
246 * Fixed bug in parsing URL with no path, is NOT absolute!
248 * Revision 1.28 1996/05/30 10:07:26 robertj
249 * Fixed bug in version number checking of return code compatibility.
251 * Revision 1.27 1996/05/26 03:46:42 robertj
252 * Compatibility to GNU 2.7.x
254 * Revision 1.26 1996/05/23 10:02:13 robertj
255 * Added common function for GET and HEAD commands.
256 * Fixed status codes to be the actual status code instead of sequential enum.
257 * This fixed some problems with proxy pass through of status codes.
258 * Fixed bug in URL parsing of username and passwords.
260 * Revision 1.19.1.1 1996/04/17 11:08:22 craigs
261 * New version by craig pending confirmation by robert
263 * Revision 1.19 1996/04/05 01:46:30 robertj
264 * Assured PSocket::Write always writes the number of bytes specified, no longer need write loops.
265 * Added workaraound for NT Netscape Navigator bug with persistent connections.
267 * Revision 1.18 1996/03/31 09:05:07 robertj
268 * HTTP 1.1 upgrade.
270 * Revision 1.17 1996/03/17 05:48:07 robertj
271 * Fixed host name print out of URLs.
272 * Added hit count to PHTTPResource.
274 * Revision 1.16 1996/03/16 05:00:26 robertj
275 * Added ParseReponse() for splitting reponse line into code and info.
276 * Added client side support for HTTP socket.
277 * Added hooks for proxy support in HTTP socket.
278 * Added translation type to TranslateString() to accommodate query variables.
279 * Defaulted scheme field in URL to "http".
280 * Inhibited output of port field on string conversion of URL according to scheme.
282 * Revision 1.15 1996/03/11 10:29:50 robertj
283 * Fixed bug in help image HTML.
285 * Revision 1.14 1996/03/10 13:15:24 robertj
286 * Redesign to make resources thread safe.
288 * Revision 1.13 1996/03/02 03:27:37 robertj
289 * Added function to translate a string to a form suitable for inclusion in a URL.
290 * Added radio button and selection boxes to HTTP form resource.
291 * Fixed bug in URL parsing, losing first / if hostname specified.
293 * Revision 1.12 1996/02/25 11:14:24 robertj
294 * Radio button support for forms.
296 * Revision 1.11 1996/02/25 03:10:34 robertj
297 * Removed pass through HTTP resource.
298 * Fixed PHTTPConfig resource to use correct name for config key.
300 * Revision 1.10 1996/02/19 13:48:28 robertj
301 * Put multiple uses of literal strings into const variables.
302 * Fixed URL parsing so that the unmangling of strings occurs correctly.
303 * Moved nested classes from PHTTPForm.
304 * Added overwrite option to AddResource().
305 * Added get/set string to PHTTPString resource.
307 * Revision 1.9 1996/02/13 13:09:17 robertj
308 * Added extra parameters to callback function in PHTTPResources, required
309 * by descendants to make informed decisions on data being loaded.
311 * Revision 1.8 1996/02/08 12:26:29 robertj
312 * Redesign of resource callback mechanism.
313 * Added new resource types for HTML data entry forms.
315 * Revision 1.7 1996/02/03 11:33:19 robertj
316 * Changed RadCmd() so can distinguish between I/O error and unknown command.
318 * Revision 1.6 1996/02/03 11:11:49 robertj
319 * Numerous bug fixes.
320 * Added expiry date and ismodifiedsince support.
322 * Revision 1.5 1996/01/30 23:32:40 robertj
323 * Added single .
325 * Revision 1.4 1996/01/28 14:19:09 robertj
326 * Split HTML into separate source file.
327 * Beginning of pass through resource type.
328 * Changed PCharArray in OnLoadData to PString for convenience in mangling data.
329 * Made PHTTPSpace return standard page on selection of partial path.
331 * Revision 1.3 1996/01/28 02:49:16 robertj
332 * Further implementation.
334 * Revision 1.2 1996/01/26 02:24:30 robertj
335 * Further implemetation.
337 * Revision 1.1 1996/01/23 13:04:32 robertj
338 * Initial revision
342 #ifdef __GNUC__
343 #pragma implementation "http.h"
344 #pragma implementation "url.h"
345 #endif
347 #include <ptlib.h>
348 #include <ptlib/sockets.h>
349 #include <ptclib/http.h>
350 #include <ptclib/url.h>
352 #include <ctype.h>
354 #ifdef WIN32
355 #include <shellapi.h>
356 #endif
359 // RFC 1738
360 // http://host:port/path...
361 // https://host:port/path....
362 // gopher://host:port
363 // wais://host:port
364 // nntp://host:port
365 // prospero://host:port
366 // ftp://user:password@host:port/path...
367 // telnet://user:password@host:port
368 // file://hostname/path...
370 // mailto:user@hostname
371 // news:string
373 #define DEFAULT_FTP_PORT 21
374 #define DEFAULT_TELNET_PORT 23
375 #define DEFAULT_GOPHER_PORT 70
376 #define DEFAULT_HTTP_PORT 80
377 #define DEFAULT_NNTP_PORT 119
378 #define DEFAULT_WAIS_PORT 210
379 #define DEFAULT_HTTPS_PORT 443
380 #define DEFAULT_RTSP_PORT 554
381 #define DEFAULT_RTSPU_PORT 554
382 #define DEFAULT_PROSPERO_PORT 1525
383 #define DEFAULT_H323_PORT 1720
384 #define DEFAULT_H323RAS_PORT 1719
385 #define DEFAULT_SIP_PORT 5060
388 struct schemeStruct {
389 const char * name;
390 BOOL hasUsername;
391 BOOL hasPassword;
392 BOOL hasHostPort;
393 BOOL defaultToUserIfNoAt;
394 BOOL defaultHostToLocal;
395 BOOL hasQuery;
396 BOOL hasParameters;
397 BOOL hasFragments;
398 BOOL hasPath;
399 BOOL relativeImpliesScheme;
400 WORD defaultPort;
403 #define DEFAULT_SCHEME 0
404 #define FILE_SCHEME 1
406 static schemeStruct const SchemeTable[] = {
407 // scheme user pass host @def defhost query params frags path rel port
408 { "http", TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, DEFAULT_HTTP_PORT }, // Must be first
409 { "file", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, 0 }, // Must be second
410 { "https", FALSE, FALSE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, DEFAULT_HTTPS_PORT },
411 { "gopher", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_GOPHER_PORT },
412 { "wais", FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_WAIS_PORT },
413 { "nntp", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_NNTP_PORT },
414 { "prospero", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_PROSPERO_PORT },
415 { "rtsp", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_RTSP_PORT },
416 { "rtspu", FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_RTSPU_PORT },
417 { "ftp", TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_FTP_PORT },
418 { "telnet", TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, DEFAULT_TELNET_PORT },
419 { "mailto", FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, 0 },
420 { "news", FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, 0 },
421 { "h323", TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, DEFAULT_H323_PORT },
422 { "sip", TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, DEFAULT_SIP_PORT },
423 { "tel", FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0 },
424 { "fax", FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0 },
425 { "callto", FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0 },
426 { NULL, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0 }
429 static const schemeStruct * GetSchemeInfo(const PCaselessString & scheme)
431 PINDEX i;
432 for (i = 0; SchemeTable[i].name != NULL; i++) {
433 if (scheme == SchemeTable[i].name)
434 return &SchemeTable[i];
436 return NULL;
439 //////////////////////////////////////////////////////////////////////////////
440 // PURL
442 PURL::PURL()
443 : scheme(SchemeTable[DEFAULT_SCHEME].name),
444 port(0),
445 relativePath(FALSE)
450 PURL::PURL(const char * str, const char * defaultScheme)
452 Parse(str, defaultScheme);
456 PURL::PURL(const PString & str, const char * defaultScheme)
458 Parse(str, defaultScheme);
462 PURL::PURL(const PFilePath & filePath)
463 : scheme(SchemeTable[FILE_SCHEME].name),
464 port(0),
465 relativePath(FALSE)
467 PStringArray pathArray = filePath.GetDirectory().GetPath();
468 hostname = pathArray[0];
470 PINDEX i;
471 for (i = 1; i < pathArray.GetSize(); i++)
472 pathArray[i-1] = pathArray[i];
473 pathArray[i-1] = filePath.GetFileName();
475 SetPath(pathArray);
479 PObject::Comparison PURL::Compare(const PObject & obj) const
481 PAssert(obj.IsDescendant(PURL::Class()), PInvalidCast);
482 return urlString.Compare(((const PURL &)obj).urlString);
486 PINDEX PURL::HashFunction() const
488 return urlString.HashFunction();
492 void PURL::PrintOn(ostream & stream) const
494 stream << urlString;
498 void PURL::ReadFrom(istream & stream)
500 PString s;
501 stream >> s;
502 Parse(s);
506 PString PURL::TranslateString(const PString & str, TranslationType type)
508 PString xlat = str;
510 PString safeChars = "abcdefghijklmnopqrstuvwxyz"
511 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
512 "0123456789$-_.!*'(),";
513 switch (type) {
514 case LoginTranslation :
515 safeChars += "+;?&=";
516 break;
518 case PathTranslation :
519 safeChars += "+:@&=";
520 break;
522 case QueryTranslation :
523 safeChars += ":@";
525 PINDEX pos = (PINDEX)-1;
526 while ((pos += 1+strspn(&xlat[pos+1], safeChars)) < xlat.GetLength())
527 xlat.Splice(psprintf("%%%02X", (BYTE)xlat[pos]), pos, 1);
529 if (type == QueryTranslation) {
530 PINDEX space = (PINDEX)-1;
531 while ((space = xlat.Find(' ', space+1)) != P_MAX_INDEX)
532 xlat[space] = '+';
535 return xlat;
539 PString PURL::UntranslateString(const PString & str, TranslationType type)
541 PString xlat = str;
542 xlat.MakeUnique();
544 PINDEX pos;
545 if (type == PURL::QueryTranslation) {
546 pos = (PINDEX)-1;
547 while ((pos = xlat.Find('+', pos+1)) != P_MAX_INDEX)
548 xlat[pos] = ' ';
551 pos = (PINDEX)-1;
552 while ((pos = xlat.Find('%', pos+1)) != P_MAX_INDEX) {
553 int digit1 = xlat[pos+1];
554 int digit2 = xlat[pos+2];
555 if (isxdigit(digit1) && isxdigit(digit2)) {
556 xlat[pos] = (char)(
557 (isdigit(digit2) ? (digit2-'0') : (toupper(digit2)-'A'+10)) +
558 ((isdigit(digit1) ? (digit1-'0') : (toupper(digit1)-'A'+10)) << 4));
559 xlat.Delete(pos+1, 2);
563 return xlat;
567 static void SplitVars(const PString & str, PStringToString & vars, char sep1, char sep2)
569 PINDEX sep1prev = 0;
570 do {
571 PINDEX sep1next = str.Find(sep1, sep1prev);
572 if (sep1next == P_MAX_INDEX)
573 sep1next--; // Implicit assumption string is not a couple of gigabytes long ...
575 PINDEX sep2pos = str.Find(sep2, sep1prev);
576 if (sep2pos > sep1next)
577 sep2pos = sep1next;
579 PCaselessString key = PURL::UntranslateString(str(sep1prev, sep2pos-1), PURL::QueryTranslation);
580 if (!key) {
581 PString data = PURL::UntranslateString(str(sep2pos+1, sep1next-1), PURL::QueryTranslation);
583 if (vars.Contains(key))
584 vars.SetAt(key, vars[key] + ',' + data);
585 else
586 vars.SetAt(key, data);
589 sep1prev = sep1next+1;
590 } while (sep1prev != P_MAX_INDEX);
594 void PURL::SplitQueryVars(const PString & queryStr, PStringToString & queryVars)
596 SplitVars(queryStr, queryVars, '&', '=');
600 void PURL::Parse(const char * cstr, const char * defaultScheme)
602 hostname = PCaselessString();
604 pathStr.MakeEmpty();
605 username.MakeEmpty();
606 password.MakeEmpty();
607 fragment.MakeEmpty();
609 path.SetSize(0);
610 queryVars.RemoveAll();
611 port = 0;
612 relativePath = FALSE;
614 // copy the string so we can take bits off it
615 while (isspace(*cstr))
616 cstr++;
617 PString url = cstr;
619 PINDEX pos;
621 // get information which tells us how to parse URL for this
622 // particular scheme
623 const schemeStruct * schemeInfo = NULL;
625 // determine if the URL has a scheme
626 scheme.MakeEmpty();
628 // Character set as per RFC2396
629 pos = 0;
630 while (isalnum(url[pos]) || url[pos] == '+' || url[pos] == '-' || url[pos] == '.')
631 pos++;
633 // Have explicit scheme
634 if (url[pos] == ':') {
635 schemeInfo = GetSchemeInfo(url.Left(pos));
636 if (schemeInfo == NULL && defaultScheme == NULL)
637 schemeInfo = &SchemeTable[PARRAYSIZE(SchemeTable)-1];
638 if (schemeInfo != NULL)
639 url.Delete(0, pos+1);
642 // if we could not match a scheme, then use the default scheme
643 if (schemeInfo == NULL && defaultScheme != NULL)
644 schemeInfo = GetSchemeInfo(defaultScheme);
646 if (schemeInfo == NULL)
647 schemeInfo = &SchemeTable[DEFAULT_SCHEME];
649 scheme = schemeInfo->name;
651 // Super special case!
652 if (scheme *= "callto") {
653 // Actually not part of MS spec, but a lot of people put in the // into
654 // the URL, so we take it out of it is there.
655 if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/')
656 url.Delete(0, 2);
658 // For some bizarre reason callto uses + instead of ; for paramters
659 // We do a loop so that phone numbers of the form +61243654666 still work
660 do {
661 pos = url.Find('+');
662 } while (pos != P_MAX_INDEX && isdigit(url[pos+1]));
664 if (pos != P_MAX_INDEX) {
665 SplitVars(url(pos+1, P_MAX_INDEX), paramVars, '+', '=');
666 url.Delete(pos, P_MAX_INDEX);
669 hostname = paramVars("gateway");
670 if (!hostname)
671 username = UntranslateString(url, LoginTranslation);
672 else {
673 PCaselessString type = paramVars("type");
674 if (type == "directory") {
675 pos = url.Find('/');
676 if (pos == P_MAX_INDEX)
677 username = UntranslateString(url, LoginTranslation);
678 else {
679 hostname = UntranslateString(url.Left(pos), LoginTranslation);
680 username = UntranslateString(url.Mid(pos+1), LoginTranslation);
683 else {
684 // Now look for an @ and split user and host
685 pos = url.Find('@');
686 if (pos != P_MAX_INDEX) {
687 username = UntranslateString(url.Left(pos), LoginTranslation);
688 hostname = UntranslateString(url.Mid(pos+1), LoginTranslation);
690 else {
691 if (type == "ip" || type == "host")
692 hostname = UntranslateString(url, LoginTranslation);
693 else
694 username = UntranslateString(url, LoginTranslation);
699 // Allow for [ipv6] form
700 pos = hostname.Find(']');
701 if (pos == P_MAX_INDEX)
702 pos = 0;
703 pos = hostname.Find(':', pos);
704 if (pos != P_MAX_INDEX) {
705 port = (WORD)hostname.Mid(pos+1).AsUnsigned();
706 hostname.Delete(pos, P_MAX_INDEX);
709 password = paramVars("password");
710 return;
713 // if the URL should have leading slash, then remove it if it has one
714 if (schemeInfo != NULL && schemeInfo->hasHostPort && schemeInfo->hasPath) {
715 if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/')
716 url.Delete(0, 2);
717 else
718 relativePath = TRUE;
721 // parse user/password/host/port
722 if (!relativePath && schemeInfo->hasHostPort) {
723 PString endHostChars;
724 if (schemeInfo->hasPath)
725 endHostChars += '/';
726 if (schemeInfo->hasQuery)
727 endHostChars += '?';
728 if (schemeInfo->hasParameters)
729 endHostChars += ';';
730 if (schemeInfo->hasFragments)
731 endHostChars += '#';
732 if (endHostChars.IsEmpty())
733 pos = P_MAX_INDEX;
734 else
735 pos = url.FindOneOf(endHostChars);
737 PString uphp = url.Left(pos);
738 if (pos != P_MAX_INDEX)
739 url.Delete(0, pos);
740 else
741 url.MakeEmpty();
743 // if the URL is of type UserPasswordHostPort, then parse it
744 if (schemeInfo->hasUsername) {
745 // extract username and password
746 PINDEX pos2 = uphp.Find('@');
747 PINDEX pos3 = P_MAX_INDEX;
748 if (schemeInfo->hasPassword)
749 pos3 = uphp.Find(':');
750 switch (pos2) {
751 case 0 :
752 uphp.Delete(0, 1);
753 break;
755 case P_MAX_INDEX :
756 if (schemeInfo->defaultToUserIfNoAt) {
757 if (pos3 == P_MAX_INDEX)
758 username = UntranslateString(uphp, LoginTranslation);
759 else {
760 username = UntranslateString(uphp.Left(pos3), LoginTranslation);
761 password = UntranslateString(uphp.Mid(pos3+1), LoginTranslation);
763 uphp.MakeEmpty();
765 break;
767 default :
768 if (pos3 > pos2)
769 username = UntranslateString(uphp.Left(pos2), LoginTranslation);
770 else {
771 username = UntranslateString(uphp.Left(pos3), LoginTranslation);
772 password = UntranslateString(uphp(pos3+1, pos2-1), LoginTranslation);
774 uphp.Delete(0, pos2+1);
778 // if the URL does not have a port, then this is the hostname
779 if (schemeInfo->defaultPort == 0)
780 hostname = UntranslateString(uphp, LoginTranslation);
781 else {
782 // determine if the URL has a port number
783 // Allow for [ipv6] form
784 pos = uphp.Find(']');
785 if (pos == P_MAX_INDEX)
786 pos = 0;
787 pos = uphp.Find(':', pos);
788 if (pos == P_MAX_INDEX)
789 hostname = UntranslateString(uphp, LoginTranslation);
790 else {
791 hostname = UntranslateString(uphp.Left(pos), LoginTranslation);
792 port = (WORD)uphp.Mid(pos+1).AsUnsigned();
795 if (hostname.IsEmpty() && schemeInfo->defaultHostToLocal)
796 hostname = PIPSocket::GetHostName();
800 if (schemeInfo->hasQuery) {
801 // chop off any trailing query
802 pos = url.Find('?');
803 if (pos != P_MAX_INDEX) {
804 SplitQueryVars(url(pos+1, P_MAX_INDEX), queryVars);
805 url.Delete(pos, P_MAX_INDEX);
809 if (schemeInfo->hasParameters) {
810 // chop off any trailing parameters
811 pos = url.Find(';');
812 if (pos != P_MAX_INDEX) {
813 SplitVars(url(pos+1, P_MAX_INDEX), paramVars, ';', '=');
814 url.Delete(pos, P_MAX_INDEX);
818 if (schemeInfo->hasFragments) {
819 // chop off any trailing fragment
820 pos = url.Find('#');
821 if (pos != P_MAX_INDEX) {
822 fragment = UntranslateString(url(pos+1, P_MAX_INDEX), PathTranslation);
823 url.Delete(pos, P_MAX_INDEX);
827 if (schemeInfo->hasPath)
828 SetPathStr(url); // the hierarchy is what is left
829 else {
830 // if the rest of the URL isn't a path, then we are finished!
831 pathStr = UntranslateString(url, PathTranslation);
832 Recalculate();
835 if (port == 0 && schemeInfo->defaultPort != 0 && !relativePath) {
836 // Yes another horrible, horrible special case!
837 if (scheme == "h323" && paramVars("type") == "gk")
838 port = DEFAULT_H323RAS_PORT;
839 else
840 port = schemeInfo->defaultPort;
845 PFilePath PURL::AsFilePath() const
847 if (scheme != SchemeTable[FILE_SCHEME].name)
848 return PString::Empty();
850 PStringStream str;
852 if (relativePath) {
853 for (PINDEX i = 0; i < path.GetSize(); i++) {
854 if (i > 0)
855 str << PDIR_SEPARATOR;
856 str << path[i];
859 else {
860 if (hostname != "localhost")
861 str << hostname;
862 for (PINDEX i = 0; i < path.GetSize(); i++)
863 str << PDIR_SEPARATOR << path[i];
866 return str;
870 PString PURL::AsString(UrlFormat fmt) const
872 PINDEX i;
873 PStringStream str;
875 if (fmt == FullURL)
876 return urlString;
878 if (scheme.IsEmpty())
879 return PString::Empty();
881 const schemeStruct * schemeInfo = GetSchemeInfo(scheme);
882 if (schemeInfo == NULL)
883 schemeInfo = &SchemeTable[PARRAYSIZE(SchemeTable)-1];
885 if (fmt == HostPortOnly) {
886 str << scheme << ':';
888 if (relativePath) {
889 if (schemeInfo->relativeImpliesScheme)
890 return PString::Empty();
891 return str;
894 if (schemeInfo->hasPath && schemeInfo->hasHostPort)
895 str << "//";
897 if (schemeInfo->hasUsername) {
898 if (!username) {
899 str << TranslateString(username, LoginTranslation);
900 if (schemeInfo->hasPassword && !password)
901 str << ':' << TranslateString(password, LoginTranslation);
902 str << '@';
906 if (schemeInfo->hasHostPort)
907 str << hostname;
909 if (schemeInfo->defaultPort != 0) {
910 if (port != schemeInfo->defaultPort)
911 str << ':' << port;
914 return str;
917 // URIOnly and PathOnly
918 if (schemeInfo->hasPath) {
919 for (i = 0; i < path.GetSize(); i++) {
920 if (i > 0 || !relativePath)
921 str << '/';
922 str << TranslateString(path[i], PathTranslation);
925 else
926 str << TranslateString(pathStr, PathTranslation);
928 if (fmt == URIOnly) {
929 if (!fragment)
930 str << "#" << TranslateString(fragment, PathTranslation);
932 for (i = 0; i < paramVars.GetSize(); i++) {
933 str << ';' << TranslateString(paramVars.GetKeyAt(i), QueryTranslation);
934 PString data = paramVars.GetDataAt(i);
935 if (!data)
936 str << '=' << TranslateString(data, QueryTranslation);
939 if (!queryVars.IsEmpty())
940 str << '?' << GetQuery();
943 return str;
947 void PURL::SetScheme(const PString & s)
949 scheme = s;
950 Recalculate();
954 void PURL::SetUserName(const PString & u)
956 username = u;
957 Recalculate();
961 void PURL::SetPassword(const PString & p)
963 password = p;
964 Recalculate();
968 void PURL::SetHostName(const PString & h)
970 hostname = h;
971 Recalculate();
975 void PURL::SetPort(WORD newPort)
977 port = newPort;
978 Recalculate();
982 void PURL::SetPathStr(const PString & p)
984 pathStr = p;
986 path = pathStr.Tokenise("/", TRUE);
988 if (path.GetSize() > 0 && path[0].IsEmpty())
989 path.RemoveAt(0);
991 for (PINDEX i = 0; i < path.GetSize(); i++) {
992 path[i] = UntranslateString(path[i], PathTranslation);
993 if (i > 0 && path[i] == ".." && path[i-1] != "..") {
994 path.RemoveAt(i--);
995 path.RemoveAt(i--);
999 Recalculate();
1003 void PURL::SetPath(const PStringArray & p)
1005 path = p;
1007 pathStr.MakeEmpty();
1008 for (PINDEX i = 0; i < path.GetSize(); i++)
1009 pathStr += '/' + path[i];
1011 Recalculate();
1015 PString PURL::GetParameters() const
1017 PStringStream str;
1019 for (PINDEX i = 0; i < paramVars.GetSize(); i++) {
1020 if (i > 0)
1021 str << ';';
1022 str << paramVars.GetKeyAt(i);
1023 PString data = paramVars.GetDataAt(i);
1024 if (!data)
1025 str << '=' << data;
1028 return str;
1032 void PURL::SetParameters(const PString & parameters)
1034 SplitVars(parameters, paramVars, ';', '=');
1035 Recalculate();
1039 void PURL::SetParamVars(const PStringToString & p)
1041 paramVars = p;
1042 Recalculate();
1046 void PURL::SetParamVar(const PString & key, const PString & data)
1048 if (data.IsEmpty())
1049 paramVars.RemoveAt(key);
1050 else
1051 paramVars.SetAt(key, data);
1052 Recalculate();
1056 PString PURL::GetQuery() const
1058 PStringStream str;
1060 for (PINDEX i = 0; i < queryVars.GetSize(); i++) {
1061 if (i > 0)
1062 str << '&';
1063 str << TranslateString(queryVars.GetKeyAt(i), QueryTranslation)
1064 << '='
1065 << TranslateString(queryVars.GetDataAt(i), QueryTranslation);
1068 return str;
1072 void PURL::SetQuery(const PString & queryStr)
1074 SplitQueryVars(queryStr, queryVars);
1075 Recalculate();
1079 void PURL::SetQueryVars(const PStringToString & q)
1081 queryVars = q;
1082 Recalculate();
1086 void PURL::SetQueryVar(const PString & key, const PString & data)
1088 if (data.IsEmpty())
1089 queryVars.RemoveAt(key);
1090 else
1091 queryVars.SetAt(key, data);
1092 Recalculate();
1096 BOOL PURL::OpenBrowser(const PString & url)
1098 #ifdef WIN32
1099 SHELLEXECUTEINFO sei;
1100 ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));
1101 sei.cbSize = sizeof(SHELLEXECUTEINFO);
1102 sei.lpVerb = TEXT("open");
1103 sei.lpFile = url;
1105 if (ShellExecuteEx(&sei) >= 32)
1106 return TRUE;
1108 #ifndef _WIN32_WCE
1109 MessageBox(NULL, "Unable to open page"&url, PProcess::Current().GetName(), MB_TASKMODAL);
1110 #else
1111 USES_CONVERSION;
1112 MessageBox(NULL, _T("Unable to open page"), A2T(PProcess::Current().GetName()), MB_APPLMODAL);
1113 #endif // _WIN32_WCE
1115 #endif // WIN32
1116 return FALSE;
1120 void PURL::Recalculate()
1122 if (scheme.IsEmpty())
1123 scheme = SchemeTable[DEFAULT_SCHEME].name;
1125 urlString = AsString(HostPortOnly) + AsString(URIOnly);
1129 //////////////////////////////////////////////////////////////////////////////
1130 // PHTTP
1132 static char const * const HTTPCommands[PHTTP::NumCommands] = {
1133 // HTTP 1.0 commands
1134 "GET", "HEAD", "POST",
1136 // HTTP 1.1 commands
1137 "PUT", "DELETE", "TRACE", "OPTIONS",
1139 // HTTPS command
1140 "CONNECT"
1144 const char * const PHTTP::AllowTag = "Allow";
1145 const char * const PHTTP::AuthorizationTag = "Authorization";
1146 const char * const PHTTP::ContentEncodingTag = "Content-Encoding";
1147 const char * const PHTTP::ContentLengthTag = "Content-Length";
1148 const char * const PHTTP::ContentTypeTag = "Content-Type";
1149 const char * const PHTTP::DateTag = "Date";
1150 const char * const PHTTP::ExpiresTag = "Expires";
1151 const char * const PHTTP::FromTag = "From";
1152 const char * const PHTTP::IfModifiedSinceTag = "If-Modified-Since";
1153 const char * const PHTTP::LastModifiedTag = "Last-Modified";
1154 const char * const PHTTP::LocationTag = "Location";
1155 const char * const PHTTP::PragmaTag = "Pragma";
1156 const char * const PHTTP::PragmaNoCacheTag = "no-cache";
1157 const char * const PHTTP::RefererTag = "Referer";
1158 const char * const PHTTP::ServerTag = "Server";
1159 const char * const PHTTP::UserAgentTag = "User-Agent";
1160 const char * const PHTTP::WWWAuthenticateTag = "WWW-Authenticate";
1161 const char * const PHTTP::MIMEVersionTag = "MIME-Version";
1162 const char * const PHTTP::ConnectionTag = "Connection";
1163 const char * const PHTTP::KeepAliveTag = "Keep-Alive";
1164 const char * const PHTTP::TransferEncodingTag= "Transfer-Encoding";
1165 const char * const PHTTP::ChunkedTag = "chunked";
1166 const char * const PHTTP::ProxyConnectionTag = "Proxy-Connection";
1167 const char * const PHTTP::ProxyAuthorizationTag = "Proxy-Authorization";
1168 const char * const PHTTP::ProxyAuthenticateTag = "Proxy-Authenticate";
1169 const char * const PHTTP::ForwardedTag = "Forwarded";
1170 const char * const PHTTP::SetCookieTag = "Set-Cookie";
1171 const char * const PHTTP::CookieTag = "Cookie";
1175 PHTTP::PHTTP()
1176 : PInternetProtocol("www 80", NumCommands, HTTPCommands)
1181 PINDEX PHTTP::ParseResponse(const PString & line)
1183 PINDEX endVer = line.Find(' ');
1184 if (endVer == P_MAX_INDEX) {
1185 lastResponseInfo = "Bad response";
1186 lastResponseCode = PHTTP::InternalServerError;
1187 return 0;
1190 lastResponseInfo = line.Left(endVer);
1191 PINDEX endCode = line.Find(' ', endVer+1);
1192 lastResponseCode = line(endVer+1,endCode-1).AsInteger();
1193 if (lastResponseCode == 0)
1194 lastResponseCode = PHTTP::InternalServerError;
1195 lastResponseInfo &= line.Mid(endCode);
1196 return 0;
1200 // End Of File ///////////////////////////////////////////////////////////////