1 /*
2  * url.cxx
3  *
4  * URL parsing classes.
5  *
6  * Portable Tools Library
7  *
8  * Copyright (c) 1993-2008 Equivalence Pty. Ltd.
9  *
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/
14  *
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.
19  *
20  * The Original Code is Portable Windows Library.
21  *
22  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23  *
24  * Contributor(s): ______________________________________.
25  *
26  * $Revision: 28604 $
27  * $Author: rjongbloed $
28  * $Date: 2012-11-27 01:41:14 -0600 (Tue, 27 Nov 2012) $
29  */
30 
31 #ifdef __GNUC__
32 #pragma implementation "url.h"
33 #endif
34 
35 #include <ptlib.h>
36 
37 #if P_URL
38 
39 #include <ptclib/url.h>
40 
41 #include <ptlib/sockets.h>
42 #include <ptclib/cypher.h>
43 #include <ctype.h>
44 
45 #if defined(_WIN32) && !defined(_WIN32_WCE)
46 #include <shellapi.h>
47 #pragma comment(lib,"shell32.lib")
48 #endif
49 
50 
51 // RFC 1738
52 // http://host:port/path...
53 // https://host:port/path....
54 // gopher://host:port
55 // wais://host:port
56 // nntp://host:port
57 // prospero://host:port
58 // ftp://user:password@host:port/path...
59 // telnet://user:password@host:port
60 // file://hostname/path...
61 
62 // mailto:user@hostname
63 // news:string
64 
65 #define DEFAULT_FTP_PORT      21
66 #define DEFAULT_TELNET_PORT   23
67 #define DEFAULT_GOPHER_PORT   70
68 #define DEFAULT_HTTP_PORT     80
69 #define DEFAULT_NNTP_PORT     119
70 #define DEFAULT_WAIS_PORT     210
71 #define DEFAULT_HTTPS_PORT    443
72 #define DEFAULT_RTSP_PORT     554
73 #define DEFAULT_RTSPU_PORT    554
74 #define DEFAULT_PROSPERO_PORT 1525
75 #define DEFAULT_H323_PORT     1720
76 #define DEFAULT_H323S_PORT    1300
77 #define DEFAULT_H323RAS_PORT  1719
78 #define DEFAULT_MSRP_PORT     2855
79 #define DEFAULT_RTMP_PORT     1935
80 #define DEFAULT_SIP_PORT      5060
81 #define DEFAULT_SIPS_PORT     5061
82 
83 
84 //                 schemeName,user,  passwd,host,  defUser,defhost,query, params,frags, path,  rel,   port
PURL_LEGACY_SCHEME(http,true,true,true,false,true,true,true,true,true,true,DEFAULT_HTTP_PORT)85 PURL_LEGACY_SCHEME(http,      true,  true,  true,  false,  true,   true,  true,  true,  true,  true,  DEFAULT_HTTP_PORT )
86 PURL_LEGACY_SCHEME(file,      false, false, true,  false,  true,   false, false, false, true,  false, 0)
87 PURL_LEGACY_SCHEME(https,     false, false, true,  false,  true,   true,  true,  true,  true,  true,  DEFAULT_HTTPS_PORT)
88 PURL_LEGACY_SCHEME(gopher,    false, false, true,  false,  true,   false, false, false, true,  false, DEFAULT_GOPHER_PORT)
89 PURL_LEGACY_SCHEME(wais,      false, false, true,  false,  false,  false, false, false, true,  false, DEFAULT_WAIS_PORT)
90 PURL_LEGACY_SCHEME(nntp,      false, false, true,  false,  true,   false, false, false, true,  false, DEFAULT_NNTP_PORT)
91 PURL_LEGACY_SCHEME(prospero,  false, false, true,  false,  true,   false, false, false, true,  false, DEFAULT_PROSPERO_PORT)
92 PURL_LEGACY_SCHEME(rtsp,      false, false, true,  false,  true,   true,  false, false, true,  false, DEFAULT_RTSP_PORT)
93 PURL_LEGACY_SCHEME(rtspu,     false, false, true,  false,  true,   false, false, false, true,  false, DEFAULT_RTSPU_PORT)
94 PURL_LEGACY_SCHEME(ftp,       true,  true,  true,  false,  true,   false, false, false, true,  false, DEFAULT_FTP_PORT)
95 PURL_LEGACY_SCHEME(telnet,    true,  true,  true,  false,  true,   false, false, false, false, false, DEFAULT_TELNET_PORT)
96 PURL_LEGACY_SCHEME(mailto,    false, false, false, true,   false,  true,  false, false, false, false, 0)
97 PURL_LEGACY_SCHEME(news,      false, false, false, false,  true,   false, false, false, false, false, 0)
98 PURL_LEGACY_SCHEME(h323,      true,  false, true,  true,   false,  false, true,  false, false, false, DEFAULT_H323_PORT)
99 PURL_LEGACY_SCHEME(h323s,     true,  false, true,  true,   false,  false, true,  false, false, false, DEFAULT_H323S_PORT)
100 PURL_LEGACY_SCHEME(rtmp,      false, false, true,  false,  false,  false, false, false, true,  false, DEFAULT_RTMP_PORT)
101 PURL_LEGACY_SCHEME(sip,       true,  true,  true,  false,  false,  true,  true,  false, false, false, DEFAULT_SIP_PORT)
102 PURL_LEGACY_SCHEME(sips,      true,  true,  true,  false,  false,  true,  true,  false, false, false, DEFAULT_SIPS_PORT)
103 PURL_LEGACY_SCHEME(fax,       false, false, false, true,   false,  false, true,  false, false, false, 0)
104 PURL_LEGACY_SCHEME(callto,    false, false, false, true,   false,  false, true,  false, false, false, 0)
105 PURL_LEGACY_SCHEME(msrp,      false, false, true,  false,  false,  true,  true,  false, true,  false, DEFAULT_MSRP_PORT)
106 
107 #define DEFAULT_SCHEME "http"
108 #define FILE_SCHEME    "file"
109 
110 //////////////////////////////////////////////////////////////////////////////
111 // PURL
112 
113 PURL::PURL()
114   : scheme(DEFAULT_SCHEME),
115     port(0),
116     portSupplied (PFalse),
117     relativePath(PFalse)
118 {
119 }
120 
121 
PURL(const char * str,const char * defaultScheme)122 PURL::PURL(const char * str, const char * defaultScheme)
123 {
124   Parse(str, defaultScheme);
125 }
126 
127 
PURL(const PString & str,const char * defaultScheme)128 PURL::PURL(const PString & str, const char * defaultScheme)
129 {
130   Parse(str, defaultScheme);
131 }
132 
133 
PURL(const PFilePath & filePath)134 PURL::PURL(const PFilePath & filePath)
135   : scheme(FILE_SCHEME),
136     port(0),
137     portSupplied (PFalse),
138     relativePath(PFalse)
139 {
140   PStringArray pathArray = filePath.GetDirectory().GetPath();
141   if (pathArray.IsEmpty())
142     return;
143 
144   if (pathArray[0].GetLength() == 2 && pathArray[0][1] == ':')
145     pathArray[0][1] = '|';
146 
147   pathArray.AppendString(filePath.GetFileName());
148 
149   SetPath(pathArray);
150 }
151 
152 
Compare(const PObject & obj) const153 PObject::Comparison PURL::Compare(const PObject & obj) const
154 {
155   PAssert(PIsDescendant(&obj, PURL), PInvalidCast);
156   return urlString.Compare(((const PURL &)obj).urlString);
157 }
158 
PURL(const PURL & other)159 PURL::PURL(const PURL & other)
160 {
161   CopyContents(other);
162 }
163 
operator =(const PURL & other)164 PURL & PURL::operator=(const PURL & other)
165 {
166   CopyContents(other);
167   return *this;
168 }
169 
CopyContents(const PURL & other)170 void PURL::CopyContents(const PURL & other)
171 {
172   urlString    = other.urlString;
173   scheme       = other.scheme;
174   username     = other.username;
175   password     = other.password;
176   hostname     = other.hostname;
177   port         = other.port;
178   portSupplied = other.portSupplied;
179   relativePath = other.relativePath;
180   path         = other.path;
181   fragment     = other.fragment;
182 
183   paramVars    = other.paramVars;
184   paramVars.MakeUnique();
185 
186   queryVars    = other.queryVars;
187   queryVars.MakeUnique();
188 
189   m_contents   = other.m_contents;
190 }
191 
HashFunction() const192 PINDEX PURL::HashFunction() const
193 {
194   return urlString.HashFunction();
195 }
196 
197 
PrintOn(ostream & stream) const198 void PURL::PrintOn(ostream & stream) const
199 {
200   stream << urlString;
201 }
202 
203 
ReadFrom(istream & stream)204 void PURL::ReadFrom(istream & stream)
205 {
206   PString s;
207   stream >> s;
208   Parse(s);
209 }
210 
211 
TranslateString(const PString & str,TranslationType type)212 PString PURL::TranslateString(const PString & str, TranslationType type)
213 {
214   PString xlat = str;
215 
216   /* Characters sets are from RFC2396.
217      The EBNF defines lowalpha, upalpha, digit and mark which are always
218      allowed. The reserved list consisting of ";/?:@&=+$," may or may not be
219      allowed depending on the syntatic element being encoded.
220    */
221   PString safeChars = "abcdefghijklmnopqrstuvwxyz"  // lowalpha
222                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  // upalpha
223                       "0123456789"                  // digit
224                       "-_.!~*'()";                  // mark
225   switch (type) {
226     case LoginTranslation :
227       safeChars += ";&=+$,";  // Section 3.2.2
228       break;
229 
230     case PathTranslation :
231       safeChars += ":@&=+$,|";   // Section 3.3
232       break;
233 
234     case ParameterTranslation :
235       /* By strict RFC2396/3.3 this should be as for PathTranslation, but many
236          URI schemes have parameters of the form key=value so we don't allow
237          '=' character in the allowed set. Also, including one of "@,|" is
238          incompatible with some schemes, leave those out too. */
239       safeChars += ":&+$";
240       break;
241 
242     case QuotedParameterTranslation :
243       safeChars += "[]/:@&=+$,|";
244       return str.FindSpan(safeChars) != P_MAX_INDEX ? str.ToLiteral() : str;
245 
246     default :
247       break;    // Section 3.4, no reserved characters may be used
248   }
249   PINDEX pos = (PINDEX)-1;
250   while ((pos = xlat.FindSpan(safeChars, pos+1)) != P_MAX_INDEX)
251     xlat.Splice(psprintf("%%%02X", (BYTE)xlat[pos]), pos, 1);
252 
253   return xlat;
254 }
255 
256 
UntranslateString(const PString & str,TranslationType type)257 PString PURL::UntranslateString(const PString & str, TranslationType type)
258 {
259   PString xlat = str;
260   xlat.MakeUnique();
261 
262   PINDEX pos;
263   if (type == PURL::QueryTranslation) {
264     /* Even though RFC2396 never mentions this, RFC1630 does. */
265     pos = (PINDEX)-1;
266     while ((pos = xlat.Find('+', pos+1)) != P_MAX_INDEX)
267       xlat[pos] = ' ';
268   }
269 
270   pos = (PINDEX)-1;
271   while ((pos = xlat.Find('%', pos+1)) != P_MAX_INDEX) {
272     int digit1 = xlat[pos+1];
273     int digit2 = xlat[pos+2];
274     if (isxdigit(digit1) && isxdigit(digit2)) {
275       xlat[pos] = (char)(
276             (isdigit(digit2) ? (digit2-'0') : (toupper(digit2)-'A'+10)) +
277            ((isdigit(digit1) ? (digit1-'0') : (toupper(digit1)-'A'+10)) << 4));
278       xlat.Delete(pos+1, 2);
279     }
280   }
281 
282   return xlat;
283 }
284 
285 
SplitVars(const PString & str,PStringToString & vars,char sep1,char sep2,TranslationType type)286 void PURL::SplitVars(const PString & str, PStringToString & vars, char sep1, char sep2, TranslationType type)
287 {
288   vars.RemoveAll();
289 
290   PINDEX sep1prev = 0;
291   do {
292     PINDEX sep1next = str.Find(sep1, sep1prev);
293     if (sep1next == P_MAX_INDEX)
294       sep1next--; // Implicit assumption string is not a couple of gigabytes long ...
295 
296     PCaselessString key, data;
297 
298     PINDEX sep2pos = str.Find(sep2, sep1prev);
299     if (sep2pos > sep1next)
300       key = str(sep1prev, sep1next-1);
301     else {
302       key = str(sep1prev, sep2pos-1);
303       if (type != QuotedParameterTranslation)
304         data = str(sep2pos+1, sep1next-1);
305       else {
306         while (isspace(str[++sep2pos]))
307           ;
308         if (str[sep2pos] != '"')
309           data = str(sep2pos, sep1next-1);
310         else {
311           // find the end quote
312           PINDEX endQuote = sep2pos+1;
313           do {
314             endQuote = str.Find('"', endQuote+1);
315             if (endQuote == P_MAX_INDEX) {
316               PTRACE(1, "URI\tNo closing double quote in parameter: " << str);
317               endQuote = str.GetLength()-1;
318               break;
319             }
320           } while (str[endQuote-1] == '\\');
321 
322           data = PString(PString::Literal, str(sep2pos, endQuote));
323 
324           if (sep1next < endQuote) {
325             sep1next = str.Find(sep1, endQuote);
326             if (sep1next == P_MAX_INDEX)
327               sep1next--; // Implicit assumption string is not a couple of gigabytes long ...
328           }
329         }
330       }
331     }
332 
333     key = PURL::UntranslateString(key, type);
334     if (!key) {
335       data = PURL::UntranslateString(data, type);
336       if (vars.Contains(key))
337         vars.SetAt(key, vars[key] + '\n' + data);
338       else
339         vars.SetAt(key, data);
340     }
341 
342     sep1prev = sep1next+1;
343   } while (sep1prev != P_MAX_INDEX);
344 }
345 
346 
OutputVars(ostream & strm,const PStringToString & vars,char sep0,char sep1,char sep2,TranslationType type)347 void PURL::OutputVars(ostream & strm,
348                       const PStringToString & vars,
349                       char sep0,
350                       char sep1,
351                       char sep2,
352                       TranslationType type)
353 {
354   for (PINDEX i = 0; i < vars.GetSize(); i++) {
355     if (i > 0)
356       strm << sep1;
357     else if (sep0 != '\0')
358       strm << sep0;
359 
360     PString key  = TranslateString(vars.GetKeyAt (i), type);
361     PString data = TranslateString(vars.GetDataAt(i), type);
362 
363     if (key.IsEmpty())
364       strm << data;
365     else if (data.IsEmpty())
366       strm << key;
367     else
368       strm << key << sep2 << data;
369   }
370 }
371 
372 
InternalParse(const char * cstr,const char * defaultScheme)373 PBoolean PURL::InternalParse(const char * cstr, const char * defaultScheme)
374 {
375   scheme.MakeEmpty();
376   username.MakeEmpty();
377   password.MakeEmpty();
378   hostname.MakeEmpty();
379   port = 0;
380   portSupplied = PFalse;
381   relativePath = PFalse;
382   path.SetSize(0);
383   paramVars.RemoveAll();
384   fragment.MakeEmpty();
385   queryVars.RemoveAll();
386   m_contents.MakeEmpty();
387 
388   if (cstr == NULL)
389     return false;
390 
391   // copy the string so we can take bits off it
392   while (((*cstr & 0x80) == 0x00) && isspace(*cstr))
393     cstr++;
394   PString url = cstr;
395   if (url.IsEmpty())
396     return false;
397 
398   // get information which tells us how to parse URL for this
399   // particular scheme
400   PURLScheme * schemeInfo = NULL;
401 
402   // Character set as per RFC2396
403   //    scheme        = alpha *( alpha | digit | "+" | "-" | "." )
404   if (isalpha(url[0])) {
405     PINDEX pos = 1;
406     while (isalnum(url[pos]) || url[pos] == '+' || url[pos] == '-' || url[pos] == '.')
407       ++pos;
408 
409     // Determine if the URL has an explicit scheme
410     if (url[pos] == ':') {
411       // get the scheme information
412       schemeInfo = PURLSchemeFactory::CreateInstance(url.Left(pos));
413       if (schemeInfo != NULL)
414         url.Delete(0, pos+1);
415     }
416   }
417 
418   // if we could not match a scheme, then use the specified default scheme
419   if (schemeInfo == NULL && defaultScheme != NULL) {
420     schemeInfo = PURLSchemeFactory::CreateInstance(defaultScheme);
421     PAssert(schemeInfo != NULL, "Default scheme " + PString(defaultScheme) + " not available");
422   }
423 
424   // if that still fails, then there is nowehere to go
425   if (schemeInfo == NULL)
426     return false;
427 
428   scheme = schemeInfo->GetName();
429   return schemeInfo->Parse(url, *this) && !IsEmpty();
430 }
431 
LegacyParse(const PString & _url,const PURLLegacyScheme * schemeInfo)432 PBoolean PURL::LegacyParse(const PString & _url, const PURLLegacyScheme * schemeInfo)
433 {
434   PString url = _url;
435   PINDEX pos;
436 
437   // Super special case!
438   if (scheme *= "callto") {
439 
440     // Actually not part of MS spec, but a lot of people put in the // into
441     // the URL, so we take it out of it is there.
442     if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/')
443       url.Delete(0, 2);
444 
445     // For some bizarre reason callto uses + instead of ; for paramters
446     // We do a loop so that phone numbers of the form +61243654666 still work
447     do {
448       pos = url.Find('+');
449     } while (pos != P_MAX_INDEX && isdigit(url[pos+1]));
450 
451     if (pos != P_MAX_INDEX) {
452       SplitVars(url(pos+1, P_MAX_INDEX), paramVars, '+', '=');
453       url.Delete(pos, P_MAX_INDEX);
454     }
455 
456     hostname = paramVars("gateway");
457     if (!hostname)
458       username = UntranslateString(url, LoginTranslation);
459     else {
460       PCaselessString type = paramVars("type");
461       if (type == "directory") {
462         pos = url.Find('/');
463         if (pos == P_MAX_INDEX)
464           username = UntranslateString(url, LoginTranslation);
465         else {
466           hostname = UntranslateString(url.Left(pos), LoginTranslation);
467           username = UntranslateString(url.Mid(pos+1), LoginTranslation);
468         }
469       }
470       else {
471         // Now look for an @ and split user and host
472         pos = url.Find('@');
473         if (pos != P_MAX_INDEX) {
474           username = UntranslateString(url.Left(pos), LoginTranslation);
475           hostname = UntranslateString(url.Mid(pos+1), LoginTranslation);
476         }
477         else {
478           if (type == "ip" || type == "host")
479             hostname = UntranslateString(url, LoginTranslation);
480           else
481             username = UntranslateString(url, LoginTranslation);
482         }
483       }
484     }
485 
486     // Allow for [ipv6] form
487     pos = hostname.Find(']');
488     if (pos == P_MAX_INDEX)
489       pos = 0;
490     pos = hostname.Find(':', pos);
491     if (pos != P_MAX_INDEX) {
492       port = (WORD)hostname.Mid(pos+1).AsUnsigned();
493       portSupplied = PTrue;
494       hostname.Delete(pos, P_MAX_INDEX);
495     }
496 
497     password = paramVars("password");
498     return PTrue;
499   }
500 
501   // if the URL should have leading slash, then remove it if it has one
502   if (schemeInfo != NULL && schemeInfo->hasHostPort && schemeInfo->hasPath) {
503     if (url.GetLength() > 2 && url[0] == '/' && url[1] == '/')
504       url.Delete(0, 2);
505     else
506       relativePath = PTrue;
507   }
508 
509   // parse user/password/host/port
510   if (!relativePath && schemeInfo->hasHostPort) {
511     PString endHostChars;
512     if (schemeInfo->hasPath)
513       endHostChars += '/';
514     if (schemeInfo->hasQuery)
515       endHostChars += '?';
516     if (schemeInfo->hasParameters)
517       endHostChars += ';';
518     if (schemeInfo->hasFragments)
519       endHostChars += '#';
520     if (endHostChars.IsEmpty())
521       pos = P_MAX_INDEX;
522     else if (schemeInfo->hasUsername) {
523       //';' showing in the username field should be valid.
524       // Looking for ';' after the '@' for the parameters.
525       PINDEX posAt = url.Find('@');
526       if (posAt != P_MAX_INDEX)
527         pos = url.FindOneOf(endHostChars, posAt);
528       else
529         pos = url.FindOneOf(endHostChars);
530     }
531     else
532       pos = url.FindOneOf(endHostChars);
533 
534     PString uphp = url.Left(pos);
535     if (pos != P_MAX_INDEX)
536       url.Delete(0, pos);
537     else
538       url.MakeEmpty();
539 
540     // if the URL is of type UserPasswordHostPort, then parse it
541     if (schemeInfo->hasUsername) {
542       // extract username and password
543       PINDEX pos2 = uphp.Find('@');
544       PINDEX pos3 = P_MAX_INDEX;
545       if (schemeInfo->hasPassword)
546         pos3 = uphp.Find(':');
547       switch (pos2) {
548         case 0 :
549           uphp.Delete(0, 1);
550           break;
551 
552         case P_MAX_INDEX :
553           if (schemeInfo->defaultToUserIfNoAt) {
554             if (pos3 == P_MAX_INDEX)
555               username = UntranslateString(uphp, LoginTranslation);
556             else {
557               username = UntranslateString(uphp.Left(pos3), LoginTranslation);
558               password = UntranslateString(uphp.Mid(pos3+1), LoginTranslation);
559             }
560             uphp.MakeEmpty();
561           }
562           break;
563 
564         default :
565           if (pos3 > pos2)
566             username = UntranslateString(uphp.Left(pos2), LoginTranslation);
567           else {
568             username = UntranslateString(uphp.Left(pos3), LoginTranslation);
569             password = UntranslateString(uphp(pos3+1, pos2-1), LoginTranslation);
570           }
571           uphp.Delete(0, pos2+1);
572       }
573     }
574 
575     // if the URL does not have a port, then this is the hostname
576     if (schemeInfo->defaultPort == 0)
577       hostname = UntranslateString(uphp, LoginTranslation);
578     else {
579       // determine if the URL has a port number
580       // Allow for [ipv6] form
581       pos = uphp.Find(']');
582       if (pos == P_MAX_INDEX)
583         pos = 0;
584       pos = uphp.Find(':', pos);
585       if (pos == P_MAX_INDEX)
586         hostname = UntranslateString(uphp, LoginTranslation);
587       else {
588         hostname = UntranslateString(uphp.Left(pos), LoginTranslation);
589         port = (WORD)uphp.Mid(pos+1).AsUnsigned();
590         portSupplied = PTrue;
591       }
592 
593       if (hostname.IsEmpty() && schemeInfo->defaultHostToLocal)
594         hostname = PIPSocket::GetHostName();
595     }
596   }
597 
598   if (schemeInfo->hasQuery) {
599     // chop off any trailing query
600     pos = url.Find('?');
601     if (pos != P_MAX_INDEX) {
602       SplitQueryVars(url(pos+1, P_MAX_INDEX), queryVars);
603       url.Delete(pos, P_MAX_INDEX);
604     }
605   }
606 
607   if (schemeInfo->hasParameters) {
608     // chop off any trailing parameters
609     pos = url.Find(';');
610     if (pos != P_MAX_INDEX) {
611       SplitVars(url(pos+1, P_MAX_INDEX), paramVars);
612       url.Delete(pos, P_MAX_INDEX);
613     }
614   }
615 
616   if (schemeInfo->hasFragments) {
617     // chop off any trailing fragment
618     pos = url.Find('#');
619     if (pos != P_MAX_INDEX) {
620       fragment = UntranslateString(url(pos+1, P_MAX_INDEX), PathTranslation);
621       url.Delete(pos, P_MAX_INDEX);
622     }
623   }
624 
625   if (schemeInfo->hasPath)
626     SetPathStr(url);   // the hierarchy is what is left
627   else {
628     // if the rest of the URL isn't a path, then we are finished!
629     m_contents = UntranslateString(url, PathTranslation);
630     Recalculate();
631   }
632 
633   if (port == 0 && schemeInfo->defaultPort != 0 && !relativePath) {
634     // Yes another horrible, horrible special case!
635     if (scheme == "h323" && paramVars("type") == "gk")
636       port = DEFAULT_H323RAS_PORT;
637     else
638       port = schemeInfo->defaultPort;
639     Recalculate();
640   }
641 
642   return PTrue;
643 }
644 
645 
AsFilePath() const646 PFilePath PURL::AsFilePath() const
647 {
648   /* While it is never explicitly stated anywhere in RFC1798, there is an
649      implication RFC 1808 that the path is absolute unless the relative
650      path rules of that RFC apply. We follow that logic. */
651 
652   if (path.IsEmpty() || scheme != FILE_SCHEME || (!hostname.IsEmpty() && hostname != "localhost"))
653     return PString::Empty();
654 
655   PStringStream str;
656 
657   if (path[0].GetLength() == 2 && path[0][1] == '|')
658     str << path[0][0] << ':' << PDIR_SEPARATOR; // Special case for Windows paths with drive letter
659   else {
660     if (!relativePath)
661       str << PDIR_SEPARATOR;
662     str << path[0];
663   }
664 
665   for (PINDEX i = 1; i < path.GetSize(); i++)
666     str << PDIR_SEPARATOR << path[i];
667 
668   return str;
669 }
670 
671 
AsString(UrlFormat fmt) const672 PString PURL::AsString(UrlFormat fmt) const
673 {
674   if (fmt == FullURL)
675     return urlString;
676 
677   if (scheme.IsEmpty())
678     return PString::Empty();
679 
680   const PURLScheme * schemeInfo = PURLSchemeFactory::CreateInstance(scheme);
681   if (schemeInfo == NULL)
682     schemeInfo = PURLSchemeFactory::CreateInstance(DEFAULT_SCHEME);
683 
684   return schemeInfo->AsString(fmt, *this);
685 }
686 
LegacyAsString(PURL::UrlFormat fmt,const PURLLegacyScheme * schemeInfo) const687 PString PURL::LegacyAsString(PURL::UrlFormat fmt, const PURLLegacyScheme * schemeInfo) const
688 {
689   PStringStream str;
690 
691   if (fmt == HostPortOnly) {
692     str << scheme << ':';
693 
694     if (relativePath) {
695       if (schemeInfo->relativeImpliesScheme)
696         return PString::Empty();
697       return str;
698     }
699 
700     if (schemeInfo->hasPath && schemeInfo->hasHostPort)
701       str << "//";
702 
703     if (schemeInfo->hasUsername) {
704       if (!username) {
705         str << TranslateString(username, LoginTranslation);
706         if (schemeInfo->hasPassword && !password)
707           str << ':' << TranslateString(password, LoginTranslation);
708         str << '@';
709       }
710     }
711 
712     if (schemeInfo->hasHostPort) {
713       if (hostname.Find(':') != P_MAX_INDEX && hostname[0] != '[')
714         str << '[' << hostname << ']';
715       else
716         str << hostname;
717     }
718 
719     if (schemeInfo->defaultPort != 0) {
720       if (port != schemeInfo->defaultPort || portSupplied)
721         str << ':' << port;
722     }
723 
724     // Problem was fixed for handling legacy schema like tel URI.
725     // HostPortOnly format: if there is no default user and host fields, only the schema itself is being returned.
726     // URIOnly only format: the pathStr will be retruned.
727     // The Recalculate() will merge both HostPortOnly and URIOnly formats for the completed uri string creation.
728     if (schemeInfo->defaultToUserIfNoAt)
729       return str;
730 
731     if (str.GetLength() > scheme.GetLength()+1)
732       return str;
733 
734     // Cannot JUST have the scheme: ....
735     return PString::Empty();
736   }
737 
738   // URIOnly and PathOnly
739   if (schemeInfo->hasPath)
740     str << GetPathStr();
741   else
742     str << TranslateString(m_contents, PathTranslation);
743 
744   if (fmt == URIOnly) {
745     if (!fragment)
746       str << "#" << TranslateString(fragment, PathTranslation);
747 
748     OutputVars(str, paramVars, ';', ';', '=', ParameterTranslation);
749     OutputVars(str, queryVars, '?', '&', '=', QueryTranslation);
750   }
751 
752   return str;
753 }
754 
755 
SetScheme(const PString & s)756 void PURL::SetScheme(const PString & s)
757 {
758   scheme = s;
759   Recalculate();
760 }
761 
762 
SetUserName(const PString & u)763 void PURL::SetUserName(const PString & u)
764 {
765   username = u;
766   Recalculate();
767 }
768 
769 
SetPassword(const PString & p)770 void PURL::SetPassword(const PString & p)
771 {
772   password = p;
773   Recalculate();
774 }
775 
776 
SetHostName(const PString & h)777 void PURL::SetHostName(const PString & h)
778 {
779   hostname = h;
780   Recalculate();
781 }
782 
783 
SetPort(WORD newPort)784 void PURL::SetPort(WORD newPort)
785 {
786   port = newPort;
787   portSupplied = true;
788   Recalculate();
789 }
790 
791 
SetPathStr(const PString & pathStr)792 void PURL::SetPathStr(const PString & pathStr)
793 {
794   path = pathStr.Tokenise("/", PTrue);
795 
796   if (path.GetSize() > 0 && path[0].IsEmpty())
797     path.RemoveAt(0);
798 
799   for (PINDEX i = 0; i < path.GetSize(); i++) {
800     path[i] = UntranslateString(path[i], PathTranslation);
801     if (i > 0 && path[i] == ".." && path[i-1] != "..") {
802       path.RemoveAt(i--);
803       path.RemoveAt(i--);
804     }
805   }
806 
807   Recalculate();
808 }
809 
810 
GetPathStr() const811 PString PURL::GetPathStr() const
812 {
813   PStringStream strm;
814   for (PINDEX i = 0; i < path.GetSize(); i++) {
815     if (i > 0 || !relativePath)
816       strm << '/';
817     strm << TranslateString(path[i], PathTranslation);
818   }
819   return strm;
820 }
821 
822 
SetPath(const PStringArray & p)823 void PURL::SetPath(const PStringArray & p)
824 {
825   path = p;
826   Recalculate();
827 }
828 
829 
AppendPath(const PString & segment)830 void PURL::AppendPath(const PString & segment)
831 {
832   path.AppendString(segment);
833   Recalculate();
834 }
835 
836 
GetParameters() const837 PString PURL::GetParameters() const
838 {
839   PStringStream strm;
840   OutputVars(strm, paramVars, '\0', ';', '=', ParameterTranslation);
841   return strm;
842 }
843 
844 
SetParameters(const PString & parameters)845 void PURL::SetParameters(const PString & parameters)
846 {
847   SplitVars(parameters, paramVars);
848   Recalculate();
849 }
850 
851 
SetParamVars(const PStringToString & p)852 void PURL::SetParamVars(const PStringToString & p)
853 {
854   paramVars = p;
855   Recalculate();
856 }
857 
858 
SetParamVar(const PString & key,const PString & data,bool emptyDataDeletes)859 void PURL::SetParamVar(const PString & key, const PString & data, bool emptyDataDeletes)
860 {
861   if (emptyDataDeletes && data.IsEmpty())
862     paramVars.RemoveAt(key);
863   else
864     paramVars.SetAt(key, data);
865   Recalculate();
866 }
867 
868 
GetQuery() const869 PString PURL::GetQuery() const
870 {
871   PStringStream strm;
872   OutputVars(strm, queryVars, '\0', '&', '=', QueryTranslation);
873   return strm;
874 }
875 
876 
SetQuery(const PString & queryStr)877 void PURL::SetQuery(const PString & queryStr)
878 {
879   SplitQueryVars(queryStr, queryVars);
880   Recalculate();
881 }
882 
883 
SetQueryVars(const PStringToString & q)884 void PURL::SetQueryVars(const PStringToString & q)
885 {
886   queryVars = q;
887   Recalculate();
888 }
889 
890 
SetQueryVar(const PString & key,const PString & data)891 void PURL::SetQueryVar(const PString & key, const PString & data)
892 {
893   if (data.IsEmpty())
894     queryVars.RemoveAt(key);
895   else
896     queryVars.SetAt(key, data);
897   Recalculate();
898 }
899 
900 
SetContents(const PString & str)901 void PURL::SetContents(const PString & str)
902 {
903   m_contents = str;
904   Recalculate();
905 }
906 
907 
LoadResource(PString & str,const PString & requiredContentType) const908 bool PURL::LoadResource(PString & str, const PString & requiredContentType) const
909 {
910   PURLLoader * loader = PURLLoaderFactory::CreateInstance(GetScheme());
911   return loader != NULL && loader->Load(*this, str, requiredContentType);
912 }
913 
914 
LoadResource(PBYTEArray & data,const PString & requiredContentType) const915 bool PURL::LoadResource(PBYTEArray & data, const PString & requiredContentType) const
916 {
917   PURLLoader * loader = PURLLoaderFactory::CreateInstance(GetScheme());
918   return loader != NULL && loader->Load(*this, data, requiredContentType);
919 }
920 
921 
OpenBrowser(const PString & url)922 bool PURL::OpenBrowser(const PString & url)
923 {
924 #ifdef _WIN32
925   SHELLEXECUTEINFO sei;
926   ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));
927   sei.cbSize = sizeof(SHELLEXECUTEINFO);
928   sei.lpVerb = TEXT("open");
929   PVarString file = url;
930   sei.lpFile = file;
931 
932   if (ShellExecuteEx(&sei) != 0)
933     return true;
934 
935   PVarString msg = "Unable to open page" & url;
936   PVarString name = PProcess::Current().GetName();
937   MessageBox(NULL, msg, name, MB_TASKMODAL);
938 
939 #endif // WIN32
940   return false;
941 }
942 
943 
Recalculate()944 void PURL::Recalculate()
945 {
946   if (scheme.IsEmpty())
947     scheme = DEFAULT_SCHEME;
948 
949   urlString = AsString(HostPortOnly) + AsString(URIOnly);
950 }
951 
952 
953 ///////////////////////////////////////////////////////////////////////////////
954 
955 // RFC3966 tel URI
956 
957 class PURL_TelScheme : public PURLScheme
958 {
959     PCLASSINFO(PURL_TelScheme, PURLScheme);
960   public:
GetName() const961     virtual PString GetName() const
962     {
963       return "tel";
964     }
965 
Parse(const PString & str,PURL & url) const966     virtual PBoolean Parse(const PString & str, PURL & url) const
967     {
968       PINDEX pos = str.FindSpan("0123456789*#", str[0] != '+' ? 0 : 1);
969       if (pos == P_MAX_INDEX)
970         url.SetUserName(str);
971       else {
972         if (str[pos] != ';')
973           return false;
974 
975         url.SetUserName(str.Left(pos));
976 
977         PStringToString paramVars;
978         PURL::SplitVars(str(pos+1, P_MAX_INDEX), paramVars);
979         url.SetParamVars(paramVars);
980 
981         PString phoneContext = paramVars("phone-context");
982         if (phoneContext.IsEmpty()) {
983           if (str[0] != '+')
984             return false;
985         }
986         else if (phoneContext[0] != '+')
987           url.SetHostName(phoneContext);
988         else if (str[0] != '+')
989           url.SetUserName(phoneContext+url.GetUserName());
990         else
991           return false;
992       }
993 
994       return url.GetUserName() != "+";
995     }
996 
AsString(PURL::UrlFormat fmt,const PURL & url) const997     virtual PString AsString(PURL::UrlFormat fmt, const PURL & url) const
998     {
999       if (fmt == PURL::HostPortOnly)
1000         return PString::Empty();
1001 
1002       PStringStream strm;
1003       strm << "tel:" + url.GetUserName();
1004       PURL::OutputVars(strm, url.GetParamVars(), ';', ';', '=', PURL::ParameterTranslation);
1005       return strm;
1006     }
1007 };
1008 
1009 static PURLSchemeFactory::Worker<PURL_TelScheme> telScheme("tel", true);
1010 
1011 
1012 ///////////////////////////////////////////////////////////////////////////////
1013 
1014 // RFC2397 data URI
1015 
1016 class PURL_DataScheme : public PURLScheme
1017 {
1018     PCLASSINFO(PURL_DataScheme, PURLScheme);
1019   public:
GetName() const1020     virtual PString GetName() const
1021     {
1022       return "data";
1023     }
1024 
Parse(const PString & url,PURL & purl) const1025     virtual PBoolean Parse(const PString & url, PURL & purl) const
1026     {
1027       PINDEX comma = url.Find(',');
1028       if (comma == P_MAX_INDEX)
1029         return false;
1030 
1031       PINDEX semi = url.Find(';');
1032       if (semi > comma)
1033         purl.SetParamVar("type", url.Left(comma));
1034       else {
1035         purl.SetParameters(url(semi, comma-1));
1036         purl.SetParamVar("type", url.Left(semi));
1037       }
1038 
1039       purl.SetContents(url.Mid(comma+1));
1040 
1041       return true;
1042     }
1043 
AsString(PURL::UrlFormat fmt,const PURL & purl) const1044     virtual PString AsString(PURL::UrlFormat fmt, const PURL & purl) const
1045     {
1046       if (fmt == PURL::HostPortOnly)
1047         return PString::Empty();
1048 
1049       const PStringToString & params = purl.GetParamVars();
1050       PStringStream strm;
1051 
1052       strm << "data:" + params("type", "text/plain");
1053 
1054       bool base64 = false;
1055       for (PINDEX i = 0; i < params.GetSize(); i++) {
1056         PCaselessString key = params.GetKeyAt(i);
1057         if (key == "type")
1058           continue;
1059         if (key == "base64") {
1060           base64 = true;
1061           continue;
1062         }
1063 
1064         strm << ';' << PURL::TranslateString(key, PURL::ParameterTranslation);
1065 
1066         PString data = params.GetDataAt(i);
1067         if (!data)
1068           strm << '=' << PURL::TranslateString(data, PURL::ParameterTranslation);
1069       }
1070 
1071       // This must always be last according to EBNF
1072       if (base64)
1073         strm << ";base64";
1074 
1075       strm << ',' << PURL::TranslateString(purl.GetContents(), PURL::ParameterTranslation);
1076 
1077       return strm;
1078     }
1079 };
1080 
1081 static PURLSchemeFactory::Worker<PURL_DataScheme> dataScheme("data", true);
1082 
1083 
1084 ///////////////////////////////////////////////////////////////////////////////
1085 
1086 class PURL_FileLoader : public PURLLoader
1087 {
1088     PCLASSINFO(PURL_FileLoader, PURLLoader);
1089   public:
Load(const PURL & url,PString & str,const PString &)1090     virtual bool Load(const PURL & url, PString & str, const PString &)
1091     {
1092       PTextFile file;
1093       if (!file.Open(url.AsFilePath()))
1094         return false;
1095       if (!str.SetSize(file.GetLength()+1))
1096         return false;
1097       return file.Read(str.GetPointer(), str.GetSize()-1);
1098     }
1099 
Load(const PURL & url,PBYTEArray & data,const PString &)1100     virtual bool Load(const PURL & url, PBYTEArray & data, const PString &)
1101     {
1102       PFile file;
1103       if (!file.Open(url.AsFilePath()))
1104         return false;
1105       if (!data.SetSize(file.GetLength()))
1106         return false;
1107       return file.Read(data.GetPointer(), data.GetSize());
1108     }
1109 };
1110 
1111 PFACTORY_CREATE(PURLLoaderFactory, PURL_FileLoader, "file", true);
1112 
1113 
1114 ///////////////////////////////////////////////////////////////////////////////
1115 
1116 class PURL_DataLoader : public PURLLoader
1117 {
1118     PCLASSINFO(PURL_FileLoader, PURLLoader);
1119   public:
Load(const PURL & url,PString & str,const PString & requiredContentType)1120     virtual bool Load(const PURL & url, PString & str, const PString & requiredContentType)
1121     {
1122       if (!requiredContentType.IsEmpty()) {
1123         PCaselessString actualContentType = url.GetParamVars()("type");
1124         if (!actualContentType.IsEmpty() && requiredContentType != requiredContentType)
1125           return false;
1126       }
1127 
1128       str = url.GetContents();
1129       return true;
1130     }
1131 
Load(const PURL & url,PBYTEArray & data,const PString & requiredContentType)1132     virtual bool Load(const PURL & url, PBYTEArray & data, const PString & requiredContentType)
1133     {
1134       if (!requiredContentType.IsEmpty()) {
1135         PCaselessString actualContentType = url.GetParamVars()("type");
1136         if (!actualContentType.IsEmpty() && requiredContentType != requiredContentType)
1137           return false;
1138       }
1139 
1140       if (url.GetParamVars().Contains("base64"))
1141         return PBase64::Decode(url.GetContents(), data);
1142 
1143       PString str = url.GetContents();
1144       PINDEX len = str.GetLength();
1145       if (!data.SetSize(len))
1146         return false;
1147 
1148       memcpy(data.GetPointer(), (const char *)str, len);
1149       return true;
1150     }
1151 };
1152 
1153 PFACTORY_CREATE(PURLLoaderFactory, PURL_DataLoader, "data", true);
1154 
1155 #endif // P_URL
1156 
1157 
1158 // End Of File ///////////////////////////////////////////////////////////////
1159