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