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