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 }