View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with 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,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.utils;
28  
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Locale;
35  
36  import org.apache.hc.core5.http.HttpHost;
37  import org.apache.hc.core5.http.URIScheme;
38  import org.apache.hc.core5.net.URIAuthority;
39  import org.apache.hc.core5.net.URIBuilder;
40  import org.apache.hc.core5.util.Args;
41  import org.apache.hc.core5.util.TextUtils;
42  
43  /**
44   * A collection of utilities for {@link URI URIs}, to workaround
45   * bugs within the class or for ease-of-use features.
46   *
47   * @since 4.0
48   */
49  public class URIUtils {
50  
51      /**
52       * A convenience method for creating a new {@link URI} whose scheme, host
53       * and port are taken from the target host, but whose path, query and
54       * fragment are taken from the existing URI. The fragment is only used if
55       * dropFragment is false. The path is set to "/" if not explicitly specified.
56       *
57       * @param uri
58       *            Contains the path, query and fragment to use.
59       * @param target
60       *            Contains the scheme, host and port to use.
61       * @param dropFragment
62       *            True if the fragment should not be copied.
63       *
64       * @throws URISyntaxException
65       *             If the resulting URI is invalid.
66       *
67       * @deprecated Use {@link URIBuilder}.
68       */
69      @Deprecated
70      public static URI rewriteURI(
71              final URI uri,
72              final HttpHost target,
73              final boolean dropFragment) throws URISyntaxException {
74          Args.notNull(uri, "URI");
75          if (uri.isOpaque()) {
76              return uri;
77          }
78          final URIBuilder uribuilder = new URIBuilder(uri);
79          if (target != null) {
80              uribuilder.setScheme(target.getSchemeName());
81              uribuilder.setHost(target.getHostName());
82              uribuilder.setPort(target.getPort());
83          } else {
84              uribuilder.setScheme(null);
85              uribuilder.setHost((String) null);
86              uribuilder.setPort(-1);
87          }
88          if (dropFragment) {
89              uribuilder.setFragment(null);
90          }
91          final List<String> originalPathSegments = uribuilder.getPathSegments();
92          final List<String> pathSegments = new ArrayList<>(originalPathSegments);
93          for (final Iterator<String> it = pathSegments.iterator(); it.hasNext(); ) {
94              final String pathSegment = it.next();
95              if (pathSegment.isEmpty() && it.hasNext()) {
96                  it.remove();
97              }
98          }
99          if (pathSegments.size() != originalPathSegments.size()) {
100             uribuilder.setPathSegments(pathSegments);
101         }
102         if (pathSegments.isEmpty()) {
103             uribuilder.setPathSegments("");
104         }
105         return uribuilder.build();
106     }
107 
108     /**
109      * A convenience method for
110      * {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the
111      * fragment.
112      *
113      * @deprecated Use {@link URIBuilder}.
114      */
115     @Deprecated
116     public static URI rewriteURI(
117             final URI uri,
118             final HttpHost target) throws URISyntaxException {
119         return rewriteURI(uri, target, false);
120     }
121 
122     /**
123      * A convenience method that creates a new {@link URI} whose scheme, host, port, path,
124      * query are taken from the existing URI, dropping any fragment or user-information.
125      * The path is set to "/" if not explicitly specified. The existing URI is returned
126      * unmodified if it has no fragment or user-information and has a path.
127      *
128      * @param uri
129      *            original URI.
130      * @throws URISyntaxException
131      *             If the resulting URI is invalid.
132      *
133      * @deprecated Use {@link URIBuilder}.
134      */
135     @Deprecated
136     public static URI rewriteURI(final URI uri) throws URISyntaxException {
137         Args.notNull(uri, "URI");
138         if (uri.isOpaque()) {
139             return uri;
140         }
141         final URIBuilder uribuilder = new URIBuilder(uri);
142         if (uribuilder.getUserInfo() != null) {
143             uribuilder.setUserInfo(null);
144         }
145         if (uribuilder.isPathEmpty()) {
146             uribuilder.setPathSegments("");
147         }
148         if (uribuilder.getHost() != null) {
149             uribuilder.setHost(uribuilder.getHost().toLowerCase(Locale.ROOT));
150         }
151         uribuilder.setFragment(null);
152         return uribuilder.build();
153     }
154 
155     /**
156      * Resolves a URI reference against a base URI. Work-around for bug in
157      * java.net.URI (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535)
158      *
159      * @param baseURI the base URI
160      * @param reference the URI reference
161      * @return the resulting URI
162      */
163     public static URI resolve(final URI baseURI, final String reference) {
164         return resolve(baseURI, URI.create(reference));
165     }
166 
167     /**
168      * Resolves a URI reference against a base URI. Work-around for bugs in
169      * java.net.URI (e.g. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535)
170      *
171      * @param baseURI the base URI
172      * @param reference the URI reference
173      * @return the resulting URI
174      */
175     public static URI resolve(final URI baseURI, final URI reference) {
176         Args.notNull(baseURI, "Base URI");
177         Args.notNull(reference, "Reference URI");
178         final String s = reference.toASCIIString();
179         if (s.startsWith("?")) {
180             String baseUri = baseURI.toASCIIString();
181             final int i = baseUri.indexOf('?');
182             baseUri = i > -1 ? baseUri.substring(0, i) : baseUri;
183             return URI.create(baseUri + s);
184         }
185         final boolean emptyReference = s.isEmpty();
186         URI resolved;
187         if (emptyReference) {
188             resolved = baseURI.resolve(URI.create("#"));
189             final String resolvedString = resolved.toASCIIString();
190             resolved = URI.create(resolvedString.substring(0, resolvedString.indexOf('#')));
191         } else {
192             resolved = baseURI.resolve(reference);
193         }
194         try {
195             return normalizeSyntax(resolved);
196         } catch (final URISyntaxException ex) {
197             throw new IllegalArgumentException(ex);
198         }
199     }
200 
201     /**
202      * Removes dot segments and performs Syntax-Based Normalization.
203      *
204      * @param uri the original URI
205      * @return the URI without dot segments
206      */
207     static URI normalizeSyntax(final URI uri) throws URISyntaxException {
208         if (uri.isOpaque() || uri.getAuthority() == null) {
209             // opaque and file: URIs
210             return uri;
211         }
212         final URIBuilder builder = new URIBuilder(uri);
213         builder.normalizeSyntax();
214         if (builder.getScheme() == null) {
215             builder.setScheme(URIScheme.HTTP.id);
216         }
217         if (builder.isPathEmpty()) {
218             builder.setPathSegments("");
219         }
220         return builder.build();
221     }
222 
223     /**
224      * Extracts target host from the given {@link URI}.
225      *
226      * @param uri
227      * @return the target host if the URI is absolute or {@code null} if the URI is
228      * relative or does not contain a valid host name.
229      *
230      * @since 4.1
231      */
232     public static HttpHost extractHost(final URI uri) {
233         if (uri == null) {
234             return null;
235         }
236         final URIBuilder uriBuilder = new URIBuilder(uri);
237         final String scheme = uriBuilder.getScheme();
238         final String host = uriBuilder.getHost();
239         final int port = uriBuilder.getPort();
240         if (!TextUtils.isBlank(host)) {
241             try {
242                 return new HttpHost(scheme, host, port);
243             } catch (final IllegalArgumentException ignore) {
244             }
245         }
246         return null;
247     }
248 
249     /**
250      * Derives the interpreted (absolute) URI that was used to generate the last
251      * request. This is done by extracting the request-uri and target origin for
252      * the last request and scanning all the redirect locations for the last
253      * fragment identifier, then combining the result into a {@link URI}.
254      *
255      * @param originalURI
256      *            original request before any redirects
257      * @param target
258      *            if the last URI is relative, it is resolved against this target,
259      *            or {@code null} if not available.
260      * @param redirects
261      *            collection of redirect locations since the original request
262      *            or {@code null} if not available.
263      * @return interpreted (absolute) URI
264      */
265     public static URI resolve(
266             final URI originalURI,
267             final HttpHost target,
268             final List<URI> redirects) throws URISyntaxException {
269         Args.notNull(originalURI, "Request URI");
270         final URIBuilder uribuilder;
271         if (redirects == null || redirects.isEmpty()) {
272             uribuilder = new URIBuilder(originalURI);
273         } else {
274             uribuilder = new URIBuilder(redirects.get(redirects.size() - 1));
275             String frag = uribuilder.getFragment();
276             // read interpreted fragment identifier from redirect locations
277             for (int i = redirects.size() - 1; frag == null && i >= 0; i--) {
278                 frag = redirects.get(i).getFragment();
279             }
280             uribuilder.setFragment(frag);
281         }
282         // read interpreted fragment identifier from original request
283         if (uribuilder.getFragment() == null) {
284             uribuilder.setFragment(originalURI.getFragment());
285         }
286         // last target origin
287         if (target != null && !uribuilder.isAbsolute()) {
288             uribuilder.setScheme(target.getSchemeName());
289             uribuilder.setHost(target.getHostName());
290             uribuilder.setPort(target.getPort());
291         }
292         return uribuilder.build();
293     }
294 
295     /**
296      * Convenience factory method for {@link URI} instances.
297      *
298      * @since 5.0
299      *
300      * @deprecated Use {@link URIBuilder}.
301      */
302     @Deprecated
303     public static URI create(final HttpHost host, final String path) throws URISyntaxException {
304         final URIBuilder builder = new URIBuilder(path);
305         if (host != null) {
306             builder.setHost(host.getHostName()).setPort(host.getPort()).setScheme(host.getSchemeName());
307         }
308         return builder.build();
309     }
310 
311     /**
312      * Convenience factory method for {@link URI} instances.
313      *
314      * @since 5.0
315      *
316      * @deprecated Use {@link URIBuilder}.
317      */
318     @Deprecated
319     public static URI create(final String scheme, final URIAuthority host, final String path) throws URISyntaxException {
320         final URIBuilder builder = new URIBuilder(path);
321         if (scheme != null) {
322             builder.setScheme(scheme);
323         }
324         if (host != null) {
325             builder.setHost(host.getHostName()).setPort(host.getPort());
326         }
327         return builder.build();
328     }
329 
330     /**
331      * This class should not be instantiated.
332      */
333     private URIUtils() {
334     }
335 
336 }