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