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