1 /* 2 * Copyright (c) 1998, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.www; 27 28 import java.io.File; 29 import java.net.URL; 30 import java.net.MalformedURLException; 31 import java.net.URI; 32 import java.net.URISyntaxException; 33 import java.nio.ByteBuffer; 34 import java.nio.CharBuffer; 35 import java.nio.charset.CharacterCodingException; 36 37 import sun.nio.cs.ThreadLocalCoders; 38 import java.nio.charset.CharsetDecoder; 39 import java.nio.charset.CoderResult; 40 import java.nio.charset.CodingErrorAction; 41 42 /** 43 * A class that contains useful routines common to sun.net.www 44 * @author Mike McCloskey 45 */ 46 47 public final class ParseUtil { 48 49 private ParseUtil() {} 50 51 /** 52 * Constructs an encoded version of the specified path string suitable 53 * for use in the construction of a URL. 54 * 55 * A path separator is replaced by a forward slash. The string is UTF8 56 * encoded. The % escape sequence is used for characters that are above 57 * 0x7F or those defined in RFC2396 as reserved or excluded in the path 58 * component of a URL. 59 */ 60 public static String encodePath(String path) { 61 return encodePath(path, true); 62 } 63 /* 64 * flag indicates whether path uses platform dependent 65 * File.separatorChar or not. True indicates path uses platform 66 * dependent File.separatorChar. 67 */ 68 public static String encodePath(String path, boolean flag) { 69 if (flag && File.separatorChar != '/') { 70 return encodePath(path, 0, File.separatorChar); 71 } else { 72 int index = firstEncodeIndex(path); 73 if (index > -1) { 74 return encodePath(path, index, '/'); 75 } else { 76 return path; 77 } 78 } 79 } 80 81 private static int firstEncodeIndex(String path) { 82 int len = path.length(); 83 for (int i = 0; i < len; i++) { 84 char c = path.charAt(i); 85 // Ordering in the following test is performance sensitive, 86 // and typically paths have most chars in the a-z range, then 87 // in the symbol range '&'-':' (includes '.', '/' and '0'-'9') 88 // and more rarely in the A-Z range. 89 if (c >= 'a' && c <= 'z' || 90 c >= '&' && c <= ':' || 91 c >= 'A' && c <= 'Z') { 92 continue; 93 } else if (c > 0x007F || match(c, L_ENCODED, H_ENCODED)) { 94 return i; 95 } 96 } 97 return -1; 98 } 99 100 private static String encodePath(String path, int index, char sep) { 101 char[] pathCC = path.toCharArray(); 102 char[] retCC = new char[pathCC.length * 2 + 16 - index]; 103 if (index > 0) { 104 System.arraycopy(pathCC, 0, retCC, 0, index); 105 } 106 int retLen = index; 107 108 for (int i = index; i < pathCC.length; i++) { 109 char c = pathCC[i]; 110 if (c == sep) 111 retCC[retLen++] = '/'; 112 else { 113 if (c <= 0x007F) { 114 if (c >= 'a' && c <= 'z' || 115 c >= 'A' && c <= 'Z' || 116 c >= '0' && c <= '9') { 117 retCC[retLen++] = c; 118 } else if (match(c, L_ENCODED, H_ENCODED)) { 119 retLen = escape(retCC, c, retLen); 120 } else { 121 retCC[retLen++] = c; 122 } 123 } else if (c > 0x07FF) { 124 retLen = escape(retCC, (char)(0xE0 | ((c >> 12) & 0x0F)), retLen); 125 retLen = escape(retCC, (char)(0x80 | ((c >> 6) & 0x3F)), retLen); 126 retLen = escape(retCC, (char)(0x80 | ((c >> 0) & 0x3F)), retLen); 127 } else { 128 retLen = escape(retCC, (char)(0xC0 | ((c >> 6) & 0x1F)), retLen); 129 retLen = escape(retCC, (char)(0x80 | ((c >> 0) & 0x3F)), retLen); 130 } 131 } 132 //worst case scenario for character [0x7ff-] every single 133 //character will be encoded into 9 characters. 134 if (retLen + 9 > retCC.length) { 135 int newLen = retCC.length * 2 + 16; 136 if (newLen < 0) { 137 newLen = Integer.MAX_VALUE; 138 } 139 char[] buf = new char[newLen]; 140 System.arraycopy(retCC, 0, buf, 0, retLen); 141 retCC = buf; 142 } 143 } 144 return new String(retCC, 0, retLen); 145 } 146 147 /** 148 * Appends the URL escape sequence for the specified char to the 149 * specified StringBuffer. 150 */ 151 private static int escape(char[] cc, char c, int index) { 152 cc[index++] = '%'; 153 cc[index++] = Character.forDigit((c >> 4) & 0xF, 16); 154 cc[index++] = Character.forDigit(c & 0xF, 16); 155 return index; 156 } 157 158 /** 159 * Un-escape and return the character at position i in string s. 160 */ 161 private static byte unescape(String s, int i) { 162 return (byte) Integer.parseInt(s, i + 1, i + 3, 16); 163 } 164 165 166 /** 167 * Returns a new String constructed from the specified String by replacing 168 * the URL escape sequences and UTF8 encoding with the characters they 169 * represent. 170 */ 171 public static String decode(String s) { 172 int n = s.length(); 173 if ((n == 0) || (s.indexOf('%') < 0)) 174 return s; 175 176 StringBuilder sb = new StringBuilder(n); 177 ByteBuffer bb = ByteBuffer.allocate(n); 178 CharBuffer cb = CharBuffer.allocate(n); 179 CharsetDecoder dec = ThreadLocalCoders.decoderFor("UTF-8") 180 .onMalformedInput(CodingErrorAction.REPORT) 181 .onUnmappableCharacter(CodingErrorAction.REPORT); 182 183 char c = s.charAt(0); 184 for (int i = 0; i < n;) { 185 assert c == s.charAt(i); 186 if (c != '%') { 187 sb.append(c); 188 if (++i >= n) 189 break; 190 c = s.charAt(i); 191 continue; 192 } 193 bb.clear(); 194 int ui = i; 195 for (;;) { 196 assert (n - i >= 2); 197 try { 198 bb.put(unescape(s, i)); 199 } catch (NumberFormatException e) { 200 throw new IllegalArgumentException(); 201 } 202 i += 3; 203 if (i >= n) 204 break; 205 c = s.charAt(i); 206 if (c != '%') 207 break; 208 } 209 bb.flip(); 210 cb.clear(); 211 dec.reset(); 212 CoderResult cr = dec.decode(bb, cb, true); 213 if (cr.isError()) 214 throw new IllegalArgumentException("Error decoding percent encoded characters"); 215 cr = dec.flush(cb); 216 if (cr.isError()) 217 throw new IllegalArgumentException("Error decoding percent encoded characters"); 218 sb.append(cb.flip().toString()); 219 } 220 221 return sb.toString(); 222 } 223 224 /** 225 * Returns a canonical version of the specified string. 226 */ 227 public static String canonizeString(String file) { 228 int len = file.length(); 229 if (len == 0 || (file.indexOf("./") == -1 && file.charAt(len - 1) != '.')) { 230 return file; 231 } else { 232 return doCanonize(file); 233 } 234 } 235 236 private static String doCanonize(String file) { 237 int i, lim; 238 239 // Remove embedded /../ 240 while ((i = file.indexOf("/../")) >= 0) { 241 if ((lim = file.lastIndexOf('/', i - 1)) >= 0) { 242 file = file.substring(0, lim) + file.substring(i + 3); 243 } else { 244 file = file.substring(i + 3); 245 } 246 } 247 // Remove embedded /./ 248 while ((i = file.indexOf("/./")) >= 0) { 249 file = file.substring(0, i) + file.substring(i + 2); 250 } 251 // Remove trailing .. 252 while (file.endsWith("/..")) { 253 i = file.indexOf("/.."); 254 if ((lim = file.lastIndexOf('/', i - 1)) >= 0) { 255 file = file.substring(0, lim+1); 256 } else { 257 file = file.substring(0, i); 258 } 259 } 260 // Remove trailing . 261 if (file.endsWith("/.")) 262 file = file.substring(0, file.length() -1); 263 264 return file; 265 } 266 267 public static URL fileToEncodedURL(File file) 268 throws MalformedURLException 269 { 270 String path = file.getAbsolutePath(); 271 path = ParseUtil.encodePath(path); 272 if (!path.startsWith("/")) { 273 path = "/" + path; 274 } 275 if (!path.endsWith("/") && file.isDirectory()) { 276 path = path + "/"; 277 } 278 return new URL("file", "", path); 279 } 280 281 public static java.net.URI toURI(URL url) { 282 String protocol = url.getProtocol(); 283 String auth = url.getAuthority(); 284 String path = url.getPath(); 285 String query = url.getQuery(); 286 String ref = url.getRef(); 287 if (path != null && !(path.startsWith("/"))) 288 path = "/" + path; 289 290 // 291 // In java.net.URI class, a port number of -1 implies the default 292 // port number. So get it stripped off before creating URI instance. 293 // 294 if (auth != null && auth.endsWith(":-1")) 295 auth = auth.substring(0, auth.length() - 3); 296 297 java.net.URI uri; 298 try { 299 uri = createURI(protocol, auth, path, query, ref); 300 } catch (java.net.URISyntaxException e) { 301 uri = null; 302 } 303 return uri; 304 } 305 306 // 307 // createURI() and its auxiliary code are cloned from java.net.URI. 308 // Most of the code are just copy and paste, except that quote() 309 // has been modified to avoid double-escape. 310 // 311 // Usually it is unacceptable, but we're forced to do it because 312 // otherwise we need to change public API, namely java.net.URI's 313 // multi-argument constructors. It turns out that the changes cause 314 // incompatibilities so can't be done. 315 // 316 private static URI createURI(String scheme, 317 String authority, 318 String path, 319 String query, 320 String fragment) throws URISyntaxException 321 { 322 String s = toString(scheme, null, 323 authority, null, null, -1, 324 path, query, fragment); 325 checkPath(s, scheme, path); 326 return new URI(s); 327 } 328 329 private static String toString(String scheme, 330 String opaquePart, 331 String authority, 332 String userInfo, 333 String host, 334 int port, 335 String path, 336 String query, 337 String fragment) 338 { 339 StringBuffer sb = new StringBuffer(); 340 if (scheme != null) { 341 sb.append(scheme); 342 sb.append(':'); 343 } 344 appendSchemeSpecificPart(sb, opaquePart, 345 authority, userInfo, host, port, 346 path, query); 347 appendFragment(sb, fragment); 348 return sb.toString(); 349 } 350 351 private static void appendSchemeSpecificPart(StringBuffer sb, 352 String opaquePart, 353 String authority, 354 String userInfo, 355 String host, 356 int port, 357 String path, 358 String query) 359 { 360 if (opaquePart != null) { 361 /* check if SSP begins with an IPv6 address 362 * because we must not quote a literal IPv6 address 363 */ 364 if (opaquePart.startsWith("//[")) { 365 int end = opaquePart.indexOf(']'); 366 if (end != -1 && opaquePart.indexOf(':')!=-1) { 367 String doquote, dontquote; 368 if (end == opaquePart.length()) { 369 dontquote = opaquePart; 370 doquote = ""; 371 } else { 372 dontquote = opaquePart.substring(0,end+1); 373 doquote = opaquePart.substring(end+1); 374 } 375 sb.append (dontquote); 376 sb.append(quote(doquote, L_URIC, H_URIC)); 377 } 378 } else { 379 sb.append(quote(opaquePart, L_URIC, H_URIC)); 380 } 381 } else { 382 appendAuthority(sb, authority, userInfo, host, port); 383 if (path != null) 384 sb.append(quote(path, L_PATH, H_PATH)); 385 if (query != null) { 386 sb.append('?'); 387 sb.append(quote(query, L_URIC, H_URIC)); 388 } 389 } 390 } 391 392 private static void appendAuthority(StringBuffer sb, 393 String authority, 394 String userInfo, 395 String host, 396 int port) 397 { 398 if (host != null) { 399 sb.append("//"); 400 if (userInfo != null) { 401 sb.append(quote(userInfo, L_USERINFO, H_USERINFO)); 402 sb.append('@'); 403 } 404 boolean needBrackets = ((host.indexOf(':') >= 0) 405 && !host.startsWith("[") 406 && !host.endsWith("]")); 407 if (needBrackets) sb.append('['); 408 sb.append(host); 409 if (needBrackets) sb.append(']'); 410 if (port != -1) { 411 sb.append(':'); 412 sb.append(port); 413 } 414 } else if (authority != null) { 415 sb.append("//"); 416 if (authority.startsWith("[")) { 417 int end = authority.indexOf(']'); 418 if (end != -1 && authority.indexOf(':')!=-1) { 419 String doquote, dontquote; 420 if (end == authority.length()) { 421 dontquote = authority; 422 doquote = ""; 423 } else { 424 dontquote = authority.substring(0,end+1); 425 doquote = authority.substring(end+1); 426 } 427 sb.append (dontquote); 428 sb.append(quote(doquote, 429 L_REG_NAME | L_SERVER, 430 H_REG_NAME | H_SERVER)); 431 } 432 } else { 433 sb.append(quote(authority, 434 L_REG_NAME | L_SERVER, 435 H_REG_NAME | H_SERVER)); 436 } 437 } 438 } 439 440 private static void appendFragment(StringBuffer sb, String fragment) { 441 if (fragment != null) { 442 sb.append('#'); 443 sb.append(quote(fragment, L_URIC, H_URIC)); 444 } 445 } 446 447 // Quote any characters in s that are not permitted 448 // by the given mask pair 449 // 450 private static String quote(String s, long lowMask, long highMask) { 451 int n = s.length(); 452 StringBuffer sb = null; 453 boolean allowNonASCII = ((lowMask & L_ESCAPED) != 0); 454 for (int i = 0; i < s.length(); i++) { 455 char c = s.charAt(i); 456 if (c < '\u0080') { 457 if (!match(c, lowMask, highMask) && !isEscaped(s, i)) { 458 if (sb == null) { 459 sb = new StringBuffer(); 460 sb.append(s, 0, i); 461 } 462 appendEscape(sb, (byte)c); 463 } else { 464 if (sb != null) 465 sb.append(c); 466 } 467 } else if (allowNonASCII 468 && (Character.isSpaceChar(c) 469 || Character.isISOControl(c))) { 470 if (sb == null) { 471 sb = new StringBuffer(); 472 sb.append(s, 0, i); 473 } 474 appendEncoded(sb, c); 475 } else { 476 if (sb != null) 477 sb.append(c); 478 } 479 } 480 return (sb == null) ? s : sb.toString(); 481 } 482 483 // 484 // To check if the given string has an escaped triplet 485 // at the given position 486 // 487 private static boolean isEscaped(String s, int pos) { 488 if (s == null || (s.length() <= (pos + 2))) 489 return false; 490 491 return s.charAt(pos) == '%' 492 && match(s.charAt(pos + 1), L_HEX, H_HEX) 493 && match(s.charAt(pos + 2), L_HEX, H_HEX); 494 } 495 496 private static void appendEncoded(StringBuffer sb, char c) { 497 ByteBuffer bb = null; 498 try { 499 bb = ThreadLocalCoders.encoderFor("UTF-8") 500 .encode(CharBuffer.wrap("" + c)); 501 } catch (CharacterCodingException x) { 502 assert false; 503 } 504 while (bb.hasRemaining()) { 505 int b = bb.get() & 0xff; 506 if (b >= 0x80) 507 appendEscape(sb, (byte)b); 508 else 509 sb.append((char)b); 510 } 511 } 512 513 private static final char[] hexDigits = { 514 '0', '1', '2', '3', '4', '5', '6', '7', 515 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 516 }; 517 518 private static void appendEscape(StringBuffer sb, byte b) { 519 sb.append('%'); 520 sb.append(hexDigits[(b >> 4) & 0x0f]); 521 sb.append(hexDigits[(b >> 0) & 0x0f]); 522 } 523 524 // Tell whether the given character is permitted by the given mask pair 525 private static boolean match(char c, long lowMask, long highMask) { 526 if (c < 64) 527 return ((1L << c) & lowMask) != 0; 528 if (c < 128) 529 return ((1L << (c - 64)) & highMask) != 0; 530 return false; 531 } 532 533 // If a scheme is given then the path, if given, must be absolute 534 // 535 private static void checkPath(String s, String scheme, String path) 536 throws URISyntaxException 537 { 538 if (scheme != null) { 539 if ((path != null) 540 && ((path.length() > 0) && (path.charAt(0) != '/'))) 541 throw new URISyntaxException(s, 542 "Relative path in absolute URI"); 543 } 544 } 545 546 547 // -- Character classes for parsing -- 548 549 // To save startup time, we manually calculate the low-/highMask constants. 550 // For reference, the following methods were used to calculate the values: 551 552 // Compute a low-order mask for the characters 553 // between first and last, inclusive 554 // private static long lowMask(char first, char last) { 555 // long m = 0; 556 // int f = Math.max(Math.min(first, 63), 0); 557 // int l = Math.max(Math.min(last, 63), 0); 558 // for (int i = f; i <= l; i++) 559 // m |= 1L << i; 560 // return m; 561 // } 562 563 // Compute the low-order mask for the characters in the given string 564 // private static long lowMask(String chars) { 565 // int n = chars.length(); 566 // long m = 0; 567 // for (int i = 0; i < n; i++) { 568 // char c = chars.charAt(i); 569 // if (c < 64) 570 // m |= (1L << c); 571 // } 572 // return m; 573 // } 574 575 // Compute a high-order mask for the characters 576 // between first and last, inclusive 577 // private static long highMask(char first, char last) { 578 // long m = 0; 579 // int f = Math.max(Math.min(first, 127), 64) - 64; 580 // int l = Math.max(Math.min(last, 127), 64) - 64; 581 // for (int i = f; i <= l; i++) 582 // m |= 1L << i; 583 // return m; 584 // } 585 586 // Compute the high-order mask for the characters in the given string 587 // private static long highMask(String chars) { 588 // int n = chars.length(); 589 // long m = 0; 590 // for (int i = 0; i < n; i++) { 591 // char c = chars.charAt(i); 592 // if ((c >= 64) && (c < 128)) 593 // m |= (1L << (c - 64)); 594 // } 595 // return m; 596 // } 597 598 599 // Character-class masks 600 601 // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | 602 // "8" | "9" 603 private static final long L_DIGIT = 0x3FF000000000000L; // lowMask('0', '9'); 604 private static final long H_DIGIT = 0L; 605 606 // hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | 607 // "a" | "b" | "c" | "d" | "e" | "f" 608 private static final long L_HEX = L_DIGIT; 609 private static final long H_HEX = 0x7E0000007EL; // highMask('A', 'F') | highMask('a', 'f'); 610 611 // upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | 612 // "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | 613 // "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" 614 private static final long L_UPALPHA = 0L; 615 private static final long H_UPALPHA = 0x7FFFFFEL; // highMask('A', 'Z'); 616 617 // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | 618 // "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | 619 // "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" 620 private static final long L_LOWALPHA = 0L; 621 private static final long H_LOWALPHA = 0x7FFFFFE00000000L; // highMask('a', 'z'); 622 623 // alpha = lowalpha | upalpha 624 private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA; 625 private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA; 626 627 // alphanum = alpha | digit 628 private static final long L_ALPHANUM = L_DIGIT | L_ALPHA; 629 private static final long H_ALPHANUM = H_DIGIT | H_ALPHA; 630 631 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | 632 // "(" | ")" 633 private static final long L_MARK = 0x678200000000L; // lowMask("-_.!~*'()"); 634 private static final long H_MARK = 0x4000000080000000L; // highMask("-_.!~*'()"); 635 636 // unreserved = alphanum | mark 637 private static final long L_UNRESERVED = L_ALPHANUM | L_MARK; 638 private static final long H_UNRESERVED = H_ALPHANUM | H_MARK; 639 640 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | 641 // "$" | "," | "[" | "]" 642 // Added per RFC2732: "[", "]" 643 private static final long L_RESERVED = 0xAC00985000000000L; // lowMask(";/?:@&=+$,[]"); 644 private static final long H_RESERVED = 0x28000001L; // highMask(";/?:@&=+$,[]"); 645 646 // The zero'th bit is used to indicate that escape pairs and non-US-ASCII 647 // characters are allowed; this is handled by the scanEscape method below. 648 private static final long L_ESCAPED = 1L; 649 private static final long H_ESCAPED = 0L; 650 651 // uric = reserved | unreserved | escaped 652 private static final long L_URIC = L_RESERVED | L_UNRESERVED | L_ESCAPED; 653 private static final long H_URIC = H_RESERVED | H_UNRESERVED | H_ESCAPED; 654 655 // pchar = unreserved | escaped | 656 // ":" | "@" | "&" | "=" | "+" | "$" | "," 657 private static final long L_PCHAR 658 = L_UNRESERVED | L_ESCAPED | 0x2400185000000000L; // lowMask(":@&=+$,"); 659 private static final long H_PCHAR 660 = H_UNRESERVED | H_ESCAPED | 0x1L; // highMask(":@&=+$,"); 661 662 // All valid path characters 663 private static final long L_PATH = L_PCHAR | 0x800800000000000L; // lowMask(";/"); 664 private static final long H_PATH = H_PCHAR; // highMask(";/") == 0x0L; 665 666 // Dash, for use in domainlabel and toplabel 667 private static final long L_DASH = 0x200000000000L; // lowMask("-"); 668 private static final long H_DASH = 0x0L; // highMask("-"); 669 670 // userinfo = *( unreserved | escaped | 671 // ";" | ":" | "&" | "=" | "+" | "$" | "," ) 672 private static final long L_USERINFO 673 = L_UNRESERVED | L_ESCAPED | 0x2C00185000000000L; // lowMask(";:&=+$,"); 674 private static final long H_USERINFO 675 = H_UNRESERVED | H_ESCAPED; // | highMask(";:&=+$,") == 0L; 676 677 // reg_name = 1*( unreserved | escaped | "$" | "," | 678 // ";" | ":" | "@" | "&" | "=" | "+" ) 679 private static final long L_REG_NAME 680 = L_UNRESERVED | L_ESCAPED | 0x2C00185000000000L; // lowMask("$,;:@&=+"); 681 private static final long H_REG_NAME 682 = H_UNRESERVED | H_ESCAPED | 0x1L; // highMask("$,;:@&=+"); 683 684 // All valid characters for server-based authorities 685 private static final long L_SERVER 686 = L_USERINFO | L_ALPHANUM | L_DASH | 0x400400000000000L; // lowMask(".:@[]"); 687 private static final long H_SERVER 688 = H_USERINFO | H_ALPHANUM | H_DASH | 0x28000001L; // highMask(".:@[]"); 689 690 // Characters that are encoded in the path component of a URI. 691 // 692 // These characters are reserved in the path segment as described in 693 // RFC2396 section 3.3: 694 // "=" | ";" | "?" | "/" 695 // 696 // These characters are defined as excluded in RFC2396 section 2.4.3 697 // and must be escaped if they occur in the data part of a URI: 698 // "#" | " " | "<" | ">" | "%" | "\"" | "{" | "}" | "|" | "\\" | "^" | 699 // "[" | "]" | "`" 700 // 701 // Also US ASCII control characters 00-1F and 7F. 702 703 // lowMask((char)0, (char)31) | lowMask("=;?/# <>%\"{}|\\^[]`"); 704 private static final long L_ENCODED = 0xF800802DFFFFFFFFL; 705 706 // highMask((char)0x7F, (char)0x7F) | highMask("=;?/# <>%\"{}|\\^[]`"); 707 private static final long H_ENCODED = 0xB800000178000000L; 708 709 }