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 }