48 |
/** |
/** |
49 |
* <p> |
* <p> |
50 |
* A URI instance represents that defined by |
* A URI instance represents that defined by |
51 |
* <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC2396</a>, |
* <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC3986</a>, |
52 |
* with some deviations. |
* with some deviations. |
53 |
* </p> |
* </p> |
54 |
* <p> |
* <p> |
98 |
* this that the path sub-part may also not be undefined, so as to ensure |
* this that the path sub-part may also not be undefined, so as to ensure |
99 |
* the former. |
* the former. |
100 |
* </p> |
* </p> |
101 |
* |
* <h2>Character Escaping and Quoting</h2> |
102 |
|
* <p> |
103 |
|
* The characters that can be used within a valid URI are restricted. |
104 |
|
* There are two main classes of characters which can't be used as is |
105 |
|
* within the URI: |
106 |
|
* </p> |
107 |
|
* <ol> |
108 |
|
* <li><strong>Characters outside the US-ASCII character set</strong>. |
109 |
|
* These have to be <strong>escaped</strong> in order to create |
110 |
|
* an RFC-compliant URI; this means replacing the character with the |
111 |
|
* appropriate hexadecimal value, preceded by a `%'.</li> |
112 |
|
* <li><strong>Illegal characters</strong> (e.g. space characters, |
113 |
|
* control characters) are quoted, which results in them being encoded |
114 |
|
* in the same way as non-US-ASCII characters.</li> |
115 |
|
* </ol> |
116 |
|
* <p> |
117 |
|
* The set of valid characters differs depending on the section of the URI: |
118 |
|
* </p> |
119 |
|
* <ul> |
120 |
|
* <li><strong>Scheme</strong>: Must be an alphanumeric, `-', `.' or '+'.</li> |
121 |
|
* <li><strong>Authority</strong>:Composed of the username, host, port, `@' |
122 |
|
* and `:'.</li> |
123 |
|
* <li><strong>Username</strong>: Allows unreserved or percent-encoded |
124 |
|
* characters, sub-delimiters and `:'.</li> |
125 |
|
* <li><strong>Host</strong>: Allows unreserved or percent-encoded |
126 |
|
* characters, sub-delimiters and square brackets (`[' and `]') for IPv6 |
127 |
|
* addresses.</li> |
128 |
|
* <li><strong>Port</strong>: Digits only.</li> |
129 |
|
* <li><strong>Path</strong>: Allows the path characters and `/'. |
130 |
|
* <li><strong>Query</strong>: Allows the path characters, `?' and '/'. |
131 |
|
* <li><strong>Fragment</strong>: Allows the path characters, `?' and '/'. |
132 |
|
* </ul> |
133 |
|
* <p> |
134 |
|
* These definitions reference the following sets of characters: |
135 |
|
* </p> |
136 |
|
* <ul> |
137 |
|
* <li><strong>Unreserved characters</strong>: The alphanumerics plus |
138 |
|
* `-', `.', `_', and `~'.</li> |
139 |
|
* <li><strong>Sub-delimiters</strong>: `!', `$', `&', `(', `)', `*', |
140 |
|
* `+', `,', `;', `=' and the single-quote itself.</li> |
141 |
|
* <li><strong>Path characters</strong>: Unreserved and percent-encoded |
142 |
|
* characters and the sub-delimiters along with `@' and `:'.</li> |
143 |
|
* </ul> |
144 |
|
* <p> |
145 |
|
* The constructors and accessor methods allow the use and retrieval of |
146 |
|
* URI components which contain non-US-ASCII characters directly. |
147 |
|
* They are only escaped when the <code>toASCIIString()</code> method |
148 |
|
* is used. In contrast, illegal characters are always quoted, with the |
149 |
|
* exception of the return values of the non-raw accessors. |
150 |
|
* </p> |
151 |
|
* |
152 |
* @author Ito Kazumitsu (ito.kazumitsu@hitachi-cable.co.jp) |
* @author Ito Kazumitsu (ito.kazumitsu@hitachi-cable.co.jp) |
153 |
* @author Dalibor Topic (robilad@kaffe.org) |
* @author Dalibor Topic (robilad@kaffe.org) |
154 |
* @author Michael Koch (konqueror@gmx.de) |
* @author Michael Koch (konqueror@gmx.de) |
158 |
public final class URI |
public final class URI |
159 |
implements Comparable, Serializable |
implements Comparable, Serializable |
160 |
{ |
{ |
161 |
|
/** |
162 |
|
* For serialization compatability. |
163 |
|
*/ |
164 |
static final long serialVersionUID = -6052424284110960213L; |
static final long serialVersionUID = -6052424284110960213L; |
165 |
|
|
166 |
/** |
/** |
172 |
private static final String URI_REGEXP = |
private static final String URI_REGEXP = |
173 |
"^(([^:/?#]+):)?((//([^/?#]*))?([^?#]*)(\\?([^#]*))?)?(#(.*))?"; |
"^(([^:/?#]+):)?((//([^/?#]*))?([^?#]*)(\\?([^#]*))?)?(#(.*))?"; |
174 |
|
|
175 |
|
/** |
176 |
|
* Regular expression for parsing the authority segment. |
177 |
|
*/ |
178 |
private static final String AUTHORITY_REGEXP = |
private static final String AUTHORITY_REGEXP = |
179 |
"(([^?#]*)@)?([^?#:]*)(:([^?#]*))?"; |
"(([^?#]*)@)?([^?#:]*)(:([0-9]*))?"; |
180 |
|
|
181 |
/** |
/** |
182 |
* Valid characters (taken from rfc2396) |
* Valid characters (taken from rfc2396/3986) |
183 |
*/ |
*/ |
184 |
private static final String RFC2396_DIGIT = "0123456789"; |
private static final String RFC2396_DIGIT = "0123456789"; |
185 |
private static final String RFC2396_LOWALPHA = "abcdefghijklmnopqrstuvwxyz"; |
private static final String RFC2396_LOWALPHA = "abcdefghijklmnopqrstuvwxyz"; |
187 |
private static final String RFC2396_ALPHA = |
private static final String RFC2396_ALPHA = |
188 |
RFC2396_LOWALPHA + RFC2396_UPALPHA; |
RFC2396_LOWALPHA + RFC2396_UPALPHA; |
189 |
private static final String RFC2396_ALPHANUM = RFC2396_DIGIT + RFC2396_ALPHA; |
private static final String RFC2396_ALPHANUM = RFC2396_DIGIT + RFC2396_ALPHA; |
190 |
private static final String RFC2396_MARK = "-_.!~*'()"; |
private static final String RFC3986_UNRESERVED = RFC2396_ALPHANUM + "-._~"; |
191 |
private static final String RFC2396_UNRESERVED = |
private static final String RFC3986_SUBDELIMS = "!$&'()*+,;="; |
192 |
RFC2396_ALPHANUM + RFC2396_MARK; |
private static final String RFC3986_REG_NAME = |
193 |
private static final String RFC2396_REG_NAME = |
RFC3986_UNRESERVED + RFC3986_SUBDELIMS + "%"; |
194 |
RFC2396_UNRESERVED + "$,;:@&=+"; |
private static final String RFC3986_PCHAR = RFC3986_UNRESERVED + |
195 |
private static final String RFC2396_PCHAR = RFC2396_UNRESERVED + ":@&=+$,"; |
RFC3986_SUBDELIMS + ":@%"; |
196 |
private static final String RFC2396_SEGMENT = RFC2396_PCHAR + ";"; |
private static final String RFC3986_SEGMENT = RFC3986_PCHAR; |
197 |
private static final String RFC2396_PATH_SEGMENTS = RFC2396_SEGMENT + "/"; |
private static final String RFC3986_PATH_SEGMENTS = RFC3986_SEGMENT + "/"; |
198 |
|
private static final String RFC3986_SSP = RFC3986_PCHAR + "?/"; |
199 |
|
private static final String RFC3986_HOST = RFC3986_REG_NAME + "[]"; |
200 |
|
private static final String RFC3986_USERINFO = RFC3986_REG_NAME + ":"; |
201 |
|
|
202 |
/** |
/** |
203 |
* Index of scheme component in parsed URI. |
* Index of scheme component in parsed URI. |
229 |
*/ |
*/ |
230 |
private static final int FRAGMENT_GROUP = 10; |
private static final int FRAGMENT_GROUP = 10; |
231 |
|
|
232 |
|
/** |
233 |
|
* Index of userinfo component in parsed authority section. |
234 |
|
*/ |
235 |
private static final int AUTHORITY_USERINFO_GROUP = 2; |
private static final int AUTHORITY_USERINFO_GROUP = 2; |
236 |
|
|
237 |
|
/** |
238 |
|
* Index of host component in parsed authority section. |
239 |
|
*/ |
240 |
private static final int AUTHORITY_HOST_GROUP = 3; |
private static final int AUTHORITY_HOST_GROUP = 3; |
241 |
|
|
242 |
|
/** |
243 |
|
* Index of port component in parsed authority section. |
244 |
|
*/ |
245 |
private static final int AUTHORITY_PORT_GROUP = 5; |
private static final int AUTHORITY_PORT_GROUP = 5; |
246 |
|
|
247 |
|
/** |
248 |
|
* The compiled version of the URI regular expression. |
249 |
|
*/ |
250 |
|
private static final Pattern URI_PATTERN; |
251 |
|
|
252 |
|
/** |
253 |
|
* The compiled version of the authority regular expression. |
254 |
|
*/ |
255 |
|
private static final Pattern AUTHORITY_PATTERN; |
256 |
|
|
257 |
|
/** |
258 |
|
* The set of valid hexadecimal characters. |
259 |
|
*/ |
260 |
|
private static final String HEX = "0123456789ABCDEF"; |
261 |
|
|
262 |
private transient String scheme; |
private transient String scheme; |
263 |
private transient String rawSchemeSpecificPart; |
private transient String rawSchemeSpecificPart; |
264 |
private transient String schemeSpecificPart; |
private transient String schemeSpecificPart; |
277 |
private transient String fragment; |
private transient String fragment; |
278 |
private String string; |
private String string; |
279 |
|
|
280 |
|
/** |
281 |
|
* Static initializer to pre-compile the regular expressions. |
282 |
|
*/ |
283 |
|
static |
284 |
|
{ |
285 |
|
URI_PATTERN = Pattern.compile(URI_REGEXP); |
286 |
|
AUTHORITY_PATTERN = Pattern.compile(AUTHORITY_REGEXP); |
287 |
|
} |
288 |
|
|
289 |
private void readObject(ObjectInputStream is) |
private void readObject(ObjectInputStream is) |
290 |
throws ClassNotFoundException, IOException |
throws ClassNotFoundException, IOException |
291 |
{ |
{ |
323 |
*/ |
*/ |
324 |
private void parseURI(String str) throws URISyntaxException |
private void parseURI(String str) throws URISyntaxException |
325 |
{ |
{ |
326 |
Pattern pattern = Pattern.compile(URI_REGEXP); |
Matcher matcher = URI_PATTERN.matcher(str); |
|
Matcher matcher = pattern.matcher(str); |
|
327 |
|
|
328 |
if (matcher.matches()) |
if (matcher.matches()) |
329 |
{ |
{ |
339 |
rawFragment = getURIGroup(matcher, FRAGMENT_GROUP); |
rawFragment = getURIGroup(matcher, FRAGMENT_GROUP); |
340 |
} |
} |
341 |
else |
else |
342 |
throw new URISyntaxException(str, "doesn't match URI regular expression"); |
throw new URISyntaxException(str, |
343 |
|
"doesn't match URI regular expression"); |
344 |
if (rawAuthority != null) |
parseServerAuthority(); |
|
{ |
|
|
pattern = Pattern.compile(AUTHORITY_REGEXP); |
|
|
matcher = pattern.matcher(rawAuthority); |
|
|
|
|
|
if (matcher.matches()) |
|
|
{ |
|
|
rawUserInfo = getURIGroup(matcher, AUTHORITY_USERINFO_GROUP); |
|
|
rawHost = getURIGroup(matcher, AUTHORITY_HOST_GROUP); |
|
|
|
|
|
String portStr = getURIGroup(matcher, AUTHORITY_PORT_GROUP); |
|
|
|
|
|
if (portStr != null) |
|
|
try |
|
|
{ |
|
|
port = Integer.parseInt(portStr); |
|
|
} |
|
|
catch (NumberFormatException e) |
|
|
{ |
|
|
URISyntaxException use = |
|
|
new URISyntaxException |
|
|
(str, "doesn't match URI regular expression"); |
|
|
use.initCause(e); |
|
|
throw use; |
|
|
} |
|
|
} |
|
|
else |
|
|
throw new URISyntaxException(str, "doesn't match URI regular expression"); |
|
|
} |
|
345 |
|
|
346 |
// We must eagerly unquote the parts, because this is the only time |
// We must eagerly unquote the parts, because this is the only time |
347 |
// we may throw an exception. |
// we may throw an exception. |
372 |
for (int i = 0; i < str.length(); i++) |
for (int i = 0; i < str.length(); i++) |
373 |
{ |
{ |
374 |
char c = str.charAt(i); |
char c = str.charAt(i); |
|
if (c > 127) |
|
|
throw new URISyntaxException(str, "Invalid character"); |
|
375 |
if (c == '%') |
if (c == '%') |
376 |
{ |
{ |
377 |
if (i + 2 >= str.length()) |
if (i + 2 >= str.length()) |
408 |
*/ |
*/ |
409 |
private static String quote(String str) |
private static String quote(String str) |
410 |
{ |
{ |
411 |
// FIXME: unimplemented. |
return quote(str, RFC3986_SSP); |
|
return str; |
|
412 |
} |
} |
413 |
|
|
414 |
/** |
/** |
426 |
{ |
{ |
427 |
// Technically, we should be using RFC2396_AUTHORITY, but |
// Technically, we should be using RFC2396_AUTHORITY, but |
428 |
// it contains no additional characters. |
// it contains no additional characters. |
429 |
return quote(str, RFC2396_REG_NAME); |
return quote(str, RFC3986_REG_NAME); |
430 |
} |
} |
431 |
|
|
432 |
/** |
/** |
433 |
* Quote characters in str that are not part of legalCharacters. |
* Quotes the characters in the supplied string that are not part of |
434 |
* |
* the specified set of legal characters. |
|
* Replace illegal characters by encoding their UTF-8 |
|
|
* representation as "%" + hex code for each resulting |
|
|
* UTF-8 character. |
|
435 |
* |
* |
436 |
* @param str The string to quote |
* @param str the string to quote |
437 |
* @param legalCharacters The set of legal characters |
* @param legalCharacters the set of legal characters |
438 |
* |
* |
439 |
* @return The quoted string. |
* @return the quoted string. |
440 |
*/ |
*/ |
441 |
private static String quote(String str, String legalCharacters) |
private static String quote(String str, String legalCharacters) |
442 |
{ |
{ |
446 |
char c = str.charAt(i); |
char c = str.charAt(i); |
447 |
if (legalCharacters.indexOf(c) == -1) |
if (legalCharacters.indexOf(c) == -1) |
448 |
{ |
{ |
|
String hex = "0123456789ABCDEF"; |
|
449 |
if (c <= 127) |
if (c <= 127) |
|
sb.append('%').append(hex.charAt(c / 16)).append(hex.charAt(c % 16)); |
|
|
else |
|
450 |
{ |
{ |
451 |
try |
sb.append('%'); |
452 |
{ |
sb.append(HEX.charAt(c / 16)); |
453 |
// this is far from optimal, but it works |
sb.append(HEX.charAt(c % 16)); |
|
byte[] utf8 = str.substring(i, i + 1).getBytes("utf-8"); |
|
|
for (int j = 0; j < utf8.length; j++) |
|
|
sb.append('%').append(hex.charAt((utf8[j] & 0xff) / 16)) |
|
|
.append(hex.charAt((utf8[j] & 0xff) % 16)); |
|
|
} |
|
|
catch (java.io.UnsupportedEncodingException x) |
|
|
{ |
|
|
throw (Error) new InternalError().initCause(x); |
|
|
} |
|
454 |
} |
} |
455 |
} |
} |
456 |
else |
else |
472 |
*/ |
*/ |
473 |
private static String quoteHost(String str) |
private static String quoteHost(String str) |
474 |
{ |
{ |
475 |
// FIXME: unimplemented. |
return quote(str, RFC3986_HOST); |
|
return str; |
|
476 |
} |
} |
477 |
|
|
478 |
/** |
/** |
490 |
{ |
{ |
491 |
// Technically, we should be using RFC2396_PATH, but |
// Technically, we should be using RFC2396_PATH, but |
492 |
// it contains no additional characters. |
// it contains no additional characters. |
493 |
return quote(str, RFC2396_PATH_SEGMENTS); |
return quote(str, RFC3986_PATH_SEGMENTS); |
494 |
} |
} |
495 |
|
|
496 |
/** |
/** |
506 |
*/ |
*/ |
507 |
private static String quoteUserInfo(String str) |
private static String quoteUserInfo(String str) |
508 |
{ |
{ |
509 |
// FIXME: unimplemented. |
return quote(str, RFC3986_USERINFO); |
|
return str; |
|
510 |
} |
} |
511 |
|
|
512 |
/** |
/** |
548 |
+ (path == null ? "" : quotePath(path)) |
+ (path == null ? "" : quotePath(path)) |
549 |
+ (query == null ? "" : "?" + quote(query)) |
+ (query == null ? "" : "?" + quote(query)) |
550 |
+ (fragment == null ? "" : "#" + quote(fragment))); |
+ (fragment == null ? "" : "#" + quote(fragment))); |
|
|
|
|
parseServerAuthority(); |
|
551 |
} |
} |
552 |
|
|
553 |
/** |
/** |
627 |
|
|
628 |
/** |
/** |
629 |
* Attempts to parse this URI's authority component, if defined, |
* Attempts to parse this URI's authority component, if defined, |
630 |
* into user-information, host, and port components |
* into user-information, host, and port components. The purpose |
631 |
* |
* of this method was to disambiguate between some authority sections, |
632 |
* @exception URISyntaxException If the given string violates RFC 2396 |
* which form invalid server-based authories, but valid registry |
633 |
|
* based authorities. In the updated RFC 3986, the authority section |
634 |
|
* is defined differently, with registry-based authorities part of |
635 |
|
* the host section. Thus, this method is now simply an explicit |
636 |
|
* way of parsing any authority section. |
637 |
|
* |
638 |
|
* @return the URI, with the authority section parsed into user |
639 |
|
* information, host and port components. |
640 |
|
* @throws URISyntaxException if the given string violates RFC 2396 |
641 |
*/ |
*/ |
642 |
public URI parseServerAuthority() throws URISyntaxException |
public URI parseServerAuthority() throws URISyntaxException |
643 |
{ |
{ |
644 |
return null; |
if (rawAuthority != null) |
645 |
|
{ |
646 |
|
Matcher matcher = AUTHORITY_PATTERN.matcher(rawAuthority); |
647 |
|
|
648 |
|
if (matcher.matches()) |
649 |
|
{ |
650 |
|
rawUserInfo = getURIGroup(matcher, AUTHORITY_USERINFO_GROUP); |
651 |
|
rawHost = getURIGroup(matcher, AUTHORITY_HOST_GROUP); |
652 |
|
|
653 |
|
String portStr = getURIGroup(matcher, AUTHORITY_PORT_GROUP); |
654 |
|
|
655 |
|
if (portStr != null) |
656 |
|
try |
657 |
|
{ |
658 |
|
port = Integer.parseInt(portStr); |
659 |
|
} |
660 |
|
catch (NumberFormatException e) |
661 |
|
{ |
662 |
|
URISyntaxException use = |
663 |
|
new URISyntaxException |
664 |
|
(string, "doesn't match URI regular expression"); |
665 |
|
use.initCause(e); |
666 |
|
throw use; |
667 |
|
} |
668 |
|
} |
669 |
|
else |
670 |
|
throw new URISyntaxException(string, |
671 |
|
"doesn't match URI regular expression"); |
672 |
|
} |
673 |
|
return this; |
674 |
} |
} |
675 |
|
|
676 |
/** |
/** |
677 |
* Returns a normalizes versions of the URI |
* <p> |
678 |
|
* Returns a normalized version of the URI. If the URI is opaque, |
679 |
|
* or its path is already in normal form, then this URI is simply |
680 |
|
* returned. Otherwise, the following transformation of the path |
681 |
|
* element takes place: |
682 |
|
* </p> |
683 |
|
* <ol> |
684 |
|
* <li>All `.' segments are removed.</li> |
685 |
|
* <li>Each `..' segment which can be paired with a prior non-`..' segment |
686 |
|
* is removed along with the preceding segment.</li> |
687 |
|
* <li>A `.' segment is added to the front if the first segment contains |
688 |
|
* a colon (`:'). This is a deviation from the RFC, which prevents |
689 |
|
* confusion between the path and the scheme.</li> |
690 |
|
* </ol> |
691 |
|
* <p> |
692 |
|
* The resulting URI will be free of `.' and `..' segments, barring those |
693 |
|
* that were prepended or which couldn't be paired, respectively. |
694 |
|
* </p> |
695 |
|
* |
696 |
|
* @return the normalized URI. |
697 |
*/ |
*/ |
698 |
public URI normalize() |
public URI normalize() |
699 |
{ |
{ |
700 |
return null; |
if (isOpaque() || path.indexOf("/./") == -1 && path.indexOf("/../") == -1) |
701 |
|
return this; |
702 |
|
try |
703 |
|
{ |
704 |
|
return new URI(scheme, authority, normalizePath(path), query, |
705 |
|
fragment); |
706 |
|
} |
707 |
|
catch (URISyntaxException e) |
708 |
|
{ |
709 |
|
throw (Error) new InternalError("Normalized URI variant could not "+ |
710 |
|
"be constructed").initCause(e); |
711 |
|
} |
712 |
|
} |
713 |
|
|
714 |
|
/** |
715 |
|
* <p> |
716 |
|
* Normalize the given path. The following transformation takes place: |
717 |
|
* </p> |
718 |
|
* <ol> |
719 |
|
* <li>All `.' segments are removed.</li> |
720 |
|
* <li>Each `..' segment which can be paired with a prior non-`..' segment |
721 |
|
* is removed along with the preceding segment.</li> |
722 |
|
* <li>A `.' segment is added to the front if the first segment contains |
723 |
|
* a colon (`:'). This is a deviation from the RFC, which prevents |
724 |
|
* confusion between the path and the scheme.</li> |
725 |
|
* </ol> |
726 |
|
* <p> |
727 |
|
* The resulting URI will be free of `.' and `..' segments, barring those |
728 |
|
* that were prepended or which couldn't be paired, respectively. |
729 |
|
* </p> |
730 |
|
* |
731 |
|
* @param relativePath the relative path to be normalized. |
732 |
|
* @return the normalized path. |
733 |
|
*/ |
734 |
|
private String normalizePath(String relativePath) |
735 |
|
{ |
736 |
|
/* |
737 |
|
This follows the algorithm in section 5.2.4. of RFC3986, |
738 |
|
but doesn't modify the input buffer. |
739 |
|
*/ |
740 |
|
StringBuffer input = new StringBuffer(relativePath); |
741 |
|
StringBuffer output = new StringBuffer(); |
742 |
|
int start = 0; |
743 |
|
while (start < input.length()) |
744 |
|
{ |
745 |
|
/* A */ |
746 |
|
if (input.indexOf("../",start) == start) |
747 |
|
{ |
748 |
|
start += 3; |
749 |
|
continue; |
750 |
|
} |
751 |
|
if (input.indexOf("./",start) == start) |
752 |
|
{ |
753 |
|
start += 2; |
754 |
|
continue; |
755 |
|
} |
756 |
|
/* B */ |
757 |
|
if (input.indexOf("/./",start) == start) |
758 |
|
{ |
759 |
|
start += 2; |
760 |
|
continue; |
761 |
|
} |
762 |
|
if (input.indexOf("/.",start) == start |
763 |
|
&& input.charAt(start + 2) != '.') |
764 |
|
{ |
765 |
|
start += 1; |
766 |
|
input.setCharAt(start,'/'); |
767 |
|
continue; |
768 |
|
} |
769 |
|
/* C */ |
770 |
|
if (input.indexOf("/../",start) == start) |
771 |
|
{ |
772 |
|
start += 3; |
773 |
|
removeLastSegment(output); |
774 |
|
continue; |
775 |
|
} |
776 |
|
if (input.indexOf("/..",start) == start) |
777 |
|
{ |
778 |
|
start += 2; |
779 |
|
input.setCharAt(start,'/'); |
780 |
|
removeLastSegment(output); |
781 |
|
continue; |
782 |
|
} |
783 |
|
/* D */ |
784 |
|
if (start == input.length() - 1 && input.indexOf(".",start) == start) |
785 |
|
{ |
786 |
|
input.delete(0,1); |
787 |
|
continue; |
788 |
|
} |
789 |
|
if (start == input.length() - 2 && input.indexOf("..",start) == start) |
790 |
|
{ |
791 |
|
input.delete(0,2); |
792 |
|
continue; |
793 |
|
} |
794 |
|
/* E */ |
795 |
|
int indexOfSlash = input.indexOf("/",start); |
796 |
|
while (indexOfSlash == start) |
797 |
|
{ |
798 |
|
output.append("/"); |
799 |
|
++start; |
800 |
|
indexOfSlash = input.indexOf("/",start); |
801 |
|
} |
802 |
|
if (indexOfSlash == -1) |
803 |
|
indexOfSlash = input.length(); |
804 |
|
output.append(input.substring(start, indexOfSlash)); |
805 |
|
start = indexOfSlash; |
806 |
|
} |
807 |
|
return output.toString(); |
808 |
|
} |
809 |
|
|
810 |
|
/** |
811 |
|
* Removes the last segment of the path from the specified buffer. |
812 |
|
* |
813 |
|
* @param buffer the buffer containing the path. |
814 |
|
*/ |
815 |
|
private void removeLastSegment(StringBuffer buffer) |
816 |
|
{ |
817 |
|
int lastSlash = buffer.lastIndexOf("/"); |
818 |
|
if (lastSlash == -1) |
819 |
|
buffer.setLength(0); |
820 |
|
else |
821 |
|
buffer.setLength(lastSlash); |
822 |
} |
} |
823 |
|
|
824 |
/** |
/** |
829 |
* @return The resulting URI, or null when it couldn't be resolved |
* @return The resulting URI, or null when it couldn't be resolved |
830 |
* for some reason. |
* for some reason. |
831 |
* |
* |
832 |
* @exception NullPointerException If uri is null |
* @throws NullPointerException if uri is null |
833 |
*/ |
*/ |
834 |
public URI resolve(URI uri) |
public URI resolve(URI uri) |
835 |
{ |
{ |
865 |
basepath.delete(i + 1, basepath.length()); |
basepath.delete(i + 1, basepath.length()); |
866 |
|
|
867 |
basepath.append(path); |
basepath.append(path); |
868 |
path = basepath.toString(); |
path = normalizePath(basepath.toString()); |
|
// FIXME We must normalize the path here. |
|
|
// Normalization process omitted. |
|
869 |
} |
} |
870 |
} |
} |
871 |
return new URI(this.scheme, authority, path, query, fragment); |
return new URI(this.scheme, authority, path, query, fragment); |
872 |
} |
} |
873 |
catch (URISyntaxException e) |
catch (URISyntaxException e) |
874 |
{ |
{ |
875 |
return null; |
throw (Error) new InternalError("Resolved URI variant could not "+ |
876 |
|
"be constructed").initCause(e); |
877 |
} |
} |
878 |
} |
} |
879 |
|
|
884 |
* |
* |
885 |
* @return The resulting URI |
* @return The resulting URI |
886 |
* |
* |
887 |
* @exception IllegalArgumentException If the given URI string |
* @throws IllegalArgumentException If the given URI string |
888 |
* violates RFC 2396 |
* violates RFC 2396 |
889 |
* @exception NullPointerException If uri is null |
* @throws NullPointerException If uri is null |
890 |
*/ |
*/ |
891 |
public URI resolve(String str) throws IllegalArgumentException |
public URI resolve(String str) throws IllegalArgumentException |
892 |
{ |
{ |
894 |
} |
} |
895 |
|
|
896 |
/** |
/** |
897 |
* Relativizes the given URI against this URI |
* <p> |
898 |
* |
* Relativizes the given URI against this URI using the following |
899 |
* @param uri The URI to relativize this URI |
* algorithm: |
900 |
* |
* </p> |
901 |
* @return The resulting URI |
* <ul> |
902 |
* |
* <li>If either URI is opaque, the given URI is returned.</li> |
903 |
* @exception NullPointerException If uri is null |
* <li>If the schemes of the URIs differ, the given URI is returned.</li> |
904 |
|
* <li>If the authority components of the URIs differ, then the given |
905 |
|
* URI is returned.</li> |
906 |
|
* <li>If the path of this URI is not a prefix of the supplied URI, |
907 |
|
* then the given URI is returned.</li> |
908 |
|
* <li>If all the above conditions hold, a new URI is created using the |
909 |
|
* query and fragment components of the given URI, along with a path |
910 |
|
* computed by removing the path of this URI from the start of the path |
911 |
|
* of the supplied URI.</li> |
912 |
|
* </ul> |
913 |
|
* |
914 |
|
* @param uri the URI to relativize agsint this URI |
915 |
|
* @return the resulting URI |
916 |
|
* @throws NullPointerException if the uri is null |
917 |
*/ |
*/ |
918 |
public URI relativize(URI uri) |
public URI relativize(URI uri) |
919 |
{ |
{ |
920 |
return null; |
if (isOpaque() || uri.isOpaque()) |
921 |
|
return uri; |
922 |
|
if (scheme == null && uri.getScheme() != null) |
923 |
|
return uri; |
924 |
|
if (scheme != null && !(scheme.equals(uri.getScheme()))) |
925 |
|
return uri; |
926 |
|
if (rawAuthority == null && uri.getRawAuthority() != null) |
927 |
|
return uri; |
928 |
|
if (rawAuthority != null && !(rawAuthority.equals(uri.getRawAuthority()))) |
929 |
|
return uri; |
930 |
|
if (!(uri.getRawPath().startsWith(rawPath))) |
931 |
|
return uri; |
932 |
|
try |
933 |
|
{ |
934 |
|
return new URI(null, null, |
935 |
|
uri.getRawPath().substring(rawPath.length()), |
936 |
|
uri.getRawQuery(), uri.getRawFragment()); |
937 |
|
} |
938 |
|
catch (URISyntaxException e) |
939 |
|
{ |
940 |
|
throw (Error) new InternalError("Relativized URI variant could not "+ |
941 |
|
"be constructed").initCause(e); |
942 |
|
} |
943 |
} |
} |
944 |
|
|
945 |
/** |
/** |
946 |
* Creates an URL from an URI |
* Creates an URL from an URI |
947 |
* |
* |
948 |
* @exception MalformedURLException If a protocol handler for the URL could |
* @throws MalformedURLException If a protocol handler for the URL could |
949 |
* not be found, or if some other error occurred while constructing the URL |
* not be found, or if some other error occurred while constructing the URL |
950 |
* @exception IllegalArgumentException If the URI is not absolute |
* @throws IllegalArgumentException If the URI is not absolute |
951 |
*/ |
*/ |
952 |
public URL toURL() throws IllegalArgumentException, MalformedURLException |
public URL toURL() throws IllegalArgumentException, MalformedURLException |
953 |
{ |
{ |
999 |
} |
} |
1000 |
|
|
1001 |
/** |
/** |
1002 |
* Returns the rae authority part of this URI |
* Returns the raw authority part of this URI |
1003 |
*/ |
*/ |
1004 |
public String getRawAuthority() |
public String getRawAuthority() |
1005 |
{ |
{ |
1095 |
} |
} |
1096 |
|
|
1097 |
/** |
/** |
1098 |
* Compares the URI with a given object |
* <p> |
1099 |
* |
* Compares the URI with the given object for equality. If the |
1100 |
* @param obj The obj to compare the URI with |
* object is not a <code>URI</code>, then the method returns false. |
1101 |
|
* Otherwise, the following criteria are observed: |
1102 |
|
* </p> |
1103 |
|
* <ul> |
1104 |
|
* <li>The scheme of the URIs must either be null (undefined) in both cases, |
1105 |
|
* or equal, ignorant of case.</li> |
1106 |
|
* <li>The raw fragment of the URIs must either be null (undefined) in both |
1107 |
|
* cases, or equal, ignorant of case.</li> |
1108 |
|
* <li>Both URIs must be of the same type (opaque or hierarchial)</li> |
1109 |
|
* <li><strong>For opaque URIs:</strong></li> |
1110 |
|
* <ul> |
1111 |
|
* <li>The raw scheme-specific parts must be equal.</li> |
1112 |
|
* </ul> |
1113 |
|
* <li>For hierarchical URIs:</li> |
1114 |
|
* <ul> |
1115 |
|
* <li>The raw paths must be equal, ignorant of case.</li> |
1116 |
|
* <li>The raw queries are either both undefined or both equal, ignorant |
1117 |
|
* of case.</li> |
1118 |
|
* <li>The raw authority sections are either both undefined or:</li> |
1119 |
|
* <li><strong>For registry-based authorities:</strong></li> |
1120 |
|
* <ul><li>they are equal.</li></ul> |
1121 |
|
* <li><strong>For server-based authorities:</strong></li> |
1122 |
|
* <ul> |
1123 |
|
* <li>the hosts are equal, ignoring case</li> |
1124 |
|
* <li>the ports are equal</li> |
1125 |
|
* <li>the user information components are equal</li> |
1126 |
|
* </ul> |
1127 |
|
* </ul> |
1128 |
|
* </ul> |
1129 |
|
* |
1130 |
|
* @param obj the obj to compare the URI with. |
1131 |
|
* @return <code>true</code> if the objects are equal, according to |
1132 |
|
* the specification above. |
1133 |
*/ |
*/ |
1134 |
public boolean equals(Object obj) |
public boolean equals(Object obj) |
1135 |
{ |
{ |
1136 |
return false; |
if (!(obj instanceof URI)) |
1137 |
|
return false; |
1138 |
|
URI uriObj = (URI) obj; |
1139 |
|
if (scheme == null) |
1140 |
|
{ |
1141 |
|
if (uriObj.getScheme() != null) |
1142 |
|
return false; |
1143 |
|
} |
1144 |
|
else |
1145 |
|
if (!(scheme.equalsIgnoreCase(uriObj.getScheme()))) |
1146 |
|
return false; |
1147 |
|
if (rawFragment == null) |
1148 |
|
{ |
1149 |
|
if (uriObj.getRawFragment() != null) |
1150 |
|
return false; |
1151 |
|
} |
1152 |
|
else |
1153 |
|
if (!(rawFragment.equalsIgnoreCase(uriObj.getRawFragment()))) |
1154 |
|
return false; |
1155 |
|
boolean opaqueThis = isOpaque(); |
1156 |
|
boolean opaqueObj = uriObj.isOpaque(); |
1157 |
|
if (opaqueThis && opaqueObj) |
1158 |
|
return rawSchemeSpecificPart.equals(uriObj.getRawSchemeSpecificPart()); |
1159 |
|
else if (!opaqueThis && !opaqueObj) |
1160 |
|
{ |
1161 |
|
boolean common = rawPath.equalsIgnoreCase(uriObj.getRawPath()) |
1162 |
|
&& ((rawQuery == null && uriObj.getRawQuery() == null) |
1163 |
|
|| rawQuery.equalsIgnoreCase(uriObj.getRawQuery())); |
1164 |
|
if (rawAuthority == null && uriObj.getRawAuthority() == null) |
1165 |
|
return common; |
1166 |
|
if (host == null) |
1167 |
|
return common |
1168 |
|
&& rawAuthority.equalsIgnoreCase(uriObj.getRawAuthority()); |
1169 |
|
return common |
1170 |
|
&& host.equalsIgnoreCase(uriObj.getHost()) |
1171 |
|
&& port == uriObj.getPort() |
1172 |
|
&& (rawUserInfo == null ? |
1173 |
|
uriObj.getRawUserInfo() == null : |
1174 |
|
rawUserInfo.equalsIgnoreCase(uriObj.getRawUserInfo())); |
1175 |
|
} |
1176 |
|
else |
1177 |
|
return false; |
1178 |
} |
} |
1179 |
|
|
1180 |
/** |
/** |
1181 |
* Computes the hascode of the URI |
* Computes the hashcode of the URI |
1182 |
*/ |
*/ |
1183 |
public int hashCode() |
public int hashCode() |
1184 |
{ |
{ |
1185 |
return 0; |
return (getScheme() == null ? 0 : 13 * getScheme().hashCode()) |
1186 |
|
+ 17 * getRawSchemeSpecificPart().hashCode() |
1187 |
|
+ (getRawFragment() == null ? 0 : 21 + getRawFragment().hashCode()); |
1188 |
} |
} |
1189 |
|
|
1190 |
/** |
/** |
1191 |
* Compare the URI with another object that must be an URI too |
* Compare the URI with another object that must also be a URI. |
1192 |
|
* Undefined components are taken to be less than any other component. |
1193 |
|
* The following criteria are observed: |
1194 |
|
* </p> |
1195 |
|
* <ul> |
1196 |
|
* <li>Two URIs with different schemes are compared according to their |
1197 |
|
* scheme, regardless of case.</li> |
1198 |
|
* <li>A hierarchical URI is less than an opaque URI with the same |
1199 |
|
* scheme.</li> |
1200 |
|
* <li><strong>For opaque URIs:</strong></li> |
1201 |
|
* <ul> |
1202 |
|
* <li>URIs with differing scheme-specific parts are ordered according |
1203 |
|
* to the ordering of the scheme-specific part.</li> |
1204 |
|
* <li>URIs with the same scheme-specific part are ordered by the |
1205 |
|
* raw fragment.</li> |
1206 |
|
* </ul> |
1207 |
|
* <li>For hierarchical URIs:</li> |
1208 |
|
* <ul> |
1209 |
|
* <li>URIs are ordered according to their raw authority sections, |
1210 |
|
* if they are unequal.</li> |
1211 |
|
* <li><strong>For registry-based authorities:</strong></li> |
1212 |
|
* <ul><li>they are ordered according to the ordering of the authority |
1213 |
|
* component.</li></ul> |
1214 |
|
* <li><strong>For server-based authorities:</strong></li> |
1215 |
|
* <ul> |
1216 |
|
* <li>URIs are ordered according to the raw user information.</li> |
1217 |
|
* <li>URIs with the same user information are ordered by the host, |
1218 |
|
* ignoring case.</li> |
1219 |
|
* <lI>URIs with the same host are ordered by the port.</li> |
1220 |
|
* </ul> |
1221 |
|
* <li>URIs with the same authority section are ordered by the raw path.</li> |
1222 |
|
* <li>URIs with the same path are ordered by their raw query.</li> |
1223 |
|
* <li>URIs with the same query are ordered by their raw fragments.</li> |
1224 |
|
* </ul> |
1225 |
|
* </ul> |
1226 |
* |
* |
1227 |
* @param obj This object to compare this URI with |
* @param obj This object to compare this URI with |
1228 |
|
* @return a negative integer, zero or a positive integer depending |
1229 |
|
* on whether this URI is less than, equal to or greater |
1230 |
|
* than that supplied, respectively. |
1231 |
|
* @throws ClassCastException if the given object is not a URI |
1232 |
|
*/ |
1233 |
|
public int compareTo(Object obj) |
1234 |
|
throws ClassCastException |
1235 |
|
{ |
1236 |
|
URI uri = (URI) obj; |
1237 |
|
if (scheme == null && uri.getScheme() != null) |
1238 |
|
return -1; |
1239 |
|
if (scheme != null) |
1240 |
|
{ |
1241 |
|
int sCompare = scheme.compareToIgnoreCase(uri.getScheme()); |
1242 |
|
if (sCompare != 0) |
1243 |
|
return sCompare; |
1244 |
|
} |
1245 |
|
boolean opaqueThis = isOpaque(); |
1246 |
|
boolean opaqueObj = uri.isOpaque(); |
1247 |
|
if (opaqueThis && !opaqueObj) |
1248 |
|
return 1; |
1249 |
|
if (!opaqueThis && opaqueObj) |
1250 |
|
return -1; |
1251 |
|
if (opaqueThis) |
1252 |
|
{ |
1253 |
|
int ssCompare = |
1254 |
|
rawSchemeSpecificPart.compareTo(uri.getRawSchemeSpecificPart()); |
1255 |
|
if (ssCompare == 0) |
1256 |
|
return compareFragments(uri); |
1257 |
|
else |
1258 |
|
return ssCompare; |
1259 |
|
} |
1260 |
|
if (rawAuthority == null && uri.getRawAuthority() != null) |
1261 |
|
return -1; |
1262 |
|
if (rawAuthority != null) |
1263 |
|
{ |
1264 |
|
int aCompare = rawAuthority.compareTo(uri.getRawAuthority()); |
1265 |
|
if (aCompare != 0) |
1266 |
|
{ |
1267 |
|
if (host == null) |
1268 |
|
return aCompare; |
1269 |
|
if (rawUserInfo == null && uri.getRawUserInfo() != null) |
1270 |
|
return -1; |
1271 |
|
int uCompare = rawUserInfo.compareTo(uri.getRawUserInfo()); |
1272 |
|
if (uCompare != 0) |
1273 |
|
return uCompare; |
1274 |
|
if (host == null && uri.getHost() != null) |
1275 |
|
return -1; |
1276 |
|
int hCompare = host.compareTo(uri.getHost()); |
1277 |
|
if (hCompare != 0) |
1278 |
|
return hCompare; |
1279 |
|
return new Integer(port).compareTo(new Integer(uri.getPort())); |
1280 |
|
} |
1281 |
|
} |
1282 |
|
if (rawPath == null && uri.getRawPath() != null) |
1283 |
|
return -1; |
1284 |
|
if (rawPath != null) |
1285 |
|
{ |
1286 |
|
int pCompare = rawPath.compareTo(uri.getRawPath()); |
1287 |
|
if (pCompare != 0) |
1288 |
|
return pCompare; |
1289 |
|
} |
1290 |
|
if (rawQuery == null && uri.getRawQuery() != null) |
1291 |
|
return -1; |
1292 |
|
if (rawQuery != null) |
1293 |
|
{ |
1294 |
|
int qCompare = rawQuery.compareTo(uri.getRawQuery()); |
1295 |
|
if (qCompare != 0) |
1296 |
|
return qCompare; |
1297 |
|
} |
1298 |
|
return compareFragments(uri); |
1299 |
|
} |
1300 |
|
|
1301 |
|
/** |
1302 |
|
* Compares the fragment of this URI with that of the supplied URI. |
1303 |
* |
* |
1304 |
* @exception ClassCastException If given object ist not an URI |
* @param uri the URI to compare with this one. |
1305 |
|
* @return a negative integer, zero or a positive integer depending |
1306 |
|
* on whether this uri's fragment is less than, equal to |
1307 |
|
* or greater than the fragment of the uri supplied, respectively. |
1308 |
*/ |
*/ |
1309 |
public int compareTo(Object obj) throws ClassCastException |
private int compareFragments(URI uri) |
1310 |
{ |
{ |
1311 |
return 0; |
if (rawFragment == null && uri.getRawFragment() != null) |
1312 |
|
return -1; |
1313 |
|
else if (rawFragment == null) |
1314 |
|
return 0; |
1315 |
|
else |
1316 |
|
return rawFragment.compareTo(uri.getRawFragment()); |
1317 |
} |
} |
1318 |
|
|
1319 |
/** |
/** |
1324 |
*/ |
*/ |
1325 |
public String toString() |
public String toString() |
1326 |
{ |
{ |
1327 |
return (getScheme() == null ? "" : getScheme() + ":") |
return (scheme == null ? "" : scheme + ":") |
1328 |
+ getRawSchemeSpecificPart() |
+ rawSchemeSpecificPart |
1329 |
+ (getRawFragment() == null ? "" : "#" + getRawFragment()); |
+ (rawFragment == null ? "" : "#" + rawFragment); |
1330 |
} |
} |
1331 |
|
|
1332 |
/** |
/** |
1333 |
* Returns the URI as US-ASCII string |
* Returns the URI as US-ASCII string. This is the same as the result |
1334 |
|
* from <code>toString()</code> for URIs that don't contain any non-US-ASCII |
1335 |
|
* characters. Otherwise, the non-US-ASCII characters are replaced |
1336 |
|
* by their percent-encoded representations. |
1337 |
|
* |
1338 |
|
* @return a string representation of the URI, containing only US-ASCII |
1339 |
|
* characters. |
1340 |
*/ |
*/ |
1341 |
public String toASCIIString() |
public String toASCIIString() |
1342 |
{ |
{ |
1343 |
return ""; |
String strRep = toString(); |
1344 |
|
boolean inNonAsciiBlock = false; |
1345 |
|
StringBuffer buffer = new StringBuffer(); |
1346 |
|
StringBuffer encBuffer = null; |
1347 |
|
for (int i = 0; i < strRep.length(); i++) |
1348 |
|
{ |
1349 |
|
char c = strRep.charAt(i); |
1350 |
|
if (c <= 127) |
1351 |
|
{ |
1352 |
|
if (inNonAsciiBlock) |
1353 |
|
{ |
1354 |
|
buffer.append(escapeCharacters(encBuffer.toString())); |
1355 |
|
inNonAsciiBlock = false; |
1356 |
|
} |
1357 |
|
buffer.append(c); |
1358 |
|
} |
1359 |
|
else |
1360 |
|
{ |
1361 |
|
if (!inNonAsciiBlock) |
1362 |
|
{ |
1363 |
|
encBuffer = new StringBuffer(); |
1364 |
|
inNonAsciiBlock = true; |
1365 |
|
} |
1366 |
|
encBuffer.append(c); |
1367 |
|
} |
1368 |
|
} |
1369 |
|
return buffer.toString(); |
1370 |
} |
} |
1371 |
|
|
1372 |
|
/** |
1373 |
|
* Converts the non-ASCII characters in the supplied string |
1374 |
|
* to their equivalent percent-encoded representations. |
1375 |
|
* That is, they are replaced by "%" followed by their hexadecimal value. |
1376 |
|
* |
1377 |
|
* @param str a string including non-ASCII characters. |
1378 |
|
* @return the string with the non-ASCII characters converted to their |
1379 |
|
* percent-encoded representations. |
1380 |
|
*/ |
1381 |
|
private static String escapeCharacters(String str) |
1382 |
|
{ |
1383 |
|
try |
1384 |
|
{ |
1385 |
|
StringBuffer sb = new StringBuffer(); |
1386 |
|
// this is far from optimal, but it works |
1387 |
|
byte[] utf8 = str.getBytes("utf-8"); |
1388 |
|
for (int j = 0; j < utf8.length; j++) |
1389 |
|
{ |
1390 |
|
sb.append('%'); |
1391 |
|
sb.append(HEX.charAt((utf8[j] & 0xff) / 16)); |
1392 |
|
sb.append(HEX.charAt((utf8[j] & 0xff) % 16)); |
1393 |
|
} |
1394 |
|
return sb.toString(); |
1395 |
|
} |
1396 |
|
catch (java.io.UnsupportedEncodingException x) |
1397 |
|
{ |
1398 |
|
throw (Error) new InternalError("Escaping error").initCause(x); |
1399 |
|
} |
1400 |
|
} |
1401 |
|
|
1402 |
} |
} |