/* * http.cxx * * HTTP ancestor class and common classes. * * Portable Windows Library * * Copyright (c) 1993-2002 Equivalence Pty. Ltd. * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Contributor(s): ______________________________________. * * $Log: http.cxx,v $ * Revision 1.116 2005/11/30 12:47:41 csoutheren * Removed tabs, reformatted some code, and changed tags for Doxygen * * Revision 1.115 2005/04/20 05:19:48 csoutheren * Patch 1185334. Ensure SIP URLs correctly store status of port * Thanks to Ted Szoczei * * Revision 1.114 2005/01/16 20:36:48 csoutheren * Changed URLS to put IP address in [] if contains a ":" * * Revision 1.113 2005/01/04 08:09:41 csoutheren * Fixed Linux configure problems * * Revision 1.112 2004/12/08 00:51:12 csoutheren * Move PURLLegacyScheme to header file to allow external usage * * Revision 1.111 2004/10/23 11:27:24 ykiryanov * Added ifdef _WIN32_WCE for PocketPC 2003 SDK port * * Revision 1.110 2004/08/31 23:40:51 csoutheren * Fixed problem with absolute file paths in URLs * * Revision 1.109 2004/07/14 13:15:45 rjongbloed * Fixed minor bug where a URL is "non-empty" if requires a host but has none. * eg could end up with "sip:" or "http://:80" which are illegal. * * Revision 1.108 2004/07/12 09:17:20 csoutheren * Fixed warnings and errors under Linux * * Revision 1.107 2004/07/06 10:12:52 csoutheren * Added static integer o factory template to assist in ensuring factories are instantiated * * Revision 1.106 2004/06/30 12:17:05 rjongbloed * Rewrite of plug in system to use single global variable for all factories to avoid all sorts * of issues with startup orders and Windows DLL multiple instances. * * Revision 1.105 2004/06/16 07:48:12 csoutheren * Added assert to clarify usage of default scheme * * Revision 1.104 2004/06/03 13:30:58 csoutheren * Renamed INSTANTIATE_FACTORY to avoid potential namespace collisions * Added documentaton on new PINSTANTIATE_FACTORY macro * Added generic form of PINSTANTIATE_FACTORY * * Revision 1.103 2004/06/03 12:47:58 csoutheren * Decomposed PFactory declarations to hopefully avoid problems with Windows DLLs * * Revision 1.102 2004/06/01 07:28:45 csoutheren * Changed URL parsing to use abstract factory code * * Revision 1.101 2004/04/04 00:21:47 csoutheren * FIxed problem with some URL parsing * * Revision 1.100 2004/04/03 08:22:20 csoutheren * Remove pseudo-RTTI and replaced with real RTTI * * Revision 1.99 2004/03/23 05:08:21 csoutheren * Fixed problem with use of ShellExecuteEx function * * Revision 1.98 2004/03/13 06:30:52 rjongbloed * Virtualised parse function. * * Revision 1.97 2004/02/24 11:14:44 rjongbloed * Fixed correct initialisation of some internal variables in URL if parse fails. * * Revision 1.96 2004/01/17 17:44:17 csoutheren * Changed to use PString::MakeEmpty * * Revision 1.95 2003/11/18 09:22:17 csoutheren * Fixed problems with PURL::OpenBrowser, thanks to David Parr * * Revision 1.94 2003/08/27 03:37:45 dereksmithies * Fix initialization of pathStr so it really is empty. BIG thanks to Diego Tartara. * * Revision 1.93 2003/07/22 03:26:10 csoutheren * Fixed problem with parsing default H323 addresses * * Revision 1.92 2003/06/23 15:31:40 ykiryanov * Slightly changed call to ShellExecuteEx to make compatible with Win32 * * Revision 1.91 2003/06/23 14:31:33 ykiryanov * Modified for WinCE - used ShellExecuteEx instead of ShellExecute * * Revision 1.90 2003/06/05 00:15:54 rjongbloed * Fixed callto bug created by previous patch. * * Revision 1.89 2003/06/04 01:42:05 rjongbloed * Fixed h323 scheme, does not have a "password" field. * * Revision 1.88 2003/06/02 02:46:45 rjongbloed * Fixed issue with callto URL parsing incorrect username field. * Added automatic removal of illegal (though common) "//" in callto URL. * * Revision 1.87 2003/05/05 07:30:17 craigs * Fixed problem with URLs that do not specify schemes * * Revision 1.86 2003/05/02 13:50:23 craigs * Fixed a problem with callto:localhost * * Revision 1.85 2003/05/02 13:20:33 craigs * Fixed callto problems * * Revision 1.84 2003/04/28 04:41:22 robertj * Changed URL parsing so if a default scheme is present then explicit scheme * must be "known" to avoid ambiguity with host:port parsing. * * Revision 1.83 2003/04/10 00:13:56 robertj * Fixed correct decoding of user/password/host/port field, for non h323 schemes. * * Revision 1.82 2003/04/08 06:28:14 craigs * Fixed introduced problem with HTTP server mistaking relative URLs for proxy requests * * Revision 1.81 2003/04/04 08:03:55 robertj * Fixed special case of h323 URL default port changing depending on * if it the host is an endpoint or gatekeeper. * * Revision 1.80 2003/04/04 05:18:08 robertj * Added "callto", "tel" and fixed "h323" URL types. * * Revision 1.79 2002/12/02 00:17:03 robertj * Fixed URL parsing/display problems with non-path URL type eg mailto * * Revision 1.78 2002/11/22 06:16:49 robertj * Fixed usage of URI (relative http/https URL). * * Revision 1.77 2002/11/20 02:10:56 robertj * Fixed some more realtive/absolute path issues. * * Revision 1.76 2002/11/20 01:01:49 robertj * Fixed GNU compatibility * * Revision 1.75 2002/11/20 00:49:37 robertj * Fixed correct interpretation of url re double slashes as per latest RFC, * including file: mapping and relative paths. Probably still more to do. * * Revision 1.74 2002/11/19 22:45:03 robertj * Fixed support for file: scheme under unix * * Revision 1.73 2002/11/19 10:36:50 robertj * Added functions to set anf get "file:" URL. as PFilePath and do the right * things with platform dependent directory components. * * Revision 1.72 2002/11/06 22:47:25 robertj * Fixed header comment (copyright etc) * * Revision 1.71 2002/09/23 07:17:24 robertj * Changes to allow winsock2 to be included. * * Revision 1.70 2002/08/28 08:06:11 craigs * Fixed problem (again) with file:// URLs * * Revision 1.69 2002/08/28 05:11:23 craigs * Fixed problem with file:// URLs * * Revision 1.68 2002/05/02 05:11:29 craigs * Fixed problem with not translating + chars in URL query parameters * * Revision 1.67 2002/03/19 23:39:57 robertj * Fixed string output to include PathOnly variant, lost in previous mod. * * Revision 1.66 2002/03/19 23:24:08 robertj * Fixed problems with backward compatibility on parameters processing. * * Revision 1.65 2002/03/18 05:02:27 robertj * Added functions to set component parts of URL. * Fixed output of parameters when more than one ';' involved. * * Revision 1.64 2001/11/09 05:46:14 robertj * Removed double slash on sip URL. * Fixed extra : if have username but no password. * Added h323: scheme * * Revision 1.63 2001/11/08 00:32:49 robertj * Added parsing of ';' based parameter fields into string dictionary if there are multiple parameters, with '=' values. * * Revision 1.62 2001/10/31 01:33:07 robertj * Added extra const for constant HTTP tag name strings. * * Revision 1.61 2001/10/03 00:26:34 robertj * Upgraded client to HTTP/1.1 and for chunked mode entity bodies. * * Revision 1.60 2001/09/28 00:45:42 robertj * Broke out internal static function for unstranslating URL strings. * * Revision 1.59 2001/07/16 00:43:06 craigs * Added ability to parse other transport URLs * * Revision 1.58 2000/05/02 08:29:07 craigs * Removed "memory leaks" caused by brain-dead GNU linker * * Revision 1.57 1999/05/11 12:24:18 robertj * Fixed URL parser so leading blanks are ignored. * * Revision 1.56 1999/05/04 15:26:01 robertj * Improved HTTP/1.1 compatibility (pass through user commands). * Fixed problems with quicktime installer. * * Revision 1.55 1999/04/21 01:56:13 robertj * Fixed problem with escape codes greater that %80 * * Revision 1.54 1999/01/16 12:45:54 robertj * Added RTSP schemes to URL's * * Revision 1.53 1998/11/30 05:38:15 robertj * Moved PURL::Open() code to .cxx file to avoid linking unused code. * * Revision 1.52 1998/11/30 04:51:53 robertj * New directory structure * * Revision 1.51 1998/09/23 06:22:07 robertj * Added open source copyright license. * * Revision 1.50 1998/02/03 10:02:34 robertj * Added ability to get scheme, host and port from URL as a string. * * Revision 1.49 1998/02/03 06:27:26 robertj * Fixed URL encoding to be closer to RFC * * Revision 1.48 1998/01/26 02:49:16 robertj * GNU support. * * Revision 1.47 1997/11/10 12:40:20 robertj * Fixed illegal character set for URL's. * * Revision 1.46 1997/07/14 11:47:10 robertj * Added "const" to numerous variables. * * Revision 1.45 1997/07/12 09:45:01 robertj * Fixed bug when URL has + sign in somthing other than parameters. * * Revision 1.44 1997/06/06 08:54:47 robertj * Allowed username/password on http scheme URL. * * Revision 1.43 1997/04/06 07:46:09 robertj * Fixed bug where URL has more than special character ('?', '#' etc). * * Revision 1.42 1997/03/28 04:40:24 robertj * Added tags for cookies. * * Revision 1.41 1997/03/18 22:03:44 robertj * Fixed bug that incorrectly parses URL with double slashes. * * Revision 1.40 1997/02/14 13:55:44 robertj * Fixed bug in URL for reproducing fields with special characters, must be escaped and weren't. * * Revision 1.39 1997/01/12 04:15:21 robertj * Globalised MIME tag strings. * * Revision 1.38 1996/09/14 13:09:28 robertj * Major upgrade: * rearranged sockets to help support IPX. * added indirect channel class and moved all protocols to descend from it, * separating the protocol from the low level byte transport. * * Revision 1.37 1996/08/25 09:37:41 robertj * Added function to detect "local" host name. * Fixed printing of trailing '/' in empty URL, is distinction between with and without. * * Revision 1.36 1996/08/22 13:22:26 robertj * Fixed bug in print of URLs, extra @ signs. * * Revision 1.35 1996/08/19 13:42:40 robertj * Fixed errors in URL parsing and display. * Fixed "Forbidden" problem out of HTTP authorisation system. * Fixed authorisation so if have no user/password on basic authentication, does not require it. * * Revision 1.34 1996/07/27 04:13:47 robertj * Fixed use of HTTP proxy on non-persistent connections. * * Revision 1.33 1996/07/15 10:37:20 robertj * Improved proxy "self" detection (especially localhost). * * Revision 1.32 1996/06/28 13:20:24 robertj * Modified HTTPAuthority so gets PHTTPReqest (mainly for URL) passed in. * Moved HTTP form resource to another compilation module. * Fixed memory leak in POST command. * * Revision 1.31 1996/06/10 10:00:00 robertj * Added global function for query parameters parsing. * * Revision 1.30 1996/06/07 13:52:23 robertj * Added PUT to HTTP proxy FTP. Necessitating redisign of entity body processing. * * Revision 1.29 1996/06/05 12:33:04 robertj * Fixed bug in parsing URL with no path, is NOT absolute! * * Revision 1.28 1996/05/30 10:07:26 robertj * Fixed bug in version number checking of return code compatibility. * * Revision 1.27 1996/05/26 03:46:42 robertj * Compatibility to GNU 2.7.x * * Revision 1.26 1996/05/23 10:02:13 robertj * Added common function for GET and HEAD commands. * Fixed status codes to be the actual status code instead of sequential enum. * This fixed some problems with proxy pass through of status codes. * Fixed bug in URL parsing of username and passwords. * * Revision 1.19.1.1 1996/04/17 11:08:22 craigs * New version by craig pending confirmation by robert * * Revision 1.19 1996/04/05 01:46:30 robertj * Assured PSocket::Write always writes the number of bytes specified, no longer need write loops. * Added workaraound for NT Netscape Navigator bug with persistent connections. * * Revision 1.18 1996/03/31 09:05:07 robertj * HTTP 1.1 upgrade. * * Revision 1.17 1996/03/17 05:48:07 robertj * Fixed host name print out of URLs. * Added hit count to PHTTPResource. * * Revision 1.16 1996/03/16 05:00:26 robertj * Added ParseReponse() for splitting reponse line into code and info. * Added client side support for HTTP socket. * Added hooks for proxy support in HTTP socket. * Added translation type to TranslateString() to accommodate query variables. * Defaulted scheme field in URL to "http". * Inhibited output of port field on string conversion of URL according to scheme. * * Revision 1.15 1996/03/11 10:29:50 robertj * Fixed bug in help image HTML. * * Revision 1.14 1996/03/10 13:15:24 robertj * Redesign to make resources thread safe. * * Revision 1.13 1996/03/02 03:27:37 robertj * Added function to translate a string to a form suitable for inclusion in a URL. * Added radio button and selection boxes to HTTP form resource. * Fixed bug in URL parsing, losing first / if hostname specified. * * Revision 1.12 1996/02/25 11:14:24 robertj * Radio button support for forms. * * Revision 1.11 1996/02/25 03:10:34 robertj * Removed pass through HTTP resource. * Fixed PHTTPConfig resource to use correct name for config key. * * Revision 1.10 1996/02/19 13:48:28 robertj * Put multiple uses of literal strings into const variables. * Fixed URL parsing so that the unmangling of strings occurs correctly. * Moved nested classes from PHTTPForm. * Added overwrite option to AddResource(). * Added get/set string to PHTTPString resource. * * Revision 1.9 1996/02/13 13:09:17 robertj * Added extra parameters to callback function in PHTTPResources, required * by descendants to make informed decisions on data being loaded. * * Revision 1.8 1996/02/08 12:26:29 robertj * Redesign of resource callback mechanism. * Added new resource types for HTML data entry forms. * * Revision 1.7 1996/02/03 11:33:19 robertj * Changed RadCmd() so can distinguish between I/O error and unknown command. * * Revision 1.6 1996/02/03 11:11:49 robertj * Numerous bug fixes. * Added expiry date and ismodifiedsince support. * * Revision 1.5 1996/01/30 23:32:40 robertj * Added single . * * Revision 1.4 1996/01/28 14:19:09 robertj * Split HTML into separate source file. * Beginning of pass through resource type. * Changed PCharArray in OnLoadData to PString for convenience in mangling data. * Made PHTTPSpace return standard page on selection of partial path. * * Revision 1.3 1996/01/28 02:49:16 robertj * Further implementation. * * Revision 1.2 1996/01/26 02:24:30 robertj * Further implemetation. * * Revision 1.1 1996/01/23 13:04:32 robertj * Initial revision * */ #ifdef __GNUC__ #pragma implementation "http.h" #pragma implementation "url.h" #endif #include #define P_DISABLE_FACTORY_INSTANCES #include #include #include #include #ifdef WIN32 #include #endif // RFC 1738 // http://host:port/path... // https://host:port/path.... // gopher://host:port // wais://host:port // nntp://host:port // prospero://host:port // ftp://user:password@host:port/path... // telnet://user:password@host:port // file://hostname/path... // mailto:user@hostname // news:string #define DEFAULT_FTP_PORT 21 #define DEFAULT_TELNET_PORT 23 #define DEFAULT_GOPHER_PORT 70 #define DEFAULT_HTTP_PORT 80 #define DEFAULT_NNTP_PORT 119 #define DEFAULT_WAIS_PORT 210 #define DEFAULT_HTTPS_PORT 443 #define DEFAULT_RTSP_PORT 554 #define DEFAULT_RTSPU_PORT 554 #define DEFAULT_PROSPERO_PORT 1525 #define DEFAULT_H323_PORT 1720 #define DEFAULT_H323RAS_PORT 1719 #define DEFAULT_SIP_PORT 5060 #define DEFINE_LEGACY_URL_SCHEME(schemeName, user, pass, host, def, defhost, query, params, frags, path, rel, port) \ class PURLLegacyScheme_##schemeName : public PURLLegacyScheme \ { \ public: \ PURLLegacyScheme_##schemeName() \ : PURLLegacyScheme(#schemeName ) \ { \ hasUsername = user; \ hasPassword = pass; \ hasHostPort = host; \ defaultToUserIfNoAt = def; \ defaultHostToLocal = defhost; \ hasQuery = query; \ hasParameters = params; \ hasFragments = frags; \ hasPath = path; \ relativeImpliesScheme = rel; \ defaultPort = port; \ } \ }; \ static PFactory::Worker schemeName##Factory(#schemeName, true); \ DEFINE_LEGACY_URL_SCHEME(http, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, DEFAULT_HTTP_PORT ) DEFINE_LEGACY_URL_SCHEME(file, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, 0) DEFINE_LEGACY_URL_SCHEME(https, FALSE, FALSE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, DEFAULT_HTTPS_PORT) DEFINE_LEGACY_URL_SCHEME(gopher, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_GOPHER_PORT) DEFINE_LEGACY_URL_SCHEME(wais, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_WAIS_PORT) DEFINE_LEGACY_URL_SCHEME(nntp, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_NNTP_PORT) DEFINE_LEGACY_URL_SCHEME(prospero, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_PROSPERO_PORT) DEFINE_LEGACY_URL_SCHEME(rtsp, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_RTSP_PORT) DEFINE_LEGACY_URL_SCHEME(rtspu, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_RTSPU_PORT) DEFINE_LEGACY_URL_SCHEME(ftp, TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, DEFAULT_FTP_PORT) DEFINE_LEGACY_URL_SCHEME(telnet, TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, DEFAULT_TELNET_PORT) DEFINE_LEGACY_URL_SCHEME(mailto, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, 0) DEFINE_LEGACY_URL_SCHEME(news, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, 0) DEFINE_LEGACY_URL_SCHEME(h323, TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, DEFAULT_H323_PORT) DEFINE_LEGACY_URL_SCHEME(sip, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, DEFAULT_SIP_PORT) DEFINE_LEGACY_URL_SCHEME(tel, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0) DEFINE_LEGACY_URL_SCHEME(fax, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0) DEFINE_LEGACY_URL_SCHEME(callto, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, 0) PINSTANTIATE_FACTORY(PURLScheme, PString) #define DEFAULT_SCHEME "http" #define FILE_SCHEME "file" ////////////////////////////////////////////////////////////////////////////// // PURL PURL::PURL() : //scheme(SchemeTable[DEFAULT_SCHEME].name), scheme(DEFAULT_SCHEME), port(0), portSupplied (FALSE), relativePath(FALSE) { } PURL::PURL(const char * str, const char * defaultScheme) { Parse(str, defaultScheme); } PURL::PURL(const PString & str, const char * defaultScheme) { Parse(str, defaultScheme); } PURL::PURL(const PFilePath & filePath) : //scheme(SchemeTable[FILE_SCHEME].name), scheme(FILE_SCHEME), port(0), portSupplied (FALSE), relativePath(FALSE) { PStringArray pathArray = filePath.GetDirectory().GetPath(); hostname = pathArray[0]; PINDEX i; for (i = 1; i < pathArray.GetSize(); i++) pathArray[i-1] = pathArray[i]; pathArray[i-1] = filePath.GetFileName(); SetPath(pathArray); } PObject::Comparison PURL::Compare(const PObject & obj) const { PAssert(PIsDescendant(&obj, PURL), PInvalidCast); return urlString.Compare(((const PURL &)obj).urlString); } PINDEX PURL::HashFunction() const { return urlString.HashFunction(); } void PURL::PrintOn(ostream & stream) const { stream << urlString; } void PURL::ReadFrom(istream & stream) { PString s; stream >> s; Parse(s); } PString PURL::TranslateString(const PString & str, TranslationType type) { PString xlat = str; PString safeChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789$-_.!*'(),"; switch (type) { case LoginTranslation : safeChars += "+;?&="; break; case PathTranslation : safeChars += "+:@&="; break; case QueryTranslation : safeChars += ":@"; } PINDEX pos = (PINDEX)-1; while ((pos += 1+strspn(&xlat[pos+1], safeChars)) < xlat.GetLength()) xlat.Splice(psprintf("%%%02X", (BYTE)xlat[pos]), pos, 1); if (type == QueryTranslation) { PINDEX space = (PINDEX)-1; while ((space = xlat.Find(' ', space+1)) != P_MAX_INDEX) xlat[space] = '+'; } return xlat; } PString PURL::UntranslateString(const PString & str, TranslationType type) { PString xlat = str; xlat.MakeUnique(); PINDEX pos; if (type == PURL::QueryTranslation) { pos = (PINDEX)-1; while ((pos = xlat.Find('+', pos+1)) != P_MAX_INDEX) xlat[pos] = ' '; } pos = (PINDEX)-1; while ((pos = xlat.Find('%', pos+1)) != P_MAX_INDEX) { int digit1 = xlat[pos+1]; int digit2 = xlat[pos+2]; if (isxdigit(digit1) && isxdigit(digit2)) { xlat[pos] = (char)( (isdigit(digit2) ? (digit2-'0') : (toupper(digit2)-'A'+10)) + ((isdigit(digit1) ? (digit1-'0') : (toupper(digit1)-'A'+10)) << 4)); xlat.Delete(pos+1, 2); } } return xlat; } static void SplitVars(const PString & str, PStringToString & vars, char sep1, char sep2) { PINDEX sep1prev = 0; do { PINDEX sep1next = str.Find(sep1, sep1prev); if (sep1next == P_MAX_INDEX) sep1next--; // Implicit assumption string is not a couple of gigabytes long ... PINDEX sep2pos = str.Find(sep2, sep1prev); if (sep2pos > sep1next) sep2pos = sep1next; PCaselessString key = PURL::UntranslateString(str(sep1prev, sep2pos-1), PURL::QueryTranslation); if (!key) { PString data = PURL::UntranslateString(str(sep2pos+1, sep1next-1), PURL::QueryTranslation); if (vars.Contains(key)) vars.SetAt(key, vars[key] + ',' + data); else vars.SetAt(key, data); } sep1prev = sep1next+1; } while (sep1prev != P_MAX_INDEX); } void PURL::SplitQueryVars(const PString & queryStr, PStringToString & queryVars) { SplitVars(queryStr, queryVars, '&', '='); } BOOL PURL::InternalParse(const char * cstr, const char * defaultScheme) { urlString = cstr; scheme.MakeEmpty(); username.MakeEmpty(); password.MakeEmpty(); hostname.MakeEmpty(); port = 0; portSupplied = FALSE; relativePath = FALSE; pathStr.MakeEmpty(); path.SetSize(0); paramVars.RemoveAll(); fragment.MakeEmpty(); queryVars.RemoveAll(); // copy the string so we can take bits off it while (isspace(*cstr)) cstr++; PString url = cstr; // Character set as per RFC2396 PINDEX pos = 0; while (isalnum(url[pos]) || url[pos] == '+' || url[pos] == '-' || url[pos] == '.') pos++; PString schemeName; // get information which tells us how to parse URL for this // particular scheme PURLScheme * schemeInfo = NULL; // Determine if the URL has an explicit scheme if (url[pos] == ':') { // get the scheme information, or get the default scheme schemeInfo = PFactory::CreateInstance(url.Left(pos)); if (schemeInfo == NULL && defaultScheme == NULL) { PFactory::KeyList_T keyList = PFactory::GetKeyList(); if (keyList.size() != 0) schemeInfo = PFactory::CreateInstance(keyList[0]); } if (schemeInfo != NULL) url.Delete(0, pos+1); } // if we could not match a scheme, then use the specified default scheme if (schemeInfo == NULL && defaultScheme != NULL) schemeInfo = PFactory::CreateInstance(defaultScheme); // if that still fails, then use the global default scheme if (schemeInfo == NULL) schemeInfo = PFactory::CreateInstance(DEFAULT_SCHEME); // if that fails, then there is nowehere to go PAssert(schemeInfo != NULL, "Default scheme not available"); scheme = schemeInfo->GetName(); if (!schemeInfo->Parse(url, *this)) return FALSE; return !IsEmpty(); } BOOL PURL::LegacyParse(const PString & _url, const PURLLegacyScheme * schemeInfo) { PString url = _url; PINDEX pos; // Super special case! if (scheme *= "callto") { // Actually not part of MS spec, but a lot of people put in the // into // the URL, so we take it out of it is there. if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/') url.Delete(0, 2); // For some bizarre reason callto uses + instead of ; for paramters // We do a loop so that phone numbers of the form +61243654666 still work do { pos = url.Find('+'); } while (pos != P_MAX_INDEX && isdigit(url[pos+1])); if (pos != P_MAX_INDEX) { SplitVars(url(pos+1, P_MAX_INDEX), paramVars, '+', '='); url.Delete(pos, P_MAX_INDEX); } hostname = paramVars("gateway"); if (!hostname) username = UntranslateString(url, LoginTranslation); else { PCaselessString type = paramVars("type"); if (type == "directory") { pos = url.Find('/'); if (pos == P_MAX_INDEX) username = UntranslateString(url, LoginTranslation); else { hostname = UntranslateString(url.Left(pos), LoginTranslation); username = UntranslateString(url.Mid(pos+1), LoginTranslation); } } else { // Now look for an @ and split user and host pos = url.Find('@'); if (pos != P_MAX_INDEX) { username = UntranslateString(url.Left(pos), LoginTranslation); hostname = UntranslateString(url.Mid(pos+1), LoginTranslation); } else { if (type == "ip" || type == "host") hostname = UntranslateString(url, LoginTranslation); else username = UntranslateString(url, LoginTranslation); } } } // Allow for [ipv6] form pos = hostname.Find(']'); if (pos == P_MAX_INDEX) pos = 0; pos = hostname.Find(':', pos); if (pos != P_MAX_INDEX) { port = (WORD)hostname.Mid(pos+1).AsUnsigned(); portSupplied = TRUE; hostname.Delete(pos, P_MAX_INDEX); } password = paramVars("password"); return TRUE; } // if the URL should have leading slash, then remove it if it has one if (schemeInfo != NULL && schemeInfo->hasHostPort && schemeInfo->hasPath) { if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/') url.Delete(0, 2); else relativePath = TRUE; } // parse user/password/host/port if (!relativePath && schemeInfo->hasHostPort) { PString endHostChars; if (schemeInfo->hasPath) endHostChars += '/'; if (schemeInfo->hasQuery) endHostChars += '?'; if (schemeInfo->hasParameters) endHostChars += ';'; if (schemeInfo->hasFragments) endHostChars += '#'; if (endHostChars.IsEmpty()) pos = P_MAX_INDEX; else pos = url.FindOneOf(endHostChars); PString uphp = url.Left(pos); if (pos != P_MAX_INDEX) url.Delete(0, pos); else url.MakeEmpty(); // if the URL is of type UserPasswordHostPort, then parse it if (schemeInfo->hasUsername) { // extract username and password PINDEX pos2 = uphp.Find('@'); PINDEX pos3 = P_MAX_INDEX; if (schemeInfo->hasPassword) pos3 = uphp.Find(':'); switch (pos2) { case 0 : uphp.Delete(0, 1); break; case P_MAX_INDEX : if (schemeInfo->defaultToUserIfNoAt) { if (pos3 == P_MAX_INDEX) username = UntranslateString(uphp, LoginTranslation); else { username = UntranslateString(uphp.Left(pos3), LoginTranslation); password = UntranslateString(uphp.Mid(pos3+1), LoginTranslation); } uphp.MakeEmpty(); } break; default : if (pos3 > pos2) username = UntranslateString(uphp.Left(pos2), LoginTranslation); else { username = UntranslateString(uphp.Left(pos3), LoginTranslation); password = UntranslateString(uphp(pos3+1, pos2-1), LoginTranslation); } uphp.Delete(0, pos2+1); } } // if the URL does not have a port, then this is the hostname if (schemeInfo->defaultPort == 0) hostname = UntranslateString(uphp, LoginTranslation); else { // determine if the URL has a port number // Allow for [ipv6] form pos = uphp.Find(']'); if (pos == P_MAX_INDEX) pos = 0; pos = uphp.Find(':', pos); if (pos == P_MAX_INDEX) hostname = UntranslateString(uphp, LoginTranslation); else { hostname = UntranslateString(uphp.Left(pos), LoginTranslation); port = (WORD)uphp.Mid(pos+1).AsUnsigned(); portSupplied = TRUE; } if (hostname.IsEmpty() && schemeInfo->defaultHostToLocal) hostname = PIPSocket::GetHostName(); } } if (schemeInfo->hasQuery) { // chop off any trailing query pos = url.Find('?'); if (pos != P_MAX_INDEX) { SplitQueryVars(url(pos+1, P_MAX_INDEX), queryVars); url.Delete(pos, P_MAX_INDEX); } } if (schemeInfo->hasParameters) { // chop off any trailing parameters pos = url.Find(';'); if (pos != P_MAX_INDEX) { SplitVars(url(pos+1, P_MAX_INDEX), paramVars, ';', '='); url.Delete(pos, P_MAX_INDEX); } } if (schemeInfo->hasFragments) { // chop off any trailing fragment pos = url.Find('#'); if (pos != P_MAX_INDEX) { fragment = UntranslateString(url(pos+1, P_MAX_INDEX), PathTranslation); url.Delete(pos, P_MAX_INDEX); } } if (schemeInfo->hasPath) SetPathStr(url); // the hierarchy is what is left else { // if the rest of the URL isn't a path, then we are finished! pathStr = UntranslateString(url, PathTranslation); Recalculate(); } if (port == 0 && schemeInfo->defaultPort != 0 && !relativePath) { // Yes another horrible, horrible special case! if (scheme == "h323" && paramVars("type") == "gk") port = DEFAULT_H323RAS_PORT; else port = schemeInfo->defaultPort; Recalculate(); } return TRUE; } PFilePath PURL::AsFilePath() const { //if (scheme != SchemeTable[FILE_SCHEME].name) // return PString::Empty(); if (scheme != FILE_SCHEME) return PString::Empty(); PStringStream str; if (relativePath) { for (PINDEX i = 0; i < path.GetSize(); i++) { if (i > 0) str << PDIR_SEPARATOR; str << path[i]; } } else { if (hostname != "localhost") str << PDIR_SEPARATOR << hostname; for (PINDEX i = 0; i < path.GetSize(); i++) str << PDIR_SEPARATOR << path[i]; } return str; } PString PURL::AsString(UrlFormat fmt) const { if (fmt == FullURL) return urlString; if (scheme.IsEmpty()) return PString::Empty(); //const schemeStruct * schemeInfo = GetSchemeInfo(scheme); //if (schemeInfo == NULL) // schemeInfo = &SchemeTable[PARRAYSIZE(SchemeTable)-1]; const PURLScheme * schemeInfo = PFactory::CreateInstance(scheme); if (schemeInfo == NULL) schemeInfo = PFactory::CreateInstance(DEFAULT_SCHEME); return schemeInfo->AsString(fmt, *this); } PString PURL::LegacyAsString(PURL::UrlFormat fmt, const PURLLegacyScheme * schemeInfo) const { PStringStream str; PINDEX i; if (fmt == HostPortOnly) { if (schemeInfo->hasHostPort && hostname.IsEmpty()) return str; str << scheme << ':'; if (relativePath) { if (schemeInfo->relativeImpliesScheme) return PString::Empty(); return str; } if (schemeInfo->hasPath && schemeInfo->hasHostPort) str << "//"; if (schemeInfo->hasUsername) { if (!username) { str << TranslateString(username, LoginTranslation); if (schemeInfo->hasPassword && !password) str << ':' << TranslateString(password, LoginTranslation); str << '@'; } } if (schemeInfo->hasHostPort) { if (hostname.Find(':') != P_MAX_INDEX) str << '[' << hostname << ']'; else str << hostname; } if (schemeInfo->defaultPort != 0) { if (port != schemeInfo->defaultPort || portSupplied) str << ':' << port; } return str; } // URIOnly and PathOnly if (schemeInfo->hasPath) { for (i = 0; i < path.GetSize(); i++) { if (i > 0 || !relativePath) str << '/'; str << TranslateString(path[i], PathTranslation); } } else str << TranslateString(pathStr, PathTranslation); if (fmt == URIOnly) { if (!fragment) str << "#" << TranslateString(fragment, PathTranslation); for (i = 0; i < paramVars.GetSize(); i++) { str << ';' << TranslateString(paramVars.GetKeyAt(i), QueryTranslation); PString data = paramVars.GetDataAt(i); if (!data) str << '=' << TranslateString(data, QueryTranslation); } if (!queryVars.IsEmpty()) str << '?' << GetQuery(); } return str; } void PURL::SetScheme(const PString & s) { scheme = s; Recalculate(); } void PURL::SetUserName(const PString & u) { username = u; Recalculate(); } void PURL::SetPassword(const PString & p) { password = p; Recalculate(); } void PURL::SetHostName(const PString & h) { hostname = h; Recalculate(); } void PURL::SetPort(WORD newPort) { port = newPort; Recalculate(); } void PURL::SetPathStr(const PString & p) { pathStr = p; path = pathStr.Tokenise("/", TRUE); if (path.GetSize() > 0 && path[0].IsEmpty()) path.RemoveAt(0); for (PINDEX i = 0; i < path.GetSize(); i++) { path[i] = UntranslateString(path[i], PathTranslation); if (i > 0 && path[i] == ".." && path[i-1] != "..") { path.RemoveAt(i--); path.RemoveAt(i--); } } Recalculate(); } void PURL::SetPath(const PStringArray & p) { path = p; pathStr.MakeEmpty(); for (PINDEX i = 0; i < path.GetSize(); i++) pathStr += '/' + path[i]; Recalculate(); } PString PURL::GetParameters() const { PStringStream str; for (PINDEX i = 0; i < paramVars.GetSize(); i++) { if (i > 0) str << ';'; str << paramVars.GetKeyAt(i); PString data = paramVars.GetDataAt(i); if (!data) str << '=' << data; } return str; } void PURL::SetParameters(const PString & parameters) { SplitVars(parameters, paramVars, ';', '='); Recalculate(); } void PURL::SetParamVars(const PStringToString & p) { paramVars = p; Recalculate(); } void PURL::SetParamVar(const PString & key, const PString & data) { if (data.IsEmpty()) paramVars.RemoveAt(key); else paramVars.SetAt(key, data); Recalculate(); } PString PURL::GetQuery() const { PStringStream str; for (PINDEX i = 0; i < queryVars.GetSize(); i++) { if (i > 0) str << '&'; str << TranslateString(queryVars.GetKeyAt(i), QueryTranslation) << '=' << TranslateString(queryVars.GetDataAt(i), QueryTranslation); } return str; } void PURL::SetQuery(const PString & queryStr) { SplitQueryVars(queryStr, queryVars); Recalculate(); } void PURL::SetQueryVars(const PStringToString & q) { queryVars = q; Recalculate(); } void PURL::SetQueryVar(const PString & key, const PString & data) { if (data.IsEmpty()) queryVars.RemoveAt(key); else queryVars.SetAt(key, data); Recalculate(); } BOOL PURL::OpenBrowser(const PString & url) { #ifdef WIN32 SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.lpVerb = TEXT("open"); #ifndef _WIN32_WCE sei.lpFile = url; #else USES_CONVERSION; sei.lpFile = A2T(url); #endif // _WIN32_WCE if (ShellExecuteEx(&sei) != 0) return TRUE; #ifndef _WIN32_WCE MessageBox(NULL, "Unable to open page"&url, PProcess::Current().GetName(), MB_TASKMODAL); #else MessageBox(NULL, _T("Unable to open page"), A2T(PProcess::Current().GetName()), MB_APPLMODAL); #endif // _WIN32_WCE #endif // WIN32 return FALSE; } void PURL::Recalculate() { //if (scheme.IsEmpty()) // scheme = SchemeTable[DEFAULT_SCHEME].name; if (scheme.IsEmpty()) scheme = DEFAULT_SCHEME; urlString = AsString(HostPortOnly) + AsString(URIOnly); } ////////////////////////////////////////////////////////////////////////////// // PHTTP static char const * const HTTPCommands[PHTTP::NumCommands] = { // HTTP 1.0 commands "GET", "HEAD", "POST", // HTTP 1.1 commands "PUT", "DELETE", "TRACE", "OPTIONS", // HTTPS command "CONNECT" }; const char * const PHTTP::AllowTag = "Allow"; const char * const PHTTP::AuthorizationTag = "Authorization"; const char * const PHTTP::ContentEncodingTag = "Content-Encoding"; const char * const PHTTP::ContentLengthTag = "Content-Length"; const char * const PHTTP::ContentTypeTag = "Content-Type"; const char * const PHTTP::DateTag = "Date"; const char * const PHTTP::ExpiresTag = "Expires"; const char * const PHTTP::FromTag = "From"; const char * const PHTTP::IfModifiedSinceTag = "If-Modified-Since"; const char * const PHTTP::LastModifiedTag = "Last-Modified"; const char * const PHTTP::LocationTag = "Location"; const char * const PHTTP::PragmaTag = "Pragma"; const char * const PHTTP::PragmaNoCacheTag = "no-cache"; const char * const PHTTP::RefererTag = "Referer"; const char * const PHTTP::ServerTag = "Server"; const char * const PHTTP::UserAgentTag = "User-Agent"; const char * const PHTTP::WWWAuthenticateTag = "WWW-Authenticate"; const char * const PHTTP::MIMEVersionTag = "MIME-Version"; const char * const PHTTP::ConnectionTag = "Connection"; const char * const PHTTP::KeepAliveTag = "Keep-Alive"; const char * const PHTTP::TransferEncodingTag= "Transfer-Encoding"; const char * const PHTTP::ChunkedTag = "chunked"; const char * const PHTTP::ProxyConnectionTag = "Proxy-Connection"; const char * const PHTTP::ProxyAuthorizationTag = "Proxy-Authorization"; const char * const PHTTP::ProxyAuthenticateTag = "Proxy-Authenticate"; const char * const PHTTP::ForwardedTag = "Forwarded"; const char * const PHTTP::SetCookieTag = "Set-Cookie"; const char * const PHTTP::CookieTag = "Cookie"; PHTTP::PHTTP() : PInternetProtocol("www 80", NumCommands, HTTPCommands) { } PINDEX PHTTP::ParseResponse(const PString & line) { PINDEX endVer = line.Find(' '); if (endVer == P_MAX_INDEX) { lastResponseInfo = "Bad response"; lastResponseCode = PHTTP::InternalServerError; return 0; } lastResponseInfo = line.Left(endVer); PINDEX endCode = line.Find(' ', endVer+1); lastResponseCode = line(endVer+1,endCode-1).AsInteger(); if (lastResponseCode == 0) lastResponseCode = PHTTP::InternalServerError; lastResponseInfo &= line.Mid(endCode); return 0; } // End Of File ///////////////////////////////////////////////////////////////