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 && !path.isEmpty() && path.charAt(0) != '/')
 540                 throw new URISyntaxException(s,
 541                                              "Relative path in absolute URI");
 542         }
 543     }
 544 
 545 
 546     // -- Character classes for parsing --
 547 
 548     // To save startup time, we manually calculate the low-/highMask constants.
 549     // For reference, the following methods were used to calculate the values:
 550 
 551     // Compute a low-order mask for the characters
 552     // between first and last, inclusive
 553     //    private static long lowMask(char first, char last) {
 554     //        long m = 0;
 555     //        int f = Math.max(Math.min(first, 63), 0);
 556     //        int l = Math.max(Math.min(last, 63), 0);
 557     //        for (int i = f; i <= l; i++)
 558     //            m |= 1L << i;
 559     //        return m;
 560     //    }
 561 
 562     // Compute the low-order mask for the characters in the given string
 563     //    private static long lowMask(String chars) {
 564     //        int n = chars.length();
 565     //        long m = 0;
 566     //        for (int i = 0; i < n; i++) {
 567     //            char c = chars.charAt(i);
 568     //            if (c < 64)
 569     //                m |= (1L << c);
 570     //        }
 571     //        return m;
 572     //    }
 573 
 574     // Compute a high-order mask for the characters
 575     // between first and last, inclusive
 576     //    private static long highMask(char first, char last) {
 577     //        long m = 0;
 578     //        int f = Math.max(Math.min(first, 127), 64) - 64;
 579     //        int l = Math.max(Math.min(last, 127), 64) - 64;
 580     //        for (int i = f; i <= l; i++)
 581     //            m |= 1L << i;
 582     //        return m;
 583     //    }
 584 
 585     // Compute the high-order mask for the characters in the given string
 586     //    private static long highMask(String chars) {
 587     //        int n = chars.length();
 588     //        long m = 0;
 589     //        for (int i = 0; i < n; i++) {
 590     //            char c = chars.charAt(i);
 591     //            if ((c >= 64) && (c < 128))
 592     //                m |= (1L << (c - 64));
 593     //        }
 594     //        return m;
 595     //     }
 596 
 597 
 598     // Character-class masks
 599 
 600     // digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
 601     //            "8" | "9"
 602     private static final long L_DIGIT = 0x3FF000000000000L; // lowMask('0', '9');
 603     private static final long H_DIGIT = 0L;
 604 
 605     // hex           =  digit | "A" | "B" | "C" | "D" | "E" | "F" |
 606     //                          "a" | "b" | "c" | "d" | "e" | "f"
 607     private static final long L_HEX = L_DIGIT;
 608     private static final long H_HEX = 0x7E0000007EL; // highMask('A', 'F') | highMask('a', 'f');
 609 
 610     // upalpha  = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
 611     //            "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
 612     //            "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
 613     private static final long L_UPALPHA = 0L;
 614     private static final long H_UPALPHA = 0x7FFFFFEL; // highMask('A', 'Z');
 615 
 616     // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
 617     //            "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
 618     //            "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
 619     private static final long L_LOWALPHA = 0L;
 620     private static final long H_LOWALPHA = 0x7FFFFFE00000000L; // highMask('a', 'z');
 621 
 622     // alpha         = lowalpha | upalpha
 623     private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA;
 624     private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA;
 625 
 626     // alphanum      = alpha | digit
 627     private static final long L_ALPHANUM = L_DIGIT | L_ALPHA;
 628     private static final long H_ALPHANUM = H_DIGIT | H_ALPHA;
 629 
 630     // mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
 631     //                 "(" | ")"
 632     private static final long L_MARK = 0x678200000000L; // lowMask("-_.!~*'()");
 633     private static final long H_MARK = 0x4000000080000000L; // highMask("-_.!~*'()");
 634 
 635     // unreserved    = alphanum | mark
 636     private static final long L_UNRESERVED = L_ALPHANUM | L_MARK;
 637     private static final long H_UNRESERVED = H_ALPHANUM | H_MARK;
 638 
 639     // reserved      = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
 640     //                 "$" | "," | "[" | "]"
 641     // Added per RFC2732: "[", "]"
 642     private static final long L_RESERVED = 0xAC00985000000000L; // lowMask(";/?:@&=+$,[]");
 643     private static final long H_RESERVED = 0x28000001L; // highMask(";/?:@&=+$,[]");
 644 
 645     // The zero'th bit is used to indicate that escape pairs and non-US-ASCII
 646     // characters are allowed; this is handled by the scanEscape method below.
 647     private static final long L_ESCAPED = 1L;
 648     private static final long H_ESCAPED = 0L;
 649 
 650     // uric          = reserved | unreserved | escaped
 651     private static final long L_URIC = L_RESERVED | L_UNRESERVED | L_ESCAPED;
 652     private static final long H_URIC = H_RESERVED | H_UNRESERVED | H_ESCAPED;
 653 
 654     // pchar         = unreserved | escaped |
 655     //                 ":" | "@" | "&" | "=" | "+" | "$" | ","
 656     private static final long L_PCHAR
 657             = L_UNRESERVED | L_ESCAPED | 0x2400185000000000L; // lowMask(":@&=+$,");
 658     private static final long H_PCHAR
 659             = H_UNRESERVED | H_ESCAPED | 0x1L; // highMask(":@&=+$,");
 660 
 661     // All valid path characters
 662     private static final long L_PATH = L_PCHAR | 0x800800000000000L; // lowMask(";/");
 663     private static final long H_PATH = H_PCHAR; // highMask(";/") == 0x0L;
 664 
 665     // Dash, for use in domainlabel and toplabel
 666     private static final long L_DASH = 0x200000000000L; // lowMask("-");
 667     private static final long H_DASH = 0x0L; // highMask("-");
 668 
 669     // userinfo      = *( unreserved | escaped |
 670     //                    ";" | ":" | "&" | "=" | "+" | "$" | "," )
 671     private static final long L_USERINFO
 672             = L_UNRESERVED | L_ESCAPED | 0x2C00185000000000L; // lowMask(";:&=+$,");
 673     private static final long H_USERINFO
 674             = H_UNRESERVED | H_ESCAPED; // | highMask(";:&=+$,") == 0L;
 675 
 676     // reg_name      = 1*( unreserved | escaped | "$" | "," |
 677     //                     ";" | ":" | "@" | "&" | "=" | "+" )
 678     private static final long L_REG_NAME
 679             = L_UNRESERVED | L_ESCAPED | 0x2C00185000000000L; // lowMask("$,;:@&=+");
 680     private static final long H_REG_NAME
 681             = H_UNRESERVED | H_ESCAPED | 0x1L; // highMask("$,;:@&=+");
 682 
 683     // All valid characters for server-based authorities
 684     private static final long L_SERVER
 685             = L_USERINFO | L_ALPHANUM | L_DASH | 0x400400000000000L; // lowMask(".:@[]");
 686     private static final long H_SERVER
 687             = H_USERINFO | H_ALPHANUM | H_DASH | 0x28000001L; // highMask(".:@[]");
 688 
 689     // Characters that are encoded in the path component of a URI.
 690     //
 691     // These characters are reserved in the path segment as described in
 692     // RFC2396 section 3.3:
 693     //     "=" | ";" | "?" | "/"
 694     //
 695     // These characters are defined as excluded in RFC2396 section 2.4.3
 696     // and must be escaped if they occur in the data part of a URI:
 697     //     "#" | " " | "<" | ">" | "%" | "\"" | "{" | "}" | "|" | "\\" | "^" |
 698     //     "[" | "]" | "`"
 699     //
 700     // Also US ASCII control characters 00-1F and 7F.
 701 
 702     // lowMask((char)0, (char)31) | lowMask("=;?/# <>%\"{}|\\^[]`");
 703     private static final long L_ENCODED = 0xF800802DFFFFFFFFL;
 704 
 705     // highMask((char)0x7F, (char)0x7F) | highMask("=;?/# <>%\"{}|\\^[]`");
 706     private static final long H_ENCODED = 0xB800000178000000L;
 707 
 708 }