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