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