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