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.http.client.utils;
28  
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.nio.charset.Charset;
32  import java.util.ArrayList;
33  import java.util.Iterator;
34  import java.util.List;
35  
36  import org.apache.http.Consts;
37  import org.apache.http.NameValuePair;
38  import org.apache.http.annotation.NotThreadSafe;
39  import org.apache.http.conn.util.InetAddressUtils;
40  import org.apache.http.message.BasicNameValuePair;
41  
42  /**
43   * Builder for {@link URI} instances.
44   *
45   * @since 4.2
46   */
47  @NotThreadSafe
48  public class URIBuilder {
49  
50      private String scheme;
51      private String encodedSchemeSpecificPart;
52      private String encodedAuthority;
53      private String userInfo;
54      private String encodedUserInfo;
55      private String host;
56      private int port;
57      private String path;
58      private String encodedPath;
59      private String encodedQuery;
60      private List<NameValuePair> queryParams;
61      private String query;
62      private Charset charset;
63      private String fragment;
64      private String encodedFragment;
65  
66      /**
67       * Constructs an empty instance.
68       */
69      public URIBuilder() {
70          super();
71          this.port = -1;
72      }
73  
74      /**
75       * Construct an instance from the string which must be a valid URI.
76       *
77       * @param string a valid URI in string form
78       * @throws URISyntaxException if the input is not a valid URI
79       */
80      public URIBuilder(final String string) throws URISyntaxException {
81          super();
82          digestURI(new URI(string));
83      }
84  
85      /**
86       * Construct an instance from the provided URI.
87       * @param uri
88       */
89      public URIBuilder(final URI uri) {
90          super();
91          digestURI(uri);
92      }
93  
94      /**
95       * @since 4.4
96       */
97      public URIBuilder setCharset(final Charset charset) {
98          this.charset = charset;
99          return this;
100     }
101 
102     /**
103      * @since 4.4
104      */
105     public Charset getCharset() {
106         return charset;
107     }
108 
109     private List <NameValuePair> parseQuery(final String query, final Charset charset) {
110         if (query != null && !query.isEmpty()) {
111             return URLEncodedUtils.parse(query, charset);
112         }
113         return null;
114     }
115 
116     /**
117      * Builds a {@link URI} instance.
118      */
119     public URI build() throws URISyntaxException {
120         return new URI(buildString());
121     }
122 
123     private String buildString() {
124         final StringBuilder sb = new StringBuilder();
125         if (this.scheme != null) {
126             sb.append(this.scheme).append(':');
127         }
128         if (this.encodedSchemeSpecificPart != null) {
129             sb.append(this.encodedSchemeSpecificPart);
130         } else {
131             if (this.encodedAuthority != null) {
132                 sb.append("//").append(this.encodedAuthority);
133             } else if (this.host != null) {
134                 sb.append("//");
135                 if (this.encodedUserInfo != null) {
136                     sb.append(this.encodedUserInfo).append("@");
137                 } else if (this.userInfo != null) {
138                     sb.append(encodeUserInfo(this.userInfo)).append("@");
139                 }
140                 if (InetAddressUtils.isIPv6Address(this.host)) {
141                     sb.append("[").append(this.host).append("]");
142                 } else {
143                     sb.append(this.host);
144                 }
145                 if (this.port >= 0) {
146                     sb.append(":").append(this.port);
147                 }
148             }
149             if (this.encodedPath != null) {
150                 sb.append(normalizePath(this.encodedPath));
151             } else if (this.path != null) {
152                 sb.append(encodePath(normalizePath(this.path)));
153             }
154             if (this.encodedQuery != null) {
155                 sb.append("?").append(this.encodedQuery);
156             } else if (this.queryParams != null) {
157                 sb.append("?").append(encodeUrlForm(this.queryParams));
158             } else if (this.query != null) {
159                 sb.append("?").append(encodeUric(this.query));
160             }
161         }
162         if (this.encodedFragment != null) {
163             sb.append("#").append(this.encodedFragment);
164         } else if (this.fragment != null) {
165             sb.append("#").append(encodeUric(this.fragment));
166         }
167         return sb.toString();
168     }
169 
170     private void digestURI(final URI uri) {
171         this.scheme = uri.getScheme();
172         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
173         this.encodedAuthority = uri.getRawAuthority();
174         this.host = uri.getHost();
175         this.port = uri.getPort();
176         this.encodedUserInfo = uri.getRawUserInfo();
177         this.userInfo = uri.getUserInfo();
178         this.encodedPath = uri.getRawPath();
179         this.path = uri.getPath();
180         this.encodedQuery = uri.getRawQuery();
181         this.queryParams = parseQuery(uri.getRawQuery(), this.charset != null ? this.charset : Consts.UTF_8);
182         this.encodedFragment = uri.getRawFragment();
183         this.fragment = uri.getFragment();
184     }
185 
186     private String encodeUserInfo(final String userInfo) {
187         return URLEncodedUtils.encUserInfo(userInfo, this.charset != null ? this.charset : Consts.UTF_8);
188     }
189 
190     private String encodePath(final String path) {
191         return URLEncodedUtils.encPath(path, this.charset != null ? this.charset : Consts.UTF_8);
192     }
193 
194     private String encodeUrlForm(final List<NameValuePair> params) {
195         return URLEncodedUtils.format(params, this.charset != null ? this.charset : Consts.UTF_8);
196     }
197 
198     private String encodeUric(final String fragment) {
199         return URLEncodedUtils.encUric(fragment, this.charset != null ? this.charset : Consts.UTF_8);
200     }
201 
202     /**
203      * Sets URI scheme.
204      */
205     public URIBuilder setScheme(final String scheme) {
206         this.scheme = scheme;
207         return this;
208     }
209 
210     /**
211      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
212      * characters.
213      */
214     public URIBuilder setUserInfo(final String userInfo) {
215         this.userInfo = userInfo;
216         this.encodedSchemeSpecificPart = null;
217         this.encodedAuthority = null;
218         this.encodedUserInfo = null;
219         return this;
220     }
221 
222     /**
223      * Sets URI user info as a combination of username and password. These values are expected to
224      * be unescaped and may contain non ASCII characters.
225      */
226     public URIBuilder setUserInfo(final String username, final String password) {
227         return setUserInfo(username + ':' + password);
228     }
229 
230     /**
231      * Sets URI host.
232      */
233     public URIBuilder setHost(final String host) {
234         this.host = host;
235         this.encodedSchemeSpecificPart = null;
236         this.encodedAuthority = null;
237         return this;
238     }
239 
240     /**
241      * Sets URI port.
242      */
243     public URIBuilder setPort(final int port) {
244         this.port = port < 0 ? -1 : port;
245         this.encodedSchemeSpecificPart = null;
246         this.encodedAuthority = null;
247         return this;
248     }
249 
250     /**
251      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
252      */
253     public URIBuilder setPath(final String path) {
254         this.path = path;
255         this.encodedSchemeSpecificPart = null;
256         this.encodedPath = null;
257         return this;
258     }
259 
260     /**
261      * Removes URI query.
262      */
263     public URIBuilder removeQuery() {
264         this.queryParams = null;
265         this.query = null;
266         this.encodedQuery = null;
267         this.encodedSchemeSpecificPart = null;
268         return this;
269     }
270 
271     /**
272      * Sets URI query.
273      * <p>
274      * The value is expected to be encoded form data.
275      *
276      * @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)}
277      *
278      * @see URLEncodedUtils#parse
279      */
280     @Deprecated
281     public URIBuilder setQuery(final String query) {
282         this.queryParams = parseQuery(query, this.charset != null ? this.charset : Consts.UTF_8);
283         this.query = null;
284         this.encodedQuery = null;
285         this.encodedSchemeSpecificPart = null;
286         return this;
287     }
288 
289     /**
290      * Sets URI query parameters. The parameter name / values are expected to be unescaped
291      * and may contain non ASCII characters.
292      * <p>
293      * Please note query parameters and custom query component are mutually exclusive. This method
294      * will remove custom query if present.
295      * </p>
296      *
297      * @since 4.3
298      */
299     public URIBuilder setParameters(final List <NameValuePair> nvps) {
300         if (this.queryParams == null) {
301             this.queryParams = new ArrayList<NameValuePair>();
302         } else {
303             this.queryParams.clear();
304         }
305         this.queryParams.addAll(nvps);
306         this.encodedQuery = null;
307         this.encodedSchemeSpecificPart = null;
308         this.query = null;
309         return this;
310     }
311 
312     /**
313      * Adds URI query parameters. The parameter name / values are expected to be unescaped
314      * and may contain non ASCII characters.
315      * <p>
316      * Please note query parameters and custom query component are mutually exclusive. This method
317      * will remove custom query if present.
318      * </p>
319      *
320      * @since 4.3
321      */
322     public URIBuilder addParameters(final List <NameValuePair> nvps) {
323         if (this.queryParams == null) {
324             this.queryParams = new ArrayList<NameValuePair>();
325         }
326         this.queryParams.addAll(nvps);
327         this.encodedQuery = null;
328         this.encodedSchemeSpecificPart = null;
329         this.query = null;
330         return this;
331     }
332 
333     /**
334      * Sets URI query parameters. The parameter name / values are expected to be unescaped
335      * and may contain non ASCII characters.
336      * <p>
337      * Please note query parameters and custom query component are mutually exclusive. This method
338      * will remove custom query if present.
339      * </p>
340      *
341      * @since 4.3
342      */
343     public URIBuilder setParameters(final NameValuePair... nvps) {
344         if (this.queryParams == null) {
345             this.queryParams = new ArrayList<NameValuePair>();
346         } else {
347             this.queryParams.clear();
348         }
349         for (final NameValuePair nvp: nvps) {
350             this.queryParams.add(nvp);
351         }
352         this.encodedQuery = null;
353         this.encodedSchemeSpecificPart = null;
354         this.query = null;
355         return this;
356     }
357 
358     /**
359      * Adds parameter to URI query. The parameter name and value are expected to be unescaped
360      * and may contain non ASCII characters.
361      * <p>
362      * Please note query parameters and custom query component are mutually exclusive. This method
363      * will remove custom query if present.
364      * </p>
365      */
366     public URIBuilder addParameter(final String param, final String value) {
367         if (this.queryParams == null) {
368             this.queryParams = new ArrayList<NameValuePair>();
369         }
370         this.queryParams.add(new BasicNameValuePair(param, value));
371         this.encodedQuery = null;
372         this.encodedSchemeSpecificPart = null;
373         this.query = null;
374         return this;
375     }
376 
377     /**
378      * Sets parameter of URI query overriding existing value if set. The parameter name and value
379      * are expected to be unescaped and may contain non ASCII characters.
380      * <p>
381      * Please note query parameters and custom query component are mutually exclusive. This method
382      * will remove custom query if present.
383      * </p>
384      */
385     public URIBuilder setParameter(final String param, final String value) {
386         if (this.queryParams == null) {
387             this.queryParams = new ArrayList<NameValuePair>();
388         }
389         if (!this.queryParams.isEmpty()) {
390             for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
391                 final NameValuePair nvp = it.next();
392                 if (nvp.getName().equals(param)) {
393                     it.remove();
394                 }
395             }
396         }
397         this.queryParams.add(new BasicNameValuePair(param, value));
398         this.encodedQuery = null;
399         this.encodedSchemeSpecificPart = null;
400         this.query = null;
401         return this;
402     }
403 
404     /**
405      * Clears URI query parameters.
406      *
407      * @since 4.3
408      */
409     public URIBuilder clearParameters() {
410         this.queryParams = null;
411         this.encodedQuery = null;
412         this.encodedSchemeSpecificPart = null;
413         return this;
414     }
415 
416     /**
417      * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII
418      * characters.
419      * <p>
420      * Please note query parameters and custom query component are mutually exclusive. This method
421      * will remove query parameters if present.
422      * </p>
423      *
424      * @since 4.3
425      */
426     public URIBuilder setCustomQuery(final String query) {
427         this.query = query;
428         this.encodedQuery = null;
429         this.encodedSchemeSpecificPart = null;
430         this.queryParams = null;
431         return this;
432     }
433 
434     /**
435      * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
436      * characters.
437      */
438     public URIBuilder setFragment(final String fragment) {
439         this.fragment = fragment;
440         this.encodedFragment = null;
441         return this;
442     }
443 
444     /**
445      * @since 4.3
446      */
447     public boolean isAbsolute() {
448         return this.scheme != null;
449     }
450 
451     /**
452      * @since 4.3
453      */
454     public boolean isOpaque() {
455         return this.path == null;
456     }
457 
458     public String getScheme() {
459         return this.scheme;
460     }
461 
462     public String getUserInfo() {
463         return this.userInfo;
464     }
465 
466     public String getHost() {
467         return this.host;
468     }
469 
470     public int getPort() {
471         return this.port;
472     }
473 
474     public String getPath() {
475         return this.path;
476     }
477 
478     public List<NameValuePair> getQueryParams() {
479         if (this.queryParams != null) {
480             return new ArrayList<NameValuePair>(this.queryParams);
481         } else {
482             return new ArrayList<NameValuePair>();
483         }
484     }
485 
486     public String getFragment() {
487         return this.fragment;
488     }
489 
490     @Override
491     public String toString() {
492         return buildString();
493     }
494 
495     private static String normalizePath(final String path) {
496         String s = path;
497         if (s == null) {
498             return null;
499         }
500         int n = 0;
501         for (; n < s.length(); n++) {
502             if (s.charAt(n) != '/') {
503                 break;
504             }
505         }
506         if (n > 1) {
507             s = s.substring(n - 1);
508         }
509         return s;
510     }
511 
512 }