URI.java revision adc854b798c1cfe3bfd4c27d68d5cee38ca617da
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.net; 19 20import java.io.IOException; 21import java.io.ObjectInputStream; 22import java.io.ObjectOutputStream; 23import java.io.Serializable; 24import java.io.UnsupportedEncodingException; 25import java.util.StringTokenizer; 26 27import org.apache.harmony.luni.util.Msg; 28 29/** 30 * This class represents an instance of a URI as defined by RFC 2396. 31 * 32 * @since Android 1.0 33 */ 34public final class URI implements Comparable<URI>, Serializable { 35 36 private static final long serialVersionUID = -6052424284110960213l; 37 38 static final String unreserved = "_-!.~\'()*"; //$NON-NLS-1$ 39 40 static final String punct = ",;:$&+="; //$NON-NLS-1$ 41 42 static final String reserved = punct + "?/[]@"; //$NON-NLS-1$ 43 44 static final String someLegal = unreserved + punct; 45 46 static final String allLegal = unreserved + reserved; 47 48 private String string; 49 50 private transient String scheme; 51 52 private transient String schemespecificpart; 53 54 private transient String authority; 55 56 private transient String userinfo; 57 58 private transient String host; 59 60 private transient int port = -1; 61 62 private transient String path; 63 64 private transient String query; 65 66 private transient String fragment; 67 68 private transient boolean opaque; 69 70 private transient boolean absolute; 71 72 private transient boolean serverAuthority = false; 73 74 private transient int hash = -1; 75 76 private URI() { 77 } 78 79 /** 80 * Creates a new URI instance according to the given string {@code uri}. 81 * 82 * @param uri 83 * the textual URI representation to be parsed into a URI object. 84 * @throws URISyntaxException 85 * if the given string {@code uri} doesn't fit to the 86 * specification RFC2396 or could not be parsed correctly. 87 * @since Android 1.0 88 */ 89 public URI(String uri) throws URISyntaxException { 90 new Helper().parseURI(uri, false); 91 } 92 93 /** 94 * Creates a new URI instance using the given arguments. This constructor 95 * first creates a temporary URI string from the given components. This 96 * string will be parsed later on to create the URI instance. 97 * <p> 98 * {@code [scheme:]scheme-specific-part[#fragment]} 99 * </p> 100 * 101 * @param scheme 102 * the scheme part of the URI. 103 * @param ssp 104 * the scheme-specific-part of the URI. 105 * @param frag 106 * the fragment part of the URI. 107 * @throws URISyntaxException 108 * if the temporary created string doesn't fit to the 109 * specification RFC2396 or could not be parsed correctly. 110 * @since Android 1.0 111 */ 112 public URI(String scheme, String ssp, String frag) 113 throws URISyntaxException { 114 StringBuffer uri = new StringBuffer(); 115 if (scheme != null) { 116 uri.append(scheme); 117 uri.append(':'); 118 } 119 if (ssp != null) { 120 // QUOTE ILLEGAL CHARACTERS 121 uri.append(quoteComponent(ssp, allLegal)); 122 } 123 if (frag != null) { 124 uri.append('#'); 125 // QUOTE ILLEGAL CHARACTERS 126 uri.append(quoteComponent(frag, allLegal)); 127 } 128 129 new Helper().parseURI(uri.toString(), false); 130 } 131 132 /** 133 * Creates a new URI instance using the given arguments. This constructor 134 * first creates a temporary URI string from the given components. This 135 * string will be parsed later on to create the URI instance. 136 * <p> 137 * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} 138 * </p> 139 * 140 * @param scheme 141 * the scheme part of the URI. 142 * @param userinfo 143 * the user information of the URI for authentication and 144 * authorization. 145 * @param host 146 * the host name of the URI. 147 * @param port 148 * the port number of the URI. 149 * @param path 150 * the path to the resource on the host. 151 * @param query 152 * the query part of the URI to specify parameters for the 153 * resource. 154 * @param fragment 155 * the fragment part of the URI. 156 * @throws URISyntaxException 157 * if the temporary created string doesn't fit to the 158 * specification RFC2396 or could not be parsed correctly. 159 * @since Android 1.0 160 */ 161 public URI(String scheme, String userinfo, String host, int port, 162 String path, String query, String fragment) 163 throws URISyntaxException { 164 165 if (scheme == null && userinfo == null && host == null && path == null 166 && query == null && fragment == null) { 167 this.path = ""; //$NON-NLS-1$ 168 return; 169 } 170 171 if (scheme != null && path != null && path.length() > 0 172 && path.charAt(0) != '/') { 173 throw new URISyntaxException(path, Msg.getString("K0302")); //$NON-NLS-1$ 174 } 175 176 StringBuffer uri = new StringBuffer(); 177 if (scheme != null) { 178 uri.append(scheme); 179 uri.append(':'); 180 } 181 182 if (userinfo != null || host != null || port != -1) { 183 uri.append("//"); //$NON-NLS-1$ 184 } 185 186 if (userinfo != null) { 187 // QUOTE ILLEGAL CHARACTERS in userinfo 188 uri.append(quoteComponent(userinfo, someLegal)); 189 uri.append('@'); 190 } 191 192 if (host != null) { 193 // check for ipv6 addresses that hasn't been enclosed 194 // in square brackets 195 if (host.indexOf(':') != -1 && host.indexOf(']') == -1 196 && host.indexOf('[') == -1) { 197 host = "[" + host + "]"; //$NON-NLS-1$ //$NON-NLS-2$ 198 } 199 uri.append(host); 200 } 201 202 if (port != -1) { 203 uri.append(':'); 204 uri.append(port); 205 } 206 207 if (path != null) { 208 // QUOTE ILLEGAL CHARS 209 uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$ 210 } 211 212 if (query != null) { 213 uri.append('?'); 214 // QUOTE ILLEGAL CHARS 215 uri.append(quoteComponent(query, allLegal)); 216 } 217 218 if (fragment != null) { 219 // QUOTE ILLEGAL CHARS 220 uri.append('#'); 221 uri.append(quoteComponent(fragment, allLegal)); 222 } 223 224 new Helper().parseURI(uri.toString(), true); 225 } 226 227 /** 228 * Creates a new URI instance using the given arguments. This constructor 229 * first creates a temporary URI string from the given components. This 230 * string will be parsed later on to create the URI instance. 231 * <p> 232 * {@code [scheme:]host[path][#fragment]} 233 * </p> 234 * 235 * @param scheme 236 * the scheme part of the URI. 237 * @param host 238 * the host name of the URI. 239 * @param path 240 * the path to the resource on the host. 241 * @param fragment 242 * the fragment part of the URI. 243 * @throws URISyntaxException 244 * if the temporary created string doesn't fit to the 245 * specification RFC2396 or could not be parsed correctly. 246 * @since Android 1.0 247 */ 248 public URI(String scheme, String host, String path, String fragment) 249 throws URISyntaxException { 250 this(scheme, null, host, -1, path, null, fragment); 251 } 252 253 /** 254 * Creates a new URI instance using the given arguments. This constructor 255 * first creates a temporary URI string from the given components. This 256 * string will be parsed later on to create the URI instance. 257 * <p> 258 * {@code [scheme:][//authority][path][?query][#fragment]} 259 * </p> 260 * 261 * @param scheme 262 * the scheme part of the URI. 263 * @param authority 264 * the authority part of the URI. 265 * @param path 266 * the path to the resource on the host. 267 * @param query 268 * the query part of the URI to specify parameters for the 269 * resource. 270 * @param fragment 271 * the fragment part of the URI. 272 * @throws URISyntaxException 273 * if the temporary created string doesn't fit to the 274 * specification RFC2396 or could not be parsed correctly. 275 * @since Android 1.0 276 */ 277 public URI(String scheme, String authority, String path, String query, 278 String fragment) throws URISyntaxException { 279 if (scheme != null && path != null && path.length() > 0 280 && path.charAt(0) != '/') { 281 throw new URISyntaxException(path, Msg.getString("K0302")); //$NON-NLS-1$ 282 } 283 284 StringBuffer uri = new StringBuffer(); 285 if (scheme != null) { 286 uri.append(scheme); 287 uri.append(':'); 288 } 289 if (authority != null) { 290 uri.append("//"); //$NON-NLS-1$ 291 // QUOTE ILLEGAL CHARS 292 uri.append(quoteComponent(authority, "@[]" + someLegal)); //$NON-NLS-1$ 293 } 294 295 if (path != null) { 296 // QUOTE ILLEGAL CHARS 297 uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$ 298 } 299 if (query != null) { 300 // QUOTE ILLEGAL CHARS 301 uri.append('?'); 302 uri.append(quoteComponent(query, allLegal)); 303 } 304 if (fragment != null) { 305 // QUOTE ILLEGAL CHARS 306 uri.append('#'); 307 uri.append(quoteComponent(fragment, allLegal)); 308 } 309 310 new Helper().parseURI(uri.toString(), false); 311 } 312 313 private class Helper { 314 315 private void parseURI(String uri, boolean forceServer) 316 throws URISyntaxException { 317 String temp = uri; 318 // assign uri string to the input value per spec 319 string = uri; 320 int index, index1, index2, index3; 321 // parse into Fragment, Scheme, and SchemeSpecificPart 322 // then parse SchemeSpecificPart if necessary 323 324 // Fragment 325 index = temp.indexOf('#'); 326 if (index != -1) { 327 // remove the fragment from the end 328 fragment = temp.substring(index + 1); 329 validateFragment(uri, fragment, index + 1); 330 temp = temp.substring(0, index); 331 } 332 333 // Scheme and SchemeSpecificPart 334 index = index1 = temp.indexOf(':'); 335 index2 = temp.indexOf('/'); 336 index3 = temp.indexOf('?'); 337 338 // if a '/' or '?' occurs before the first ':' the uri has no 339 // specified scheme, and is therefore not absolute 340 if (index != -1 && (index2 >= index || index2 == -1) 341 && (index3 >= index || index3 == -1)) { 342 // the characters up to the first ':' comprise the scheme 343 absolute = true; 344 scheme = temp.substring(0, index); 345 if (scheme.length() == 0) { 346 throw new URISyntaxException(uri, Msg.getString("K0342"), //$NON-NLS-1$ 347 index); 348 } 349 validateScheme(uri, scheme, 0); 350 schemespecificpart = temp.substring(index + 1); 351 if (schemespecificpart.length() == 0) { 352 throw new URISyntaxException(uri, Msg.getString("K0303"), //$NON-NLS-1$ 353 index + 1); 354 } 355 } else { 356 absolute = false; 357 schemespecificpart = temp; 358 } 359 360 if (scheme == null || schemespecificpart.length() > 0 361 && schemespecificpart.charAt(0) == '/') { 362 opaque = false; 363 // the URI is hierarchical 364 365 // Query 366 temp = schemespecificpart; 367 index = temp.indexOf('?'); 368 if (index != -1) { 369 query = temp.substring(index + 1); 370 temp = temp.substring(0, index); 371 validateQuery(uri, query, index2 + 1 + index); 372 } 373 374 // Authority and Path 375 if (temp.startsWith("//")) { //$NON-NLS-1$ 376 index = temp.indexOf('/', 2); 377 if (index != -1) { 378 authority = temp.substring(2, index); 379 path = temp.substring(index); 380 } else { 381 authority = temp.substring(2); 382 if (authority.length() == 0 && query == null 383 && fragment == null) { 384 throw new URISyntaxException(uri, Msg 385 .getString("K0304"), uri.length()); //$NON-NLS-1$ 386 } 387 388 path = ""; //$NON-NLS-1$ 389 // nothing left, so path is empty (not null, path should 390 // never be null) 391 } 392 393 if (authority.length() == 0) { 394 authority = null; 395 } else { 396 validateAuthority(uri, authority, index1 + 3); 397 } 398 } else { // no authority specified 399 path = temp; 400 } 401 402 int pathIndex = 0; 403 if (index2 > -1) { 404 pathIndex += index2; 405 } 406 if (index > -1) { 407 pathIndex += index; 408 } 409 validatePath(uri, path, pathIndex); 410 } else { // if not hierarchical, URI is opaque 411 opaque = true; 412 validateSsp(uri, schemespecificpart, index2 + 2 + index); 413 } 414 415 parseAuthority(forceServer); 416 } 417 418 private void validateScheme(String uri, String scheme, int index) 419 throws URISyntaxException { 420 // first char needs to be an alpha char 421 char ch = scheme.charAt(0); 422 if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) { 423 throw new URISyntaxException(uri, Msg.getString("K0305"), 0); //$NON-NLS-1$ 424 } 425 426 try { 427 URIEncoderDecoder.validateSimple(scheme, "+-."); //$NON-NLS-1$ 428 } catch (URISyntaxException e) { 429 throw new URISyntaxException(uri, Msg.getString("K0305"), index //$NON-NLS-1$ 430 + e.getIndex()); 431 } 432 } 433 434 private void validateSsp(String uri, String ssp, int index) 435 throws URISyntaxException { 436 try { 437 URIEncoderDecoder.validate(ssp, allLegal); 438 } catch (URISyntaxException e) { 439 throw new URISyntaxException(uri, Msg.getString("K0306", e //$NON-NLS-1$ 440 .getReason()), index + e.getIndex()); 441 } 442 } 443 444 private void validateAuthority(String uri, String authority, int index) 445 throws URISyntaxException { 446 try { 447 URIEncoderDecoder.validate(authority, "@[]" + someLegal); //$NON-NLS-1$ 448 } catch (URISyntaxException e) { 449 throw new URISyntaxException(uri, Msg.getString("K0307", e //$NON-NLS-1$ 450 .getReason()), index + e.getIndex()); 451 } 452 } 453 454 private void validatePath(String uri, String path, int index) 455 throws URISyntaxException { 456 try { 457 URIEncoderDecoder.validate(path, "/@" + someLegal); //$NON-NLS-1$ 458 } catch (URISyntaxException e) { 459 throw new URISyntaxException(uri, Msg.getString("K0308", e //$NON-NLS-1$ 460 .getReason()), index + e.getIndex()); 461 } 462 } 463 464 private void validateQuery(String uri, String query, int index) 465 throws URISyntaxException { 466 try { 467 URIEncoderDecoder.validate(query, allLegal); 468 } catch (URISyntaxException e) { 469 throw new URISyntaxException(uri, Msg.getString("K0309", e //$NON-NLS-1$ 470 .getReason()), index + e.getIndex()); 471 472 } 473 } 474 475 private void validateFragment(String uri, String fragment, int index) 476 throws URISyntaxException { 477 try { 478 URIEncoderDecoder.validate(fragment, allLegal); 479 } catch (URISyntaxException e) { 480 throw new URISyntaxException(uri, Msg.getString("K030a", e //$NON-NLS-1$ 481 .getReason()), index + e.getIndex()); 482 } 483 } 484 485 /** 486 * determine the host, port and userinfo if the authority parses 487 * successfully to a server based authority 488 * 489 * behavour in error cases: if forceServer is true, throw 490 * URISyntaxException with the proper diagnostic messages. if 491 * forceServer is false assume this is a registry based uri, and just 492 * return leaving the host, port and userinfo fields undefined. 493 * 494 * and there are some error cases where URISyntaxException is thrown 495 * regardless of the forceServer parameter e.g. malformed ipv6 address 496 */ 497 private void parseAuthority(boolean forceServer) 498 throws URISyntaxException { 499 if (authority == null) { 500 return; 501 } 502 503 String temp, tempUserinfo = null, tempHost = null; 504 int index, hostindex = 0; 505 int tempPort = -1; 506 507 temp = authority; 508 index = temp.indexOf('@'); 509 if (index != -1) { 510 // remove user info 511 tempUserinfo = temp.substring(0, index); 512 validateUserinfo(authority, tempUserinfo, 0); 513 temp = temp.substring(index + 1); // host[:port] is left 514 hostindex = index + 1; 515 } 516 517 index = temp.lastIndexOf(':'); 518 int endindex = temp.indexOf(']'); 519 520 if (index != -1 && endindex < index) { 521 // determine port and host 522 tempHost = temp.substring(0, index); 523 524 if (index < (temp.length() - 1)) { // port part is not empty 525 try { 526 tempPort = Integer.parseInt(temp.substring(index + 1)); 527 if (tempPort < 0) { 528 if (forceServer) { 529 throw new URISyntaxException( 530 authority, 531 Msg.getString("K00b1"), hostindex + index + 1); //$NON-NLS-1$ 532 } 533 return; 534 } 535 } catch (NumberFormatException e) { 536 if (forceServer) { 537 throw new URISyntaxException(authority, Msg 538 .getString("K00b1"), hostindex + index + 1); //$NON-NLS-1$ 539 } 540 return; 541 } 542 } 543 } else { 544 tempHost = temp; 545 } 546 547 if (tempHost.equals("")) { //$NON-NLS-1$ 548 if (forceServer) { 549 throw new URISyntaxException(authority, Msg 550 .getString("K030c"), hostindex); //$NON-NLS-1$ 551 } 552 return; 553 } 554 555 if (!isValidHost(forceServer, tempHost)) { 556 return; 557 } 558 559 // this is a server based uri, 560 // fill in the userinfo, host and port fields 561 userinfo = tempUserinfo; 562 host = tempHost; 563 port = tempPort; 564 serverAuthority = true; 565 } 566 567 private void validateUserinfo(String uri, String userinfo, int index) 568 throws URISyntaxException { 569 for (int i = 0; i < userinfo.length(); i++) { 570 char ch = userinfo.charAt(i); 571 if (ch == ']' || ch == '[') { 572 throw new URISyntaxException(uri, Msg.getString("K030d"), //$NON-NLS-1$ 573 index + i); 574 } 575 } 576 } 577 578 /** 579 * distinguish between IPv4, IPv6, domain name and validate it based on 580 * its type 581 */ 582 private boolean isValidHost(boolean forceServer, String host) 583 throws URISyntaxException { 584 if (host.charAt(0) == '[') { 585 // ipv6 address 586 if (host.charAt(host.length() - 1) != ']') { 587 throw new URISyntaxException(host, 588 Msg.getString("K030e"), 0); //$NON-NLS-1$ 589 } 590 if (!isValidIP6Address(host)) { 591 throw new URISyntaxException(host, Msg.getString("K030f")); //$NON-NLS-1$ 592 } 593 return true; 594 } 595 596 // '[' and ']' can only be the first char and last char 597 // of the host name 598 if (host.indexOf('[') != -1 || host.indexOf(']') != -1) { 599 throw new URISyntaxException(host, Msg.getString("K0310"), 0); //$NON-NLS-1$ 600 } 601 602 int index = host.lastIndexOf('.'); 603 if (index < 0 || index == host.length() - 1 604 || !Character.isDigit(host.charAt(index + 1))) { 605 // domain name 606 if (isValidDomainName(host)) { 607 return true; 608 } 609 if (forceServer) { 610 throw new URISyntaxException(host, 611 Msg.getString("K0310"), 0); //$NON-NLS-1$ 612 } 613 return false; 614 } 615 616 // IPv4 address 617 if (isValidIPv4Address(host)) { 618 return true; 619 } 620 if (forceServer) { 621 throw new URISyntaxException(host, Msg.getString("K0311"), 0); //$NON-NLS-1$ 622 } 623 return false; 624 } 625 626 private boolean isValidDomainName(String host) { 627 try { 628 URIEncoderDecoder.validateSimple(host, "-."); //$NON-NLS-1$ 629 } catch (URISyntaxException e) { 630 return false; 631 } 632 633 String label = null; 634 StringTokenizer st = new StringTokenizer(host, "."); //$NON-NLS-1$ 635 while (st.hasMoreTokens()) { 636 label = st.nextToken(); 637 if (label.startsWith("-") || label.endsWith("-")) { //$NON-NLS-1$ //$NON-NLS-2$ 638 return false; 639 } 640 } 641 642 if (!label.equals(host)) { 643 char ch = label.charAt(0); 644 if (ch >= '0' && ch <= '9') { 645 return false; 646 } 647 } 648 return true; 649 } 650 651 private boolean isValidIPv4Address(String host) { 652 int index; 653 int index2; 654 try { 655 int num; 656 index = host.indexOf('.'); 657 num = Integer.parseInt(host.substring(0, index)); 658 if (num < 0 || num > 255) { 659 return false; 660 } 661 index2 = host.indexOf('.', index + 1); 662 num = Integer.parseInt(host.substring(index + 1, index2)); 663 if (num < 0 || num > 255) { 664 return false; 665 } 666 index = host.indexOf('.', index2 + 1); 667 num = Integer.parseInt(host.substring(index2 + 1, index)); 668 if (num < 0 || num > 255) { 669 return false; 670 } 671 num = Integer.parseInt(host.substring(index + 1)); 672 if (num < 0 || num > 255) { 673 return false; 674 } 675 } catch (Exception e) { 676 return false; 677 } 678 return true; 679 } 680 681 private boolean isValidIP6Address(String ipAddress) { 682 int length = ipAddress.length(); 683 boolean doubleColon = false; 684 int numberOfColons = 0; 685 int numberOfPeriods = 0; 686 String word = ""; //$NON-NLS-1$ 687 char c = 0; 688 char prevChar = 0; 689 int offset = 0; // offset for [] ip addresses 690 691 if (length < 2) { 692 return false; 693 } 694 695 for (int i = 0; i < length; i++) { 696 prevChar = c; 697 c = ipAddress.charAt(i); 698 switch (c) { 699 700 // case for an open bracket [x:x:x:...x] 701 case '[': 702 if (i != 0) { 703 return false; // must be first character 704 } 705 if (ipAddress.charAt(length - 1) != ']') { 706 return false; // must have a close ] 707 } 708 if ((ipAddress.charAt(1) == ':') 709 && (ipAddress.charAt(2) != ':')) { 710 return false; 711 } 712 offset = 1; 713 if (length < 4) { 714 return false; 715 } 716 break; 717 718 // case for a closed bracket at end of IP [x:x:x:...x] 719 case ']': 720 if (i != length - 1) { 721 return false; // must be last charcter 722 } 723 if (ipAddress.charAt(0) != '[') { 724 return false; // must have a open [ 725 } 726 break; 727 728 // case for the last 32-bits represented as IPv4 729 // x:x:x:x:x:x:d.d.d.d 730 case '.': 731 numberOfPeriods++; 732 if (numberOfPeriods > 3) { 733 return false; 734 } 735 if (!isValidIP4Word(word)) { 736 return false; 737 } 738 if (numberOfColons != 6 && !doubleColon) { 739 return false; 740 } 741 // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons 742 // with 743 // an IPv4 ending, otherwise 7 :'s is bad 744 if (numberOfColons == 7 745 && ipAddress.charAt(0 + offset) != ':' 746 && ipAddress.charAt(1 + offset) != ':') { 747 return false; 748 } 749 word = ""; //$NON-NLS-1$ 750 break; 751 752 case ':': 753 numberOfColons++; 754 if (numberOfColons > 7) { 755 return false; 756 } 757 if (numberOfPeriods > 0) { 758 return false; 759 } 760 if (prevChar == ':') { 761 if (doubleColon) { 762 return false; 763 } 764 doubleColon = true; 765 } 766 word = ""; //$NON-NLS-1$ 767 break; 768 769 default: 770 if (word.length() > 3) { 771 return false; 772 } 773 if (!isValidHexChar(c)) { 774 return false; 775 } 776 word += c; 777 } 778 } 779 780 // Check if we have an IPv4 ending 781 if (numberOfPeriods > 0) { 782 if (numberOfPeriods != 3 || !isValidIP4Word(word)) { 783 return false; 784 } 785 } else { 786 // If we're at then end and we haven't had 7 colons then there 787 // is a problem unless we encountered a doubleColon 788 if (numberOfColons != 7 && !doubleColon) { 789 return false; 790 } 791 792 // If we have an empty word at the end, it means we ended in 793 // either a : or a . 794 // If we did not end in :: then this is invalid 795 if (word == "" && ipAddress.charAt(length - 1 - offset) != ':' //$NON-NLS-1$ 796 && ipAddress.charAt(length - 2 - offset) != ':') { 797 return false; 798 } 799 } 800 801 return true; 802 } 803 804 private boolean isValidIP4Word(String word) { 805 char c; 806 if (word.length() < 1 || word.length() > 3) { 807 return false; 808 } 809 for (int i = 0; i < word.length(); i++) { 810 c = word.charAt(i); 811 if (!(c >= '0' && c <= '9')) { 812 return false; 813 } 814 } 815 if (Integer.parseInt(word) > 255) { 816 return false; 817 } 818 return true; 819 } 820 821 private boolean isValidHexChar(char c) { 822 823 return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') 824 || (c >= 'a' && c <= 'f'); 825 } 826 } 827 828 /* 829 * Quote illegal chars for each component, but not the others 830 * 831 * @param component java.lang.String the component to be converted @param 832 * legalset java.lang.String the legal character set allowed in the 833 * component s @return java.lang.String the converted string 834 */ 835 private String quoteComponent(String component, String legalset) { 836 try { 837 /* 838 * Use a different encoder than URLEncoder since: 1. chars like "/", 839 * "#", "@" etc needs to be preserved instead of being encoded, 2. 840 * UTF-8 char set needs to be used for encoding instead of default 841 * platform one 842 */ 843 return URIEncoderDecoder.quoteIllegal(component, legalset); 844 } catch (UnsupportedEncodingException e) { 845 throw new RuntimeException(e.toString()); 846 } 847 } 848 849 /** 850 * Compares this URI with the given argument {@code uri}. This method will 851 * return a negative value if this URI instance is less than the given 852 * argument and a positive value if this URI instance is greater than the 853 * given argument. The return value {@code 0} indicates that the two 854 * instances represent the same URI. To define the order the single parts of 855 * the URI are compared with each other. String components will be orderer 856 * in the natural case-sensitive way. A hierarchical URI is less than an 857 * opaque URI and if one part is {@code null} the URI with the undefined 858 * part is less than the other one. 859 * 860 * @param uri 861 * the URI this instance has to compare with. 862 * @return the value representing the order of the two instances. 863 * @since Android 1.0 864 */ 865 public int compareTo(URI uri) { 866 int ret = 0; 867 868 // compare schemes 869 if (scheme == null && uri.scheme != null) { 870 return -1; 871 } else if (scheme != null && uri.scheme == null) { 872 return 1; 873 } else if (scheme != null && uri.scheme != null) { 874 ret = scheme.compareToIgnoreCase(uri.scheme); 875 if (ret != 0) { 876 return ret; 877 } 878 } 879 880 // compare opacities 881 if (!opaque && uri.opaque) { 882 return -1; 883 } else if (opaque && !uri.opaque) { 884 return 1; 885 } else if (opaque && uri.opaque) { 886 ret = schemespecificpart.compareTo(uri.schemespecificpart); 887 if (ret != 0) { 888 return ret; 889 } 890 } else { 891 892 // otherwise both must be hierarchical 893 894 // compare authorities 895 if (authority != null && uri.authority == null) { 896 return 1; 897 } else if (authority == null && uri.authority != null) { 898 return -1; 899 } else if (authority != null && uri.authority != null) { 900 if (host != null && uri.host != null) { 901 // both are server based, so compare userinfo, host, port 902 if (userinfo != null && uri.userinfo == null) { 903 return 1; 904 } else if (userinfo == null && uri.userinfo != null) { 905 return -1; 906 } else if (userinfo != null && uri.userinfo != null) { 907 ret = userinfo.compareTo(uri.userinfo); 908 if (ret != 0) { 909 return ret; 910 } 911 } 912 913 // userinfo's are the same, compare hostname 914 ret = host.compareToIgnoreCase(uri.host); 915 if (ret != 0) { 916 return ret; 917 } 918 919 // compare port 920 if (port != uri.port) { 921 return port - uri.port; 922 } 923 } else { // one or both are registry based, compare the whole 924 // authority 925 ret = authority.compareTo(uri.authority); 926 if (ret != 0) { 927 return ret; 928 } 929 } 930 } 931 932 // authorities are the same 933 // compare paths 934 ret = path.compareTo(uri.path); 935 if (ret != 0) { 936 return ret; 937 } 938 939 // compare queries 940 941 if (query != null && uri.query == null) { 942 return 1; 943 } else if (query == null && uri.query != null) { 944 return -1; 945 } else if (query != null && uri.query != null) { 946 ret = query.compareTo(uri.query); 947 if (ret != 0) { 948 return ret; 949 } 950 } 951 } 952 953 // everything else is identical, so compare fragments 954 if (fragment != null && uri.fragment == null) { 955 return 1; 956 } else if (fragment == null && uri.fragment != null) { 957 return -1; 958 } else if (fragment != null && uri.fragment != null) { 959 ret = fragment.compareTo(uri.fragment); 960 if (ret != 0) { 961 return ret; 962 } 963 } 964 965 // identical 966 return 0; 967 } 968 969 /** 970 * Parses the given argument {@code uri} and creates an appropriate URI 971 * instance. 972 * 973 * @param uri 974 * the string which has to be parsed to create the URI instance. 975 * @return the created instance representing the given URI. 976 * @since Android 1.0 977 */ 978 public static URI create(String uri) { 979 URI result = null; 980 try { 981 result = new URI(uri); 982 } catch (URISyntaxException e) { 983 throw new IllegalArgumentException(e.getMessage()); 984 } 985 return result; 986 } 987 988 private URI duplicate() { 989 URI clone = new URI(); 990 clone.absolute = absolute; 991 clone.authority = authority; 992 clone.fragment = fragment; 993 clone.host = host; 994 clone.opaque = opaque; 995 clone.path = path; 996 clone.port = port; 997 clone.query = query; 998 clone.scheme = scheme; 999 clone.schemespecificpart = schemespecificpart; 1000 clone.userinfo = userinfo; 1001 clone.serverAuthority = serverAuthority; 1002 return clone; 1003 } 1004 1005 /* 1006 * Takes a string that may contain hex sequences like %F1 or %2b and 1007 * converts the hex values following the '%' to lowercase 1008 */ 1009 private String convertHexToLowerCase(String s) { 1010 StringBuffer result = new StringBuffer(""); //$NON-NLS-1$ 1011 if (s.indexOf('%') == -1) { 1012 return s; 1013 } 1014 1015 int index = 0, previndex = 0; 1016 while ((index = s.indexOf('%', previndex)) != -1) { 1017 result.append(s.substring(previndex, index + 1)); 1018 result.append(s.substring(index + 1, index + 3).toLowerCase()); 1019 index += 3; 1020 previndex = index; 1021 } 1022 return result.toString(); 1023 } 1024 1025 /* 1026 * Takes two strings that may contain hex sequences like %F1 or %2b and 1027 * compares them, ignoring case for the hex values hex values must always 1028 * occur in pairs like above 1029 */ 1030 private boolean equalsHexCaseInsensitive(String first, String second) { 1031 if (first.indexOf('%') != second.indexOf('%')) { 1032 return first.equals(second); 1033 } 1034 1035 int index = 0, previndex = 0; 1036 while ((index = first.indexOf('%', previndex)) != -1 1037 && second.indexOf('%', previndex) == index) { 1038 boolean match = first.substring(previndex, index).equals( 1039 second.substring(previndex, index)); 1040 if (!match) { 1041 return false; 1042 } 1043 1044 match = first.substring(index + 1, index + 3).equalsIgnoreCase( 1045 second.substring(index + 1, index + 3)); 1046 if (!match) { 1047 return false; 1048 } 1049 1050 index += 3; 1051 previndex = index; 1052 } 1053 return first.substring(previndex).equals(second.substring(previndex)); 1054 } 1055 1056 /** 1057 * Compares this URI instance with the given argument {@code o} and 1058 * determines if both are equal. Two URI instances are equal if all single 1059 * parts are identical in their meaning. 1060 * 1061 * @param o 1062 * the URI this instance has to be compared with. 1063 * @return {@code true} if both URI instances point to the same resource, 1064 * {@code false} otherwise. 1065 * @since Android 1.0 1066 */ 1067 @Override 1068 public boolean equals(Object o) { 1069 if (!(o instanceof URI)) { 1070 return false; 1071 } 1072 URI uri = (URI) o; 1073 1074 if (uri.fragment == null && fragment != null || uri.fragment != null 1075 && fragment == null) { 1076 return false; 1077 } else if (uri.fragment != null && fragment != null) { 1078 if (!equalsHexCaseInsensitive(uri.fragment, fragment)) { 1079 return false; 1080 } 1081 } 1082 1083 if (uri.scheme == null && scheme != null || uri.scheme != null 1084 && scheme == null) { 1085 return false; 1086 } else if (uri.scheme != null && scheme != null) { 1087 if (!uri.scheme.equalsIgnoreCase(scheme)) { 1088 return false; 1089 } 1090 } 1091 1092 if (uri.opaque && opaque) { 1093 return equalsHexCaseInsensitive(uri.schemespecificpart, 1094 schemespecificpart); 1095 } else if (!uri.opaque && !opaque) { 1096 if (!equalsHexCaseInsensitive(path, uri.path)) { 1097 return false; 1098 } 1099 1100 if (uri.query != null && query == null || uri.query == null 1101 && query != null) { 1102 return false; 1103 } else if (uri.query != null && query != null) { 1104 if (!equalsHexCaseInsensitive(uri.query, query)) { 1105 return false; 1106 } 1107 } 1108 1109 if (uri.authority != null && authority == null 1110 || uri.authority == null && authority != null) { 1111 return false; 1112 } else if (uri.authority != null && authority != null) { 1113 if (uri.host != null && host == null || uri.host == null 1114 && host != null) { 1115 return false; 1116 } else if (uri.host == null && host == null) { 1117 // both are registry based, so compare the whole authority 1118 return equalsHexCaseInsensitive(uri.authority, authority); 1119 } else { // uri.host != null && host != null, so server-based 1120 if (!host.equalsIgnoreCase(uri.host)) { 1121 return false; 1122 } 1123 1124 if (port != uri.port) { 1125 return false; 1126 } 1127 1128 if (uri.userinfo != null && userinfo == null 1129 || uri.userinfo == null && userinfo != null) { 1130 return false; 1131 } else if (uri.userinfo != null && userinfo != null) { 1132 return equalsHexCaseInsensitive(userinfo, uri.userinfo); 1133 } else { 1134 return true; 1135 } 1136 } 1137 } else { 1138 // no authority 1139 return true; 1140 } 1141 1142 } else { 1143 // one is opaque, the other hierarchical 1144 return false; 1145 } 1146 } 1147 1148 /** 1149 * Gets the decoded authority part of this URI. 1150 * 1151 * @return the decoded authority part or {@code null} if undefined. 1152 * @since Android 1.0 1153 */ 1154 public String getAuthority() { 1155 return decode(authority); 1156 } 1157 1158 /** 1159 * Gets the decoded fragment part of this URI. 1160 * 1161 * @return the decoded fragment part or {@code null} if undefined. 1162 * @since Android 1.0 1163 */ 1164 public String getFragment() { 1165 return decode(fragment); 1166 } 1167 1168 /** 1169 * Gets the host part of this URI. 1170 * 1171 * @return the host part or {@code null} if undefined. 1172 * @since Android 1.0 1173 */ 1174 public String getHost() { 1175 return host; 1176 } 1177 1178 /** 1179 * Gets the decoded path part of this URI. 1180 * 1181 * @return the decoded path part or {@code null} if undefined. 1182 * @since Android 1.0 1183 */ 1184 public String getPath() { 1185 return decode(path); 1186 } 1187 1188 /** 1189 * Gets the port number of this URI. 1190 * 1191 * @return the port number or {@code -1} if undefined. 1192 * @since Android 1.0 1193 */ 1194 public int getPort() { 1195 return port; 1196 } 1197 1198 /** 1199 * Gets the decoded query part of this URI. 1200 * 1201 * @return the decoded query part or {@code null} if undefined. 1202 * @since Android 1.0 1203 */ 1204 public String getQuery() { 1205 return decode(query); 1206 } 1207 1208 /** 1209 * Gets the authority part of this URI in raw form. 1210 * 1211 * @return the encoded authority part or {@code null} if undefined. 1212 * @since Android 1.0 1213 */ 1214 public String getRawAuthority() { 1215 return authority; 1216 } 1217 1218 /** 1219 * Gets the fragment part of this URI in raw form. 1220 * 1221 * @return the encoded fragment part or {@code null} if undefined. 1222 * @since Android 1.0 1223 */ 1224 public String getRawFragment() { 1225 return fragment; 1226 } 1227 1228 /** 1229 * Gets the path part of this URI in raw form. 1230 * 1231 * @return the encoded path part or {@code null} if undefined. 1232 * @since Android 1.0 1233 */ 1234 public String getRawPath() { 1235 return path; 1236 } 1237 1238 /** 1239 * Gets the query part of this URI in raw form. 1240 * 1241 * @return the encoded query part or {@code null} if undefined. 1242 * @since Android 1.0 1243 */ 1244 public String getRawQuery() { 1245 return query; 1246 } 1247 1248 /** 1249 * Gets the scheme-specific part of this URI in raw form. 1250 * 1251 * @return the encoded scheme-specific part or {@code null} if undefined. 1252 * @since Android 1.0 1253 */ 1254 public String getRawSchemeSpecificPart() { 1255 return schemespecificpart; 1256 } 1257 1258 /** 1259 * Gets the user-info part of this URI in raw form. 1260 * 1261 * @return the encoded user-info part or {@code null} if undefined. 1262 * @since Android 1.0 1263 */ 1264 public String getRawUserInfo() { 1265 return userinfo; 1266 } 1267 1268 /** 1269 * Gets the scheme part of this URI. 1270 * 1271 * @return the scheme part or {@code null} if undefined. 1272 * @since Android 1.0 1273 */ 1274 public String getScheme() { 1275 return scheme; 1276 } 1277 1278 /** 1279 * Gets the decoded scheme-specific part of this URI. 1280 * 1281 * @return the decoded scheme-specific part or {@code null} if undefined. 1282 * @since Android 1.0 1283 */ 1284 public String getSchemeSpecificPart() { 1285 return decode(schemespecificpart); 1286 } 1287 1288 /** 1289 * Gets the decoded user-info part of this URI. 1290 * 1291 * @return the decoded user-info part or {@code null} if undefined. 1292 * @since Android 1.0 1293 */ 1294 public String getUserInfo() { 1295 return decode(userinfo); 1296 } 1297 1298 /** 1299 * Gets the hashcode value of this URI instance. 1300 * 1301 * @return the appropriate hashcode value. 1302 * @since Android 1.0 1303 */ 1304 @Override 1305 public int hashCode() { 1306 if (hash == -1) { 1307 hash = getHashString().hashCode(); 1308 } 1309 return hash; 1310 } 1311 1312 /** 1313 * Indicates whether this URI is absolute, which means that a scheme part is 1314 * defined in this URI. 1315 * 1316 * @return {@code true} if this URI is absolute, {@code false} otherwise. 1317 * @since Android 1.0 1318 */ 1319 public boolean isAbsolute() { 1320 return absolute; 1321 } 1322 1323 /** 1324 * Indicates whether this URI is opaque or not. An opaque URI is absolute 1325 * and has a scheme-specific part which does not start with a slash 1326 * character. All parts except scheme, scheme-specific and fragment are 1327 * undefined. 1328 * 1329 * @return {@code true} if the URI is opaque, {@code false} otherwise. 1330 * @since Android 1.0 1331 */ 1332 public boolean isOpaque() { 1333 return opaque; 1334 } 1335 1336 /* 1337 * normalize path, and return the resulting string 1338 */ 1339 private String normalize(String path) { 1340 // count the number of '/'s, to determine number of segments 1341 int index = -1; 1342 int pathlen = path.length(); 1343 int size = 0; 1344 if (pathlen > 0 && path.charAt(0) != '/') { 1345 size++; 1346 } 1347 while ((index = path.indexOf('/', index + 1)) != -1) { 1348 if (index + 1 < pathlen && path.charAt(index + 1) != '/') { 1349 size++; 1350 } 1351 } 1352 1353 String[] seglist = new String[size]; 1354 boolean[] include = new boolean[size]; 1355 1356 // break the path into segments and store in the list 1357 int current = 0; 1358 int index2 = 0; 1359 index = (pathlen > 0 && path.charAt(0) == '/') ? 1 : 0; 1360 while ((index2 = path.indexOf('/', index + 1)) != -1) { 1361 seglist[current++] = path.substring(index, index2); 1362 index = index2 + 1; 1363 } 1364 1365 // if current==size, then the last character was a slash 1366 // and there are no more segments 1367 if (current < size) { 1368 seglist[current] = path.substring(index); 1369 } 1370 1371 // determine which segments get included in the normalized path 1372 for (int i = 0; i < size; i++) { 1373 include[i] = true; 1374 if (seglist[i].equals("..")) { //$NON-NLS-1$ 1375 int remove = i - 1; 1376 // search back to find a segment to remove, if possible 1377 while (remove > -1 && !include[remove]) { 1378 remove--; 1379 } 1380 // if we find a segment to remove, remove it and the ".." 1381 // segment 1382 if (remove > -1 && !seglist[remove].equals("..")) { //$NON-NLS-1$ 1383 include[remove] = false; 1384 include[i] = false; 1385 } 1386 } else if (seglist[i].equals(".")) { //$NON-NLS-1$ 1387 include[i] = false; 1388 } 1389 } 1390 1391 // put the path back together 1392 StringBuffer newpath = new StringBuffer(); 1393 if (path.startsWith("/")) { //$NON-NLS-1$ 1394 newpath.append('/'); 1395 } 1396 1397 for (int i = 0; i < seglist.length; i++) { 1398 if (include[i]) { 1399 newpath.append(seglist[i]); 1400 newpath.append('/'); 1401 } 1402 } 1403 1404 // if we used at least one segment and the path previously ended with 1405 // a slash and the last segment is still used, then delete the extra 1406 // trailing '/' 1407 if (!path.endsWith("/") && seglist.length > 0 //$NON-NLS-1$ 1408 && include[seglist.length - 1]) { 1409 newpath.deleteCharAt(newpath.length() - 1); 1410 } 1411 1412 String result = newpath.toString(); 1413 1414 // check for a ':' in the first segment if one exists, 1415 // prepend "./" to normalize 1416 index = result.indexOf(':'); 1417 index2 = result.indexOf('/'); 1418 if (index != -1 && (index < index2 || index2 == -1)) { 1419 newpath.insert(0, "./"); //$NON-NLS-1$ 1420 result = newpath.toString(); 1421 } 1422 return result; 1423 } 1424 1425 /** 1426 * Normalizes the path part of this URI. 1427 * 1428 * @return an URI object which represents this instance with a normalized 1429 * path. 1430 * @since Android 1.0 1431 */ 1432 public URI normalize() { 1433 if (opaque) { 1434 return this; 1435 } 1436 String normalizedPath = normalize(path); 1437 // if the path is already normalized, return this 1438 if (path.equals(normalizedPath)) { 1439 return this; 1440 } 1441 // get an exact copy of the URI re-calculate the scheme specific part 1442 // since the path of the normalized URI is different from this URI. 1443 URI result = duplicate(); 1444 result.path = normalizedPath; 1445 result.setSchemeSpecificPart(); 1446 return result; 1447 } 1448 1449 /** 1450 * Tries to parse the authority component of this URI to divide it into the 1451 * host, port, and user-info. If this URI is already determined as a 1452 * ServerAuthority this instance will be returned without changes. 1453 * 1454 * @return this instance with the components of the parsed server authority. 1455 * @throws URISyntaxException 1456 * if the authority part could not be parsed as a server-based 1457 * authority. 1458 * @since Android 1.0 1459 */ 1460 public URI parseServerAuthority() throws URISyntaxException { 1461 if (!serverAuthority) { 1462 new Helper().parseAuthority(true); 1463 } 1464 return this; 1465 } 1466 1467 /** 1468 * Makes the given URI {@code relative} to a relative URI against the URI 1469 * represented by this instance. 1470 * 1471 * @param relative 1472 * the URI which has to be relativized against this URI. 1473 * @return the relative URI. 1474 * @since Android 1.0 1475 */ 1476 public URI relativize(URI relative) { 1477 if (relative.opaque || opaque) { 1478 return relative; 1479 } 1480 1481 if (scheme == null ? relative.scheme != null : !scheme 1482 .equals(relative.scheme)) { 1483 return relative; 1484 } 1485 1486 if (authority == null ? relative.authority != null : !authority 1487 .equals(relative.authority)) { 1488 return relative; 1489 } 1490 1491 // normalize both paths 1492 String thisPath = normalize(path); 1493 String relativePath = normalize(relative.path); 1494 1495 /* 1496 * if the paths aren't equal, then we need to determine if this URI's 1497 * path is a parent path (begins with) the relative URI's path 1498 */ 1499 if (!thisPath.equals(relativePath)) { 1500 // if this URI's path doesn't end in a '/', add one 1501 if (!thisPath.endsWith("/")) { //$NON-NLS-1$ 1502 thisPath = thisPath + '/'; 1503 } 1504 /* 1505 * if the relative URI's path doesn't start with this URI's path, 1506 * then just return the relative URI; the URIs have nothing in 1507 * common 1508 */ 1509 if (!relativePath.startsWith(thisPath)) { 1510 return relative; 1511 } 1512 } 1513 1514 URI result = new URI(); 1515 result.fragment = relative.fragment; 1516 result.query = relative.query; 1517 // the result URI is the remainder of the relative URI's path 1518 result.path = relativePath.substring(thisPath.length()); 1519 return result; 1520 } 1521 1522 /** 1523 * Resolves the given URI {@code relative} against the URI represented by 1524 * this instance. 1525 * 1526 * @param relative 1527 * the URI which has to be resolved against this URI. 1528 * @return the resolved URI. 1529 * @since Android 1.0 1530 */ 1531 public URI resolve(URI relative) { 1532 if (relative.absolute || opaque) { 1533 return relative; 1534 } 1535 1536 URI result; 1537 if (relative.path.equals("") && relative.scheme == null //$NON-NLS-1$ 1538 && relative.authority == null && relative.query == null 1539 && relative.fragment != null) { 1540 // if the relative URI only consists of fragment, 1541 // the resolved URI is very similar to this URI, 1542 // except that it has the fragement from the relative URI. 1543 result = duplicate(); 1544 result.fragment = relative.fragment; 1545 // no need to re-calculate the scheme specific part, 1546 // since fragment is not part of scheme specific part. 1547 return result; 1548 } 1549 1550 if (relative.authority != null) { 1551 // if the relative URI has authority, 1552 // the resolved URI is almost the same as the relative URI, 1553 // except that it has the scheme of this URI. 1554 result = relative.duplicate(); 1555 result.scheme = scheme; 1556 result.absolute = absolute; 1557 } else { 1558 // since relative URI has no authority, 1559 // the resolved URI is very similar to this URI, 1560 // except that it has the query and fragment of the relative URI, 1561 // and the path is different. 1562 result = duplicate(); 1563 result.fragment = relative.fragment; 1564 result.query = relative.query; 1565 if (relative.path.startsWith("/")) { //$NON-NLS-1$ 1566 result.path = relative.path; 1567 } else { 1568 // resolve a relative reference 1569 int endindex = path.lastIndexOf('/') + 1; 1570 result.path = normalize(path.substring(0, endindex) 1571 + relative.path); 1572 } 1573 // re-calculate the scheme specific part since 1574 // query and path of the resolved URI is different from this URI. 1575 result.setSchemeSpecificPart(); 1576 } 1577 return result; 1578 } 1579 1580 /** 1581 * Helper method used to re-calculate the scheme specific part of the 1582 * resolved or normalized URIs 1583 */ 1584 private void setSchemeSpecificPart() { 1585 // ssp = [//authority][path][?query] 1586 StringBuffer ssp = new StringBuffer(); 1587 if (authority != null) { 1588 ssp.append("//" + authority); //$NON-NLS-1$ 1589 } 1590 if (path != null) { 1591 ssp.append(path); 1592 } 1593 if (query != null) { 1594 ssp.append("?" + query); //$NON-NLS-1$ 1595 } 1596 schemespecificpart = ssp.toString(); 1597 // reset string, so that it can be re-calculated correctly when asked. 1598 string = null; 1599 } 1600 1601 /** 1602 * Creates a new URI instance by parsing the given string {@code relative} 1603 * and resolves the created URI against the URI represented by this 1604 * instance. 1605 * 1606 * @param relative 1607 * the given string to create the new URI instance which has to 1608 * be resolved later on. 1609 * @return the created and resolved URI. 1610 * @since Android 1.0 1611 */ 1612 public URI resolve(String relative) { 1613 return resolve(create(relative)); 1614 } 1615 1616 /* 1617 * Encode unicode chars that are not part of US-ASCII char set into the 1618 * escaped form 1619 * 1620 * i.e. The Euro currency symbol is encoded as "%E2%82%AC". 1621 * 1622 * @param component java.lang.String the component to be converted @param 1623 * legalset java.lang.String the legal character set allowed in the 1624 * component s @return java.lang.String the converted string 1625 */ 1626 private String encodeOthers(String s) { 1627 try { 1628 /* 1629 * Use a different encoder than URLEncoder since: 1. chars like "/", 1630 * "#", "@" etc needs to be preserved instead of being encoded, 2. 1631 * UTF-8 char set needs to be used for encoding instead of default 1632 * platform one 3. Only other chars need to be converted 1633 */ 1634 return URIEncoderDecoder.encodeOthers(s); 1635 } catch (UnsupportedEncodingException e) { 1636 throw new RuntimeException(e.toString()); 1637 } 1638 } 1639 1640 private String decode(String s) { 1641 if (s == null) { 1642 return s; 1643 } 1644 1645 try { 1646 return URIEncoderDecoder.decode(s); 1647 } catch (UnsupportedEncodingException e) { 1648 throw new RuntimeException(e.toString()); 1649 } 1650 } 1651 1652 /** 1653 * Returns the textual string representation of this URI instance using the 1654 * US-ASCII encoding. 1655 * 1656 * @return the US-ASCII string representation of this URI. 1657 * @since Android 1.0 1658 */ 1659 public String toASCIIString() { 1660 return encodeOthers(toString()); 1661 } 1662 1663 /** 1664 * Returns the textual string representation of this URI instance. 1665 * 1666 * @return the textual string representation of this URI. 1667 * @since Android 1.0 1668 */ 1669 @Override 1670 public String toString() { 1671 if (string == null) { 1672 StringBuffer result = new StringBuffer(); 1673 if (scheme != null) { 1674 result.append(scheme); 1675 result.append(':'); 1676 } 1677 if (opaque) { 1678 result.append(schemespecificpart); 1679 } else { 1680 if (authority != null) { 1681 result.append("//"); //$NON-NLS-1$ 1682 result.append(authority); 1683 } 1684 1685 if (path != null) { 1686 result.append(path); 1687 } 1688 1689 if (query != null) { 1690 result.append('?'); 1691 result.append(query); 1692 } 1693 } 1694 1695 if (fragment != null) { 1696 result.append('#'); 1697 result.append(fragment); 1698 } 1699 1700 string = result.toString(); 1701 } 1702 return string; 1703 } 1704 1705 /* 1706 * Form a string from the components of this URI, similarly to the 1707 * toString() method. But this method converts scheme and host to lowercase, 1708 * and converts escaped octets to lowercase. 1709 */ 1710 private String getHashString() { 1711 StringBuffer result = new StringBuffer(); 1712 if (scheme != null) { 1713 result.append(scheme.toLowerCase()); 1714 result.append(':'); 1715 } 1716 if (opaque) { 1717 result.append(schemespecificpart); 1718 } else { 1719 if (authority != null) { 1720 result.append("//"); //$NON-NLS-1$ 1721 if (host == null) { 1722 result.append(authority); 1723 } else { 1724 if (userinfo != null) { 1725 result.append(userinfo + "@"); //$NON-NLS-1$ 1726 } 1727 result.append(host.toLowerCase()); 1728 if (port != -1) { 1729 result.append(":" + port); //$NON-NLS-1$ 1730 } 1731 } 1732 } 1733 1734 if (path != null) { 1735 result.append(path); 1736 } 1737 1738 if (query != null) { 1739 result.append('?'); 1740 result.append(query); 1741 } 1742 } 1743 1744 if (fragment != null) { 1745 result.append('#'); 1746 result.append(fragment); 1747 } 1748 1749 return convertHexToLowerCase(result.toString()); 1750 } 1751 1752 /** 1753 * Converts this URI instance to a URL. 1754 * 1755 * @return the created URL representing the same resource as this URI. 1756 * @throws MalformedURLException 1757 * if an error occurs while creating the URL or no protocol 1758 * handler could be found. 1759 * @since Android 1.0 1760 */ 1761 public URL toURL() throws MalformedURLException { 1762 if (!absolute) { 1763 throw new IllegalArgumentException(Msg.getString("K0312") + ": " //$NON-NLS-1$//$NON-NLS-2$ 1764 + toString()); 1765 } 1766 return new URL(toString()); 1767 } 1768 1769 private void readObject(ObjectInputStream in) throws IOException, 1770 ClassNotFoundException { 1771 in.defaultReadObject(); 1772 try { 1773 new Helper().parseURI(string, false); 1774 } catch (URISyntaxException e) { 1775 throw new IOException(e.toString()); 1776 } 1777 } 1778 1779 private void writeObject(ObjectOutputStream out) throws IOException, 1780 ClassNotFoundException { 1781 // call toString() to ensure the value of string field is calculated 1782 toString(); 1783 out.defaultWriteObject(); 1784 } 1785} 1786