/*
* 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 <ptlib.h>
#define P_DISABLE_FACTORY_INSTANCES
#include <ptlib/sockets.h>
#include <ptclib/http.h>
#include <ptclib/url.h>
#include <ctype.h>
#ifdef WIN32
#include <shellapi.h>
#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<PURLScheme>::Worker<PURLLegacyScheme_##schemeName> 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<PURLScheme>::CreateInstance(url.Left(pos));
if (schemeInfo == NULL && defaultScheme == NULL) {
PFactory<PURLScheme>::KeyList_T keyList = PFactory<PURLScheme>::GetKeyList();
if (keyList.size() != 0)
schemeInfo = PFactory<PURLScheme>::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<PURLScheme>::CreateInstance(defaultScheme);
// if that still fails, then use the global default scheme
if (schemeInfo == NULL)
schemeInfo = PFactory<PURLScheme>::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<PURLScheme>::CreateInstance(scheme);
if (schemeInfo == NULL)
schemeInfo = PFactory<PURLScheme>::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 ///////////////////////////////////////////////////////////////
syntax highlighted by Code2HTML, v. 0.9.1