1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Initial release: April 2004
8 
9         author:         Kris
10 
11 *******************************************************************************/
12 
13 module tango.net.Uri;
14 
15 public  import  tango.net.model.UriView;
16 
17 //public alias Uri UriView;
18 
19 private import  tango.core.Exception;
20 
21 private import  Integer = tango.text.convert.Integer;
22 
23 private import  tango.stdc.string : memchr;
24 
25 /*******************************************************************************
26 
27         Implements an RFC 2396 compliant URI specification. See
28         <A HREF="http://ftp.ics.uci.edu/pub/ietf/uri/rfc2396.txt">this page</A>
29         for more information.
30 
31         The implementation fails the spec on two counts: it doesn't insist
32         on a scheme being present in the Uri, and it doesn't implement the
33         "Relative References" support noted in section 5.2. The latter can
34         be found in tango.util.PathUtil instead.
35 
36         Note that IRI support can be implied by assuming each of userinfo,
37         path, query, and fragment are UTF-8 encoded
38         (see <A HREF="http://www.w3.org/2001/Talks/0912-IUC-IRI/paper.html">
39         this page</A> for further details).
40 
41 *******************************************************************************/
42 
43 class Uri : UriView
44 {
45         // simplistic string appender
46         private alias scope size_t delegate(const(void)[]) Consumer;
47 
48         /// old method names
49         public alias port        getPort;
50         public alias defaultPort getDefaultPort;
51         public alias scheme      getScheme;
52         public alias host        getHost;
53         public alias validPort   getValidPort;
54         public alias userinfo    getUserInfo;
55         public alias path        getPath;
56         public alias query       getQuery;
57         public alias fragment    getFragment;
58         public alias port        setPort;
59         public alias scheme      setScheme;
60         public alias host        setHost;
61         public alias userinfo    setUserInfo;
62         public alias query       setQuery;
63         public alias path        setPath;
64         public alias fragment    setFragment;
65 
66         public enum {InvalidPort = -1}
67 
68         private int             port_;
69         private const(char)[]   host_,
70                                 path_,
71                                 query_,
72                                 scheme_,
73                                 userinfo_,
74                                 fragment_;
75         private HeapSlice       decoded;
76 
77         private __gshared ubyte[]    map;
78 
79         private __gshared short[immutable(char)[]] genericSchemes;
80 
81         private __gshared immutable immutable(char)[] hexDigits = "0123456789abcdef";
82 
83         private __gshared const SchemePort[] schemePorts =
84                 [
85                 {"coffee",      80},
86                 {"file",        InvalidPort},
87                 {"ftp",         21},
88                 {"gopher",      70},
89                 {"hnews",       80},
90                 {"http",        80},
91                 {"http-ng",     80},
92                 {"https",       443},
93                 {"imap",        143},
94                 {"irc",         194},
95                 {"ldap",        389},
96                 {"news",        119},
97                 {"nfs",         2049},
98                 {"nntp",        119},
99                 {"pop",         110},
100                 {"rwhois",      4321},
101                 {"shttp",       80},
102                 {"smtp",        25},
103                 {"snews",       563},
104                 {"telnet",      23},
105                 {"wais",        210},
106                 {"whois",       43},
107                 {"whois++",     43},
108                 ];
109 
110         public enum
111         {
112                 ExcScheme       = 0x01,
113                 ExcAuthority    = 0x02,
114                 ExcPath         = 0x04,
115                 IncUser         = 0x80,         // encode spec for User
116                 IncPath         = 0x10,         // encode spec for Path
117                 IncQuery        = 0x20,         // encode spec for Query
118                 IncQueryAll     = 0x40,
119                 IncScheme       = 0x80,         // encode spec for Scheme
120                 IncGeneric      = IncScheme |
121                                   IncUser   |
122                                   IncPath   |
123                                   IncQuery  |
124                                   IncQueryAll
125         }
126 
127         // scheme and port pairs
128         private struct SchemePort
129         {
130                 const(char)[]  name;
131                 short   port;
132         }
133 
134         /***********************************************************************
135 
136                 Initialize the Uri character maps and so on
137 
138         ***********************************************************************/
139 
140         shared static this ()
141         {
142                 // Map known generic schemes to their default port. Specify
143                 // InvalidPort for those schemes that don't use ports. Note
144                 // that a port value of zero is not supported ...
145                 foreach (SchemePort sp; schemePorts)
146                          genericSchemes[sp.name] = sp.port;
147                 genericSchemes.rehash();
148 
149                 map = new ubyte[256];
150 
151                 // load the character map with valid symbols
152                 for (int i='a'; i <= 'z'; ++i)
153                      map[i] = IncGeneric;
154 
155                 for (int i='A'; i <= 'Z'; ++i)
156                      map[i] = IncGeneric;
157 
158                 for (int i='0'; i<='9'; ++i)
159                      map[i] = IncGeneric;
160 
161                 // exclude these from parsing elements
162                 map[':'] |= ExcScheme;
163                 map['/'] |= ExcScheme | ExcAuthority;
164                 map['?'] |= ExcScheme | ExcAuthority | ExcPath;
165                 map['#'] |= ExcScheme | ExcAuthority | ExcPath;
166 
167                 // include these as common (unreserved) symbols
168                 map['-'] |= IncUser | IncQuery | IncQueryAll | IncPath;
169                 map['_'] |= IncUser | IncQuery | IncQueryAll | IncPath;
170                 map['.'] |= IncUser | IncQuery | IncQueryAll | IncPath;
171                 map['!'] |= IncUser | IncQuery | IncQueryAll | IncPath;
172                 map['~'] |= IncUser | IncQuery | IncQueryAll | IncPath;
173                 map['*'] |= IncUser | IncQuery | IncQueryAll | IncPath;
174                 map['\''] |= IncUser | IncQuery | IncQueryAll | IncPath;
175                 map['('] |= IncUser | IncQuery | IncQueryAll | IncPath;
176                 map[')'] |= IncUser | IncQuery | IncQueryAll | IncPath;
177 
178                 // include these as scheme symbols
179                 map['+'] |= IncScheme;
180                 map['-'] |= IncScheme;
181                 map['.'] |= IncScheme;
182 
183                 // include these as userinfo symbols
184                 map[';'] |= IncUser;
185                 map[':'] |= IncUser;
186                 map['&'] |= IncUser;
187                 map['='] |= IncUser;
188                 map['+'] |= IncUser;
189                 map['$'] |= IncUser;
190                 map[','] |= IncUser;
191 
192                 // include these as path symbols
193                 map['/'] |= IncPath;
194                 map[';'] |= IncPath;
195                 map[':'] |= IncPath;
196                 map['@'] |= IncPath;
197                 map['&'] |= IncPath;
198                 map['='] |= IncPath;
199                 map['+'] |= IncPath;
200                 map['$'] |= IncPath;
201                 map[','] |= IncPath;
202 
203                 // include these as query symbols
204                 map[';'] |= IncQuery | IncQueryAll;
205                 map['/'] |= IncQuery | IncQueryAll;
206                 map[':'] |= IncQuery | IncQueryAll;
207                 map['@'] |= IncQuery | IncQueryAll;
208                 map['='] |= IncQuery | IncQueryAll;
209                 map['$'] |= IncQuery | IncQueryAll;
210                 map[','] |= IncQuery | IncQueryAll;
211 
212                 // '%' are permitted inside queries when constructing output
213                 map['%'] |= IncQueryAll;
214                 map['?'] |= IncQueryAll;
215                 map['&'] |= IncQueryAll;
216         }
217 
218         /***********************************************************************
219 
220                 Create an empty Uri
221 
222         ***********************************************************************/
223 
224         this ()
225         {
226                 port_ = InvalidPort;
227                 decoded.expand (512);
228         }
229 
230         /***********************************************************************
231 
232                 Construct a Uri from the provided character string
233 
234         ***********************************************************************/
235 
236         this (const(char)[] uri)
237         {
238                 this ();
239                 parse (uri);
240         }
241 
242         /***********************************************************************
243 
244                 Construct a Uri from the given components. The query is
245                 optional.
246 
247         ***********************************************************************/
248 
249         this (const(char)[] scheme, const(char)[] host, const(char)[] path, const(char)[] query = null)
250         {
251                 this ();
252 
253                 this.scheme_ = scheme;
254                 this.query_ = query;
255                 this.host_ = host;
256                 this.path_ = path;
257         }
258 
259         /***********************************************************************
260 
261                 Clone another Uri. This can be used to make a mutable Uri
262                 from an immutable UriView.
263 
264         ***********************************************************************/
265 
266         this (UriView other)
267         {
268                 with (other)
269                      {
270                      this (getScheme(), getHost(), getPath(), getQuery());
271                      this.userinfo_ = getUserInfo();
272                      this.fragment_ = getFragment();
273                      this.port_ = getPort();
274                      }
275         }
276 
277         /***********************************************************************
278 
279                 Return the default port for the given scheme. InvalidPort
280                 is returned if the scheme is unknown, or does not accept
281                 a port.
282 
283         ***********************************************************************/
284 
285         override final const int defaultPort (const(char)[] scheme)
286         {
287                 short* port = scheme in genericSchemes;
288                 if (port is null)
289                     return InvalidPort;
290                 return *port;
291         }
292 
293         /***********************************************************************
294 
295                 Return the parsed scheme, or null if the scheme was not
296                 specified
297 
298         ***********************************************************************/
299 
300         @property override final const const(char)[] scheme()
301         {
302                 return scheme_;
303         }
304 
305         /***********************************************************************
306 
307                 Return the parsed host, or null if the host was not
308                 specified
309 
310         ***********************************************************************/
311 
312         @property override final const const(char)[] host()
313         {
314                 return host_;
315         }
316 
317         /***********************************************************************
318 
319                 Return the parsed port number, or InvalidPort if the port
320                 was not provided.
321 
322         ***********************************************************************/
323 
324         @property override final const int port()
325         {
326                 return port_;
327         }
328 
329         /***********************************************************************
330 
331                 Return a valid port number by performing a lookup on the
332                 known schemes if the port was not explicitly specified.
333 
334         ***********************************************************************/
335 
336         override final const int validPort()
337         {
338                 if (port_ is InvalidPort)
339                     return defaultPort (scheme_);
340                 return port_;
341         }
342 
343         /***********************************************************************
344 
345                 Return the parsed userinfo, or null if userinfo was not
346                 provided.
347 
348         ***********************************************************************/
349 
350         @property override final const const(char)[] userinfo()
351         {
352                 return userinfo_;
353         }
354 
355         /***********************************************************************
356 
357                 Return the parsed path, or null if the path was not
358                 provided.
359 
360         ***********************************************************************/
361 
362         @property override final const const(char)[] path()
363         {
364                 return path_;
365         }
366 
367         /***********************************************************************
368 
369                 Return the parsed query, or null if a query was not
370                 provided.
371 
372         ***********************************************************************/
373 
374         @property override final const const(char)[] query()
375         {
376                 return query_;
377         }
378 
379         /***********************************************************************
380 
381                 Return the parsed fragment, or null if a fragment was not
382                 provided.
383 
384         ***********************************************************************/
385 
386         @property override final const const(char)[] fragment()
387         {
388                 return fragment_;
389         }
390 
391         /***********************************************************************
392 
393                 Return whether or not the Uri scheme is considered generic.
394 
395         ***********************************************************************/
396 
397         @property override final const bool isGeneric ()
398         {
399                 return (scheme_ in genericSchemes) !is null;
400         }
401 
402         /***********************************************************************
403 
404                 Emit the content of this Uri via the provided Consumer. The
405                 output is constructed per RFC 2396.
406 
407         ***********************************************************************/
408 
409         final const size_t produce (Consumer consume)
410         {
411                 size_t ret;
412 
413                 if (scheme_.length)
414                     ret += consume (scheme_), ret += consume (":");
415 
416 
417                 if (userinfo_.length || host_.length || port_ != InvalidPort)
418                    {
419                    ret += consume ("//");
420 
421                    if (userinfo_.length)
422                        ret += encode (consume, userinfo_, IncUser), ret +=consume ("@");
423 
424                    if (host_.length)
425                        ret += consume (host_);
426 
427                    if (port_ != InvalidPort && port_ != getDefaultPort(scheme_))
428                       {
429                       char[8] tmp;
430                       ret += consume (":"), ret += consume (Integer.itoa (tmp, cast(uint) port_));
431                       }
432                    }
433 
434                 if (path_.length)
435                     ret += encode (consume, path_, IncPath);
436 
437                 if (query_.length)
438                    {
439                    ret += consume ("?");
440                    ret += encode (consume, query_, IncQueryAll);
441                    }
442 
443                 if (fragment_.length)
444                    {
445                    ret += consume ("#");
446                    ret += encode (consume, fragment_, IncQuery);
447                    }
448 
449                 return ret;
450         }
451 
452         /***********************************************************************
453 
454                 Emit the content of this Uri via the provided Consumer. The
455                 output is constructed per RFC 2396.
456 
457         ***********************************************************************/
458 
459         override final string toString ()
460         {
461                 immutable(void)[] s;
462 
463                 s.length = 256, s.length = 0;
464                 produce ((const(void)[] v) {s ~= v; return v.length;});
465                 return cast(immutable(char)[]) s;
466         }
467 
468         /***********************************************************************
469 
470                 Encode uri characters into a Consumer, such that
471                 reserved chars are converted into their %hex version.
472 
473         ***********************************************************************/
474 
475         static size_t encode (Consumer consume, const(char)[] s, int flags)
476         {
477                 size_t  ret;
478                 char[3] hex;
479                 size_t  mark;
480 
481                 hex[0] = '%';
482                 foreach (i, char c; s)
483                         {
484                         if (! (map[c] & flags))
485                            {
486                            ret += consume (s[mark..i]);
487                            mark = i+1;
488 
489                            hex[1] = hexDigits [(c >> 4) & 0x0f];
490                            hex[2] = hexDigits [c & 0x0f];
491                            ret += consume (hex);
492                            }
493                         }
494 
495                 // add trailing section
496                 if (mark < s.length)
497                     ret += consume (s[mark..s.length]);
498 
499                 return ret;
500         }
501 
502         /***********************************************************************
503 
504                 Encode uri characters into a string, such that reserved
505                 chars are converted into their %hex version.
506 
507                 Returns a dup'd string
508 
509         ***********************************************************************/
510 
511         static char[] encode (const(char)[] text, int flags)
512         {
513                 void[] s;
514                 encode ((const(void)[] v) {s ~= v; return v.length;}, text, flags);
515                 return cast(char[]) s;
516         }
517 
518         /***********************************************************************
519 
520                 Decode a character string with potential %hex values in it.
521                 The decoded strings are placed into a thread-safe expanding
522                 buffer, and a slice of it is returned to the caller.
523 
524         ***********************************************************************/
525 
526         private const(char)[] decoder (const(char)[] s, char ignore=0)
527         {
528                 static int toInt (char c)
529                 {
530                         if (c >= '0' && c <= '9')
531                             c -= '0';
532                         else
533                         if (c >= 'a' && c <= 'f')
534                             c -= ('a' - 10);
535                         else
536                         if (c >= 'A' && c <= 'F')
537                             c -= ('A' - 10);
538                         return c;
539                 }
540 
541                 auto length = s.length;
542 
543                 // take a peek first, to see if there's work to do
544                 if (length && memchr (s.ptr, '%', length))
545                    {
546                    char* p;
547                    int   j;
548 
549                    // ensure we have enough decoding space available
550                    p = cast(char*) decoded.expand (length);
551 
552                    // scan string, stripping % encodings as we go
553                    for (auto i = 0; i < length; ++i, ++j, ++p)
554                        {
555                        int c = s[i];
556 
557                        if (c is '%' && (i+2) < length)
558                           {
559                           c = toInt(s[i+1]) * 16 + toInt(s[i+2]);
560 
561                           // leave ignored escapes in the stream,
562                           // permitting escaped '&' to remain in
563                           // the query string
564                           if (c && (c is ignore))
565                               c = '%';
566                           else
567                              i += 2;
568                           }
569 
570                        *p = cast(char)c;
571                        }
572 
573                    // return a slice from the decoded input
574                    return cast(char[]) decoded.slice (j);
575                    }
576 
577                 // return original content
578                 return s;
579         }
580 
581         /***********************************************************************
582 
583                 Decode a duplicated string with potential %hex values in it
584 
585         ***********************************************************************/
586 
587         final char[] decode (const(char)[] s)
588         {
589                 return decoder(s).dup;
590         }
591 
592         /***********************************************************************
593 
594                 Parsing is performed according to RFC 2396
595 
596                 <pre>
597                   ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
598                    12            3  4          5       6  7        8 9
599 
600                 2 isolates scheme
601                 4 isolates authority
602                 5 isolates path
603                 7 isolates query
604                 9 isolates fragment
605                 </pre>
606 
607                 This was originally a state-machine; it turned out to be a
608                 lot faster (~40%) when unwound like this instead.
609 
610         ***********************************************************************/
611 
612         final Uri parse (const(char)[] uri, bool relative = false)
613         {
614                 char    c;
615                 size_t  i,
616                         mark;
617                 auto    prefix = path_;
618                 auto    len = uri.length;
619 
620                 if (! relative)
621                       reset();
622 
623                 // isolate scheme (note that it's OK to not specify a scheme)
624                 for (i=0; i < len && !(map[c = uri[i]] & ExcScheme); ++i) {}
625                 if (c is ':')
626                    {
627                    /* Bad dup? */
628                    auto lower_uri = uri [mark .. i].dup;
629                    toLower (lower_uri);
630                    scheme_ = lower_uri;
631                    mark = i + 1;
632                    }
633 
634                 // isolate authority
635                 if (mark < len-1 && uri[mark] is '/' && uri[mark+1] is '/')
636                    {
637                    for (mark+=2, i=mark; i < len && !(map[uri[i]] & ExcAuthority); ++i) {}
638                    parseAuthority (uri[mark .. i]);
639                    mark = i;
640                    }
641                 else
642                    if (relative)
643                       {
644                       auto head = (uri[0] is '/') ? host_ : toLastSlash(prefix);
645                       query_ = fragment_ = null;
646                       uri = head ~ uri;
647                       len = uri.length;
648                       mark = head.length;
649                       }
650 
651                 // isolate path
652                 for (i=mark; i < len && !(map[uri[i]] & ExcPath); ++i) {}
653                 path_ = decoder (uri[mark .. i]);
654                 mark = i;
655 
656                 // isolate query
657                 if (mark < len && uri[mark] is '?')
658                    {
659                    for (++mark, i=mark; i < len && uri[i] != '#'; ++i) {}
660                    query_ = decoder (uri[mark .. i], '&');
661                    mark = i;
662                    }
663 
664                 // isolate fragment
665                 if (mark < len && uri[mark] is '#')
666                     fragment_ = decoder (uri[mark+1 .. len]);
667 
668                 return this;
669         }
670 
671         /***********************************************************************
672 
673                 Clear everything to null.
674 
675         ***********************************************************************/
676 
677         final void reset()
678         {
679                 decoded.reset();
680                 port_ = InvalidPort;
681                 host_ = path_ = query_ = scheme_ = userinfo_ = fragment_ = null;
682         }
683 
684         /***********************************************************************
685 
686                 Parse the given uri, with support for relative URLs
687 
688         ***********************************************************************/
689 
690         final Uri relParse (const(char)[] uri)
691         {
692                 return parse (uri, true);
693         }
694 
695         /***********************************************************************
696 
697                 Set the Uri scheme
698 
699         ***********************************************************************/
700 
701         @property final Uri scheme (const(char)[] scheme)
702         {
703                 this.scheme_ = scheme;
704                 return this;
705         }
706 
707         /***********************************************************************
708 
709                 Set the Uri host
710 
711         ***********************************************************************/
712 
713         @property final Uri host (const(char)[] host)
714         {
715                 this.host_ = host;
716                 return this;
717         }
718 
719         /***********************************************************************
720 
721                 Set the Uri port
722 
723         ***********************************************************************/
724 
725         @property final Uri port (int port)
726         {
727                 this.port_ = port;
728                 return this;
729         }
730 
731         /***********************************************************************
732 
733                 Set the Uri userinfo
734 
735         ***********************************************************************/
736 
737         @property final Uri userinfo (const(char)[] userinfo)
738         {
739                 this.userinfo_ = userinfo;
740                 return this;
741         }
742 
743         /***********************************************************************
744 
745                 Set the Uri query
746 
747         ***********************************************************************/
748 
749         @property final Uri query (const(char)[] query)
750         {
751                 this.query_ = query;
752                 return this;
753         }
754 
755         /***********************************************************************
756 
757                 Extend the Uri query
758 
759         ***********************************************************************/
760 
761         final const(char)[] extendQuery (const(char)[] tail)
762         {
763                 if (tail.length)
764                 {
765                     if (query_.length)
766                         query_ = query_ ~ "&" ~ tail;
767                     else
768                        query_ = tail;
769                 }
770                 return query_;
771         }
772 
773         /***********************************************************************
774 
775                 Set the Uri path
776 
777         ***********************************************************************/
778 
779         @property final Uri path (const(char)[] path)
780         {
781                 this.path_ = path;
782                 return this;
783         }
784 
785         /***********************************************************************
786 
787                 Set the Uri fragment
788 
789         ***********************************************************************/
790 
791         @property final Uri fragment (const(char)[] fragment)
792         {
793                 this.fragment_ = fragment;
794                 return this;
795         }
796 
797         /***********************************************************************
798 
799                 Authority is the section after the scheme, but before the
800                 path, query or fragment; it typically represents a host.
801 
802                 ---
803                     ^(([^@]*)@?)([^:]*)?(:(.*))?
804                      12         3       4 5
805 
806                 2 isolates userinfo
807                 3 isolates host
808                 5 isolates port
809                 ---
810 
811         ***********************************************************************/
812 
813         private void parseAuthority (const(char)[] auth)
814         {
815                 size_t  mark,
816                         len = auth.length;
817 
818                 // get userinfo: (([^@]*)@?)
819                 foreach (i, char c; auth)
820                          if (c is '@')
821                             {
822                             userinfo_ = decoder (auth[0 .. i]);
823                             mark = i + 1;
824                             break;
825                             }
826 
827                 // get port: (:(.*))?
828                 for (size_t i=mark; i < len; ++i)
829                      if (auth [i] is ':')
830                         {
831                         port_ = Integer.atoi (auth [i+1 .. len]);
832                         len = i;
833                         break;
834                         }
835 
836                 // get host: ([^:]*)?
837                 host_ = auth [mark..len];
838         }
839 
840         /**********************************************************************
841 
842         **********************************************************************/
843 
844         private final static T[] toLastSlash (T)(T[] path)
845         {
846                 if (path.ptr)
847                     for (auto p = path.ptr+path.length; --p >= path.ptr;)
848                          if (*p is '/')
849                              return path [0 .. (p-path.ptr)+1];
850                 return path;
851         }
852 
853         /**********************************************************************
854 
855                 in-place conversion to lowercase
856 
857         **********************************************************************/
858 
859         private final static char[] toLower (ref char[] src)
860         {
861                 foreach (ref char c; src)
862                          if (c >= 'A' && c <= 'Z')
863                              c = cast(char)(c + ('a' - 'A'));
864                 return src;
865         }
866 }
867 
868 
869 /*******************************************************************************
870 
871 *******************************************************************************/
872 
873 private struct HeapSlice
874 {
875         private size_t    used;
876         private void[]  buffer;
877 
878         /***********************************************************************
879 
880                 Reset content length to zero
881 
882         ***********************************************************************/
883 
884         final void reset ()
885         {
886                 used = 0;
887         }
888 
889         /***********************************************************************
890 
891                 Potentially expand the content space, and return a pointer
892                 to the start of the empty section.
893 
894         ***********************************************************************/
895 
896         final void* expand (size_t size)
897         {
898                 auto len = used + size;
899                 if (len > buffer.length)
900                     buffer.length = len + len/2;
901 
902                 return &buffer [used];
903         }
904 
905         /***********************************************************************
906 
907                 Return a slice of the content from the current position
908                 with the specified size. Adjusts the current position to
909                 point at an empty zone.
910 
911         ***********************************************************************/
912 
913         final void[] slice (int size)
914         {
915                 size_t i = used;
916                 used += size;
917                 return buffer [i..used];
918         }
919 }
920 
921 /*******************************************************************************
922 
923     Unittest
924 
925 *******************************************************************************/
926 
927 debug (UnitTest)
928 {
929     import tango.util.log.Trace;
930 
931 unittest
932 {
933     auto uri = new Uri;
934     auto uristring = "http://www.example.com/click.html/c=37571:RoS_Intern_search-link3_LB_Sky_Rec/b=98983:news-time-search-link_leader_neu/l=68%7C%7C%7C%7Cde/url=http://ads.ad4max.com/adclick.aspx?id=cf722624-efd5-4b10-ad53-88a5872a8873&pubad=b9c8acc4-e396-4b0b-b665-8bb3078128e6&avid=963171985&adcpc=xrH%2f%2bxVeFaPVkbVCMufB5A%3d%3d&a1v=6972657882&a1lang=de&a1ou=http%3a%2f%2fad.search.ch%2fiframe_ad.html%3fcampaignname%3dRoS_Intern_search-link3_LB_Sky_Rec%26bannername%3dnews-time-search-link_leader_neu%26iframeid%3dsl_if1%26content%3dvZLLbsIwEEX3%2bQo3aqUW1XEgkAckSJRuKqEuoDuELD%2bmiSEJyDEE%2fr7h0cKm6q6SF9bVjH3uzI3vMOaVYdpgPIwrodXGIHPYQGIb2BuyZDt2Vu2hRUh8Nx%2b%2fjj5Gc4u0UNAJ95H7jCbAJGi%2bZlqix3eoqyfUIhaT3YLtabpVEiXI5pEImRBdDF7k4y53Oea%2b38Mh554bhO1OCL49%2bO6qlTTZsa3546pmoNLMHOXIvaoapNIgTnpmzKZPCJNOBUyLzBEZEbkSKyczRU5E4gW9oN2frmf0rTSgS3quw7kqVx6dvNDZ6kCnIAhPojAKvX7Z%2bMFGFYBvKml%2bskxL2JI88cOHYPxzJJCtzpP79pXQaCZWqkxppcVvlDsF9b9CqiJNLiB1Xd%2bQqIKlUBHXSdWnjQbN1heLoRWTcwz%2bCAlqLCZXg5VzHoEj1gW5XJeVffOcFR8TCKVs8vcF%26crc%3dac8cc2fa9ec2e2de9d242345c2d40c25";
935 
936 
937     uri.parse(uristring);
938 
939     with(uri)
940     {
941         assert(scheme == "http");
942         assert(host == "www.example.com");
943         assert(port == InvalidPort);
944         assert(userinfo == null);
945         assert(fragment == null);
946         assert(path == "/click.html/c=37571:RoS_Intern_search-link3_LB_Sky_Rec/b=98983:news-time-search-link_leader_neu/l=68||||de/url=http://ads.ad4max.com/adclick.aspx");
947         assert(query == "id=cf722624-efd5-4b10-ad53-88a5872a8873&pubad=b9c8acc4-e396-4b0b-b665-8bb3078128e6&avid=963171985&adcpc=xrH/+xVeFaPVkbVCMufB5A==&a1v=6972657882&a1lang=de&a1ou=http://ad.search.ch/iframe_ad.html?campaignname=RoS_Intern_search-link3_LB_Sky_Rec%26bannername=news-time-search-link_leader_neu%26iframeid=sl_if1%26content=vZLLbsIwEEX3+Qo3aqUW1XEgkAckSJRuKqEuoDuELD+miSEJyDEE/r7h0cKm6q6SF9bVjH3uzI3vMOaVYdpgPIwrodXGIHPYQGIb2BuyZDt2Vu2hRUh8Nx+/jj5Gc4u0UNAJ95H7jCbAJGi+Zlqix3eoqyfUIhaT3YLtabpVEiXI5pEImRBdDF7k4y53Oea+38Mh554bhO1OCL49+O6qlTTZsa3546pmoNLMHOXIvaoapNIgTnpmzKZPCJNOBUyLzBEZEbkSKyczRU5E4gW9oN2frmf0rTSgS3quw7kqVx6dvNDZ6kCnIAhPojAKvX7Z+MFGFYBvKml+skxL2JI88cOHYPxzJJCtzpP79pXQaCZWqkxppcVvlDsF9b9CqiJNLiB1Xd+QqIKlUBHXSdWnjQbN1heLoRWTcwz+CAlqLCZXg5VzHoEj1gW5XJeVffOcFR8TCKVs8vcF%26crc=ac8cc2fa9ec2e2de9d242345c2d40c25");
948 
949 
950         parse("psyc://example.net/~marenz?what#_presence");
951 
952         assert(scheme == "psyc");
953         assert(host == "example.net");
954         assert(port == InvalidPort);
955         assert(fragment == "_presence");
956         assert(path == "/~marenz");
957         assert(query == "what");
958 
959     }
960 
961     //Cout (uri).newline;
962     //Cout (uri.encode ("&#$%", uri.IncQuery)).newline;
963 
964 }
965 
966 }