|
1 /* |
|
2 * ==================================================================== |
|
3 * |
|
4 * Licensed to the Apache Software Foundation (ASF) under one or more |
|
5 * contributor license agreements. See the NOTICE file distributed with |
|
6 * this work for additional information regarding copyright ownership. |
|
7 * The ASF licenses this file to You under the Apache License, Version 2.0 |
|
8 * (the "License"); you may not use this file except in compliance with |
|
9 * the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, software |
|
14 * distributed under the License is distributed on an "AS IS" BASIS, |
|
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
16 * See the License for the specific language governing permissions and |
|
17 * limitations under the License. |
|
18 * ==================================================================== |
|
19 * |
|
20 * This software consists of voluntary contributions made by many |
|
21 * individuals on behalf of the Apache Software Foundation. For more |
|
22 * information on the Apache Software Foundation, please see |
|
23 * <http://www.apache.org/>. |
|
24 * |
|
25 */ |
|
26 |
|
27 package ch.boye.httpclientandroidlib.client.utils; |
|
28 |
|
29 import java.net.URI; |
|
30 import java.net.URISyntaxException; |
|
31 import java.util.Stack; |
|
32 |
|
33 import ch.boye.httpclientandroidlib.annotation.Immutable; |
|
34 |
|
35 import ch.boye.httpclientandroidlib.HttpHost; |
|
36 |
|
37 /** |
|
38 * A collection of utilities for {@link URI URIs}, to workaround |
|
39 * bugs within the class or for ease-of-use features. |
|
40 * |
|
41 * @since 4.0 |
|
42 */ |
|
43 @Immutable |
|
44 public class URIUtils { |
|
45 |
|
46 /** |
|
47 * Constructs a {@link URI} using all the parameters. This should be |
|
48 * used instead of |
|
49 * {@link URI#URI(String, String, String, int, String, String, String)} |
|
50 * or any of the other URI multi-argument URI constructors. |
|
51 * |
|
52 * @param scheme |
|
53 * Scheme name |
|
54 * @param host |
|
55 * Host name |
|
56 * @param port |
|
57 * Port number |
|
58 * @param path |
|
59 * Path |
|
60 * @param query |
|
61 * Query |
|
62 * @param fragment |
|
63 * Fragment |
|
64 * |
|
65 * @throws URISyntaxException |
|
66 * If both a scheme and a path are given but the path is |
|
67 * relative, if the URI string constructed from the given |
|
68 * components violates RFC 2396, or if the authority |
|
69 * component of the string is present but cannot be parsed |
|
70 * as a server-based authority |
|
71 */ |
|
72 public static URI createURI( |
|
73 final String scheme, |
|
74 final String host, |
|
75 int port, |
|
76 final String path, |
|
77 final String query, |
|
78 final String fragment) throws URISyntaxException { |
|
79 |
|
80 StringBuilder buffer = new StringBuilder(); |
|
81 if (host != null) { |
|
82 if (scheme != null) { |
|
83 buffer.append(scheme); |
|
84 buffer.append("://"); |
|
85 } |
|
86 buffer.append(host); |
|
87 if (port > 0) { |
|
88 buffer.append(':'); |
|
89 buffer.append(port); |
|
90 } |
|
91 } |
|
92 if (path == null || !path.startsWith("/")) { |
|
93 buffer.append('/'); |
|
94 } |
|
95 if (path != null) { |
|
96 buffer.append(path); |
|
97 } |
|
98 if (query != null) { |
|
99 buffer.append('?'); |
|
100 buffer.append(query); |
|
101 } |
|
102 if (fragment != null) { |
|
103 buffer.append('#'); |
|
104 buffer.append(fragment); |
|
105 } |
|
106 return new URI(buffer.toString()); |
|
107 } |
|
108 |
|
109 /** |
|
110 * A convenience method for creating a new {@link URI} whose scheme, host |
|
111 * and port are taken from the target host, but whose path, query and |
|
112 * fragment are taken from the existing URI. The fragment is only used if |
|
113 * dropFragment is false. |
|
114 * |
|
115 * @param uri |
|
116 * Contains the path, query and fragment to use. |
|
117 * @param target |
|
118 * Contains the scheme, host and port to use. |
|
119 * @param dropFragment |
|
120 * True if the fragment should not be copied. |
|
121 * |
|
122 * @throws URISyntaxException |
|
123 * If the resulting URI is invalid. |
|
124 */ |
|
125 public static URI rewriteURI( |
|
126 final URI uri, |
|
127 final HttpHost target, |
|
128 boolean dropFragment) throws URISyntaxException { |
|
129 if (uri == null) { |
|
130 throw new IllegalArgumentException("URI may nor be null"); |
|
131 } |
|
132 if (target != null) { |
|
133 return URIUtils.createURI( |
|
134 target.getSchemeName(), |
|
135 target.getHostName(), |
|
136 target.getPort(), |
|
137 normalizePath(uri.getRawPath()), |
|
138 uri.getRawQuery(), |
|
139 dropFragment ? null : uri.getRawFragment()); |
|
140 } else { |
|
141 return URIUtils.createURI( |
|
142 null, |
|
143 null, |
|
144 -1, |
|
145 normalizePath(uri.getRawPath()), |
|
146 uri.getRawQuery(), |
|
147 dropFragment ? null : uri.getRawFragment()); |
|
148 } |
|
149 } |
|
150 |
|
151 private static String normalizePath(String path) { |
|
152 if (path == null) { |
|
153 return null; |
|
154 } |
|
155 int n = 0; |
|
156 for (; n < path.length(); n++) { |
|
157 if (path.charAt(n) != '/') { |
|
158 break; |
|
159 } |
|
160 } |
|
161 if (n > 1) { |
|
162 path = path.substring(n - 1); |
|
163 } |
|
164 return path; |
|
165 } |
|
166 |
|
167 /** |
|
168 * A convenience method for |
|
169 * {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the |
|
170 * fragment. |
|
171 */ |
|
172 public static URI rewriteURI( |
|
173 final URI uri, |
|
174 final HttpHost target) throws URISyntaxException { |
|
175 return rewriteURI(uri, target, false); |
|
176 } |
|
177 |
|
178 /** |
|
179 * Resolves a URI reference against a base URI. Work-around for bug in |
|
180 * java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) |
|
181 * |
|
182 * @param baseURI the base URI |
|
183 * @param reference the URI reference |
|
184 * @return the resulting URI |
|
185 */ |
|
186 public static URI resolve(final URI baseURI, final String reference) { |
|
187 return URIUtils.resolve(baseURI, URI.create(reference)); |
|
188 } |
|
189 |
|
190 /** |
|
191 * Resolves a URI reference against a base URI. Work-around for bugs in |
|
192 * java.net.URI (e.g. <http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) |
|
193 * |
|
194 * @param baseURI the base URI |
|
195 * @param reference the URI reference |
|
196 * @return the resulting URI |
|
197 */ |
|
198 public static URI resolve(final URI baseURI, URI reference){ |
|
199 if (baseURI == null) { |
|
200 throw new IllegalArgumentException("Base URI may nor be null"); |
|
201 } |
|
202 if (reference == null) { |
|
203 throw new IllegalArgumentException("Reference URI may nor be null"); |
|
204 } |
|
205 String s = reference.toString(); |
|
206 if (s.startsWith("?")) { |
|
207 return resolveReferenceStartingWithQueryString(baseURI, reference); |
|
208 } |
|
209 boolean emptyReference = s.length() == 0; |
|
210 if (emptyReference) { |
|
211 reference = URI.create("#"); |
|
212 } |
|
213 URI resolved = baseURI.resolve(reference); |
|
214 if (emptyReference) { |
|
215 String resolvedString = resolved.toString(); |
|
216 resolved = URI.create(resolvedString.substring(0, |
|
217 resolvedString.indexOf('#'))); |
|
218 } |
|
219 return removeDotSegments(resolved); |
|
220 } |
|
221 |
|
222 /** |
|
223 * Resolves a reference starting with a query string. |
|
224 * |
|
225 * @param baseURI the base URI |
|
226 * @param reference the URI reference starting with a query string |
|
227 * @return the resulting URI |
|
228 */ |
|
229 private static URI resolveReferenceStartingWithQueryString( |
|
230 final URI baseURI, final URI reference) { |
|
231 String baseUri = baseURI.toString(); |
|
232 baseUri = baseUri.indexOf('?') > -1 ? |
|
233 baseUri.substring(0, baseUri.indexOf('?')) : baseUri; |
|
234 return URI.create(baseUri + reference.toString()); |
|
235 } |
|
236 |
|
237 /** |
|
238 * Removes dot segments according to RFC 3986, section 5.2.4 |
|
239 * |
|
240 * @param uri the original URI |
|
241 * @return the URI without dot segments |
|
242 */ |
|
243 private static URI removeDotSegments(URI uri) { |
|
244 String path = uri.getPath(); |
|
245 if ((path == null) || (path.indexOf("/.") == -1)) { |
|
246 // No dot segments to remove |
|
247 return uri; |
|
248 } |
|
249 String[] inputSegments = path.split("/"); |
|
250 Stack<String> outputSegments = new Stack<String>(); |
|
251 for (int i = 0; i < inputSegments.length; i++) { |
|
252 if ((inputSegments[i].length() == 0) |
|
253 || (".".equals(inputSegments[i]))) { |
|
254 // Do nothing |
|
255 } else if ("..".equals(inputSegments[i])) { |
|
256 if (!outputSegments.isEmpty()) { |
|
257 outputSegments.pop(); |
|
258 } |
|
259 } else { |
|
260 outputSegments.push(inputSegments[i]); |
|
261 } |
|
262 } |
|
263 StringBuilder outputBuffer = new StringBuilder(); |
|
264 for (String outputSegment : outputSegments) { |
|
265 outputBuffer.append('/').append(outputSegment); |
|
266 } |
|
267 try { |
|
268 return new URI(uri.getScheme(), uri.getAuthority(), |
|
269 outputBuffer.toString(), uri.getQuery(), uri.getFragment()); |
|
270 } catch (URISyntaxException e) { |
|
271 throw new IllegalArgumentException(e); |
|
272 } |
|
273 } |
|
274 |
|
275 /** |
|
276 * Extracts target host from the given {@link URI}. |
|
277 * |
|
278 * @param uri |
|
279 * @return the target host if the URI is absolute or <code>null</null> if the URI is |
|
280 * relative or does not contain a valid host name. |
|
281 * |
|
282 * @since 4.1 |
|
283 */ |
|
284 public static HttpHost extractHost(final URI uri) { |
|
285 if (uri == null) { |
|
286 return null; |
|
287 } |
|
288 HttpHost target = null; |
|
289 if (uri.isAbsolute()) { |
|
290 int port = uri.getPort(); // may be overridden later |
|
291 String host = uri.getHost(); |
|
292 if (host == null) { // normal parse failed; let's do it ourselves |
|
293 // authority does not seem to care about the valid character-set for host names |
|
294 host = uri.getAuthority(); |
|
295 if (host != null) { |
|
296 // Strip off any leading user credentials |
|
297 int at = host.indexOf('@'); |
|
298 if (at >= 0) { |
|
299 if (host.length() > at+1 ) { |
|
300 host = host.substring(at+1); |
|
301 } else { |
|
302 host = null; // @ on its own |
|
303 } |
|
304 } |
|
305 // Extract the port suffix, if present |
|
306 if (host != null) { |
|
307 int colon = host.indexOf(':'); |
|
308 if (colon >= 0) { |
|
309 if (colon+1 < host.length()) { |
|
310 port = Integer.parseInt(host.substring(colon+1)); |
|
311 } |
|
312 host = host.substring(0,colon); |
|
313 } |
|
314 } |
|
315 } |
|
316 } |
|
317 String scheme = uri.getScheme(); |
|
318 if (host != null) { |
|
319 target = new HttpHost(host, port, scheme); |
|
320 } |
|
321 } |
|
322 return target; |
|
323 } |
|
324 |
|
325 /** |
|
326 * This class should not be instantiated. |
|
327 */ |
|
328 private URIUtils() { |
|
329 } |
|
330 |
|
331 } |