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