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