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.Arrays;
34  import java.util.Collections;
35  import java.util.Iterator;
36  import java.util.List;
37  import org.apache.http.Consts;
38  import org.apache.http.NameValuePair;
39  import org.apache.http.conn.util.InetAddressUtils;
40  import org.apache.http.message.BasicNameValuePair;
41  import org.apache.http.util.TextUtils;
42  
43  /**
44   * Builder for {@link URI} instances.
45   *
46   * @since 4.2
47   */
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 encodedPath;
58      private List<String> pathSegments;
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     private List <String> parsePath(final String path, final Charset charset) {
117         if (path != null && !path.isEmpty()) {
118             return URLEncodedUtils.parsePathSegments(path, charset);
119         }
120         return null;
121     }
122 
123     /**
124      * Builds a {@link URI} instance.
125      */
126     public URI build() throws URISyntaxException {
127         return new URI(buildString());
128     }
129 
130     private String buildString() {
131         final StringBuilder sb = new StringBuilder();
132         if (this.scheme != null) {
133             sb.append(this.scheme).append(':');
134         }
135         if (this.encodedSchemeSpecificPart != null) {
136             sb.append(this.encodedSchemeSpecificPart);
137         } else {
138             if (this.encodedAuthority != null) {
139                 sb.append("//").append(this.encodedAuthority);
140             } else if (this.host != null) {
141                 sb.append("//");
142                 if (this.encodedUserInfo != null) {
143                     sb.append(this.encodedUserInfo).append("@");
144                 } else if (this.userInfo != null) {
145                     sb.append(encodeUserInfo(this.userInfo)).append("@");
146                 }
147                 if (InetAddressUtils.isIPv6Address(this.host)) {
148                     sb.append("[").append(this.host).append("]");
149                 } else {
150                     sb.append(this.host);
151                 }
152                 if (this.port >= 0) {
153                     sb.append(":").append(this.port);
154                 }
155             }
156             if (this.encodedPath != null) {
157                 sb.append(normalizePath(this.encodedPath, sb.length() == 0));
158             } else if (this.pathSegments != null) {
159                 sb.append(encodePath(this.pathSegments));
160             }
161             if (this.encodedQuery != null) {
162                 sb.append("?").append(this.encodedQuery);
163             } else if (this.queryParams != null && !this.queryParams.isEmpty()) {
164                 sb.append("?").append(encodeUrlForm(this.queryParams));
165             } else if (this.query != null) {
166                 sb.append("?").append(encodeUric(this.query));
167             }
168         }
169         if (this.encodedFragment != null) {
170             sb.append("#").append(this.encodedFragment);
171         } else if (this.fragment != null) {
172             sb.append("#").append(encodeUric(this.fragment));
173         }
174         return sb.toString();
175     }
176 
177     private static String normalizePath(final String path, final boolean relative) {
178         String s = path;
179         if (TextUtils.isBlank(s)) {
180             return "";
181         }
182         if (!relative && !s.startsWith("/")) {
183             s = "/" + s;
184         }
185         return s;
186     }
187 
188     private void digestURI(final URI uri) {
189         this.scheme = uri.getScheme();
190         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
191         this.encodedAuthority = uri.getRawAuthority();
192         this.host = uri.getHost();
193         this.port = uri.getPort();
194         this.encodedUserInfo = uri.getRawUserInfo();
195         this.userInfo = uri.getUserInfo();
196         this.encodedPath = uri.getRawPath();
197         this.pathSegments = parsePath(uri.getRawPath(), this.charset != null ? this.charset : Consts.UTF_8);
198         this.encodedQuery = uri.getRawQuery();
199         this.queryParams = parseQuery(uri.getRawQuery(), this.charset != null ? this.charset : Consts.UTF_8);
200         this.encodedFragment = uri.getRawFragment();
201         this.fragment = uri.getFragment();
202     }
203 
204     private String encodeUserInfo(final String userInfo) {
205         return URLEncodedUtils.encUserInfo(userInfo, this.charset != null ? this.charset : Consts.UTF_8);
206     }
207 
208     private String encodePath(final List<String> pathSegments) {
209         return URLEncodedUtils.formatSegments(pathSegments, this.charset != null ? this.charset : Consts.UTF_8);
210     }
211 
212     private String encodeUrlForm(final List<NameValuePair> params) {
213         return URLEncodedUtils.format(params, this.charset != null ? this.charset : Consts.UTF_8);
214     }
215 
216     private String encodeUric(final String fragment) {
217         return URLEncodedUtils.encUric(fragment, this.charset != null ? this.charset : Consts.UTF_8);
218     }
219 
220     /**
221      * Sets URI scheme.
222      */
223     public URIBuilder setScheme(final String scheme) {
224         this.scheme = scheme;
225         return this;
226     }
227 
228     /**
229      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
230      * characters.
231      */
232     public URIBuilder setUserInfo(final String userInfo) {
233         this.userInfo = userInfo;
234         this.encodedSchemeSpecificPart = null;
235         this.encodedAuthority = null;
236         this.encodedUserInfo = null;
237         return this;
238     }
239 
240     /**
241      * Sets URI user info as a combination of username and password. These values are expected to
242      * be unescaped and may contain non ASCII characters.
243      */
244     public URIBuilder setUserInfo(final String username, final String password) {
245         return setUserInfo(username + ':' + password);
246     }
247 
248     /**
249      * Sets URI host.
250      */
251     public URIBuilder setHost(final String host) {
252         this.host = host;
253         this.encodedSchemeSpecificPart = null;
254         this.encodedAuthority = null;
255         return this;
256     }
257 
258     /**
259      * Sets URI port.
260      */
261     public URIBuilder setPort(final int port) {
262         this.port = port < 0 ? -1 : port;
263         this.encodedSchemeSpecificPart = null;
264         this.encodedAuthority = null;
265         return this;
266     }
267 
268     /**
269      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
270      *
271      * @return this.
272      */
273     public URIBuilder setPath(final String path) {
274         return setPathSegments(path != null ? URLEncodedUtils.splitPathSegments(path) : null);
275     }
276 
277     /**
278      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
279      *
280      * @return this.
281      *
282      * @since 4.5.8
283      */
284     public URIBuilder setPathSegments(final String... pathSegments) {
285         this.pathSegments = pathSegments.length > 0 ? Arrays.asList(pathSegments) : null;
286         this.encodedSchemeSpecificPart = null;
287         this.encodedPath = null;
288         return this;
289     }
290 
291     /**
292      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
293      *
294      * @return this.
295      *
296      * @since 4.5.8
297      */
298     public URIBuilder setPathSegments(final List<String> pathSegments) {
299         this.pathSegments = pathSegments != null && pathSegments.size() > 0 ? new ArrayList<String>(pathSegments) : null;
300         this.encodedSchemeSpecificPart = null;
301         this.encodedPath = null;
302         return this;
303     }
304 
305     /**
306      * Removes URI query.
307      */
308     public URIBuilder removeQuery() {
309         this.queryParams = null;
310         this.query = null;
311         this.encodedQuery = null;
312         this.encodedSchemeSpecificPart = null;
313         return this;
314     }
315 
316     /**
317      * Sets URI query.
318      * <p>
319      * The value is expected to be encoded form data.
320      *
321      * @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)}
322      *
323      * @see URLEncodedUtils#parse
324      */
325     @Deprecated
326     public URIBuilder setQuery(final String query) {
327         this.queryParams = parseQuery(query, this.charset != null ? this.charset : Consts.UTF_8);
328         this.query = null;
329         this.encodedQuery = null;
330         this.encodedSchemeSpecificPart = null;
331         return this;
332     }
333 
334     /**
335      * Sets URI query parameters. The parameter name / values are expected to be unescaped
336      * and may contain non ASCII characters.
337      * <p>
338      * Please note query parameters and custom query component are mutually exclusive. This method
339      * will remove custom query if present.
340      * </p>
341      *
342      * @since 4.3
343      */
344     public URIBuilder setParameters(final List <NameValuePair> nvps) {
345         if (this.queryParams == null) {
346             this.queryParams = new ArrayList<NameValuePair>();
347         } else {
348             this.queryParams.clear();
349         }
350         this.queryParams.addAll(nvps);
351         this.encodedQuery = null;
352         this.encodedSchemeSpecificPart = null;
353         this.query = null;
354         return this;
355     }
356 
357     /**
358      * Adds URI query parameters. The parameter name / values are expected to be unescaped
359      * and may contain non ASCII characters.
360      * <p>
361      * Please note query parameters and custom query component are mutually exclusive. This method
362      * will remove custom query if present.
363      * </p>
364      *
365      * @since 4.3
366      */
367     public URIBuilder addParameters(final List <NameValuePair> nvps) {
368         if (this.queryParams == null) {
369             this.queryParams = new ArrayList<NameValuePair>();
370         }
371         this.queryParams.addAll(nvps);
372         this.encodedQuery = null;
373         this.encodedSchemeSpecificPart = null;
374         this.query = null;
375         return this;
376     }
377 
378     /**
379      * Sets URI query parameters. The parameter name / values are expected to be unescaped
380      * and may contain non ASCII characters.
381      * <p>
382      * Please note query parameters and custom query component are mutually exclusive. This method
383      * will remove custom query if present.
384      * </p>
385      *
386      * @since 4.3
387      */
388     public URIBuilder setParameters(final NameValuePair... nvps) {
389         if (this.queryParams == null) {
390             this.queryParams = new ArrayList<NameValuePair>();
391         } else {
392             this.queryParams.clear();
393         }
394         for (final NameValuePair nvp: nvps) {
395             this.queryParams.add(nvp);
396         }
397         this.encodedQuery = null;
398         this.encodedSchemeSpecificPart = null;
399         this.query = null;
400         return this;
401     }
402 
403     /**
404      * Adds parameter to URI query. The parameter name and value are expected to be unescaped
405      * and may contain non ASCII characters.
406      * <p>
407      * Please note query parameters and custom query component are mutually exclusive. This method
408      * will remove custom query if present.
409      * </p>
410      */
411     public URIBuilder addParameter(final String param, final String value) {
412         if (this.queryParams == null) {
413             this.queryParams = new ArrayList<NameValuePair>();
414         }
415         this.queryParams.add(new BasicNameValuePair(param, value));
416         this.encodedQuery = null;
417         this.encodedSchemeSpecificPart = null;
418         this.query = null;
419         return this;
420     }
421 
422     /**
423      * Sets parameter of URI query overriding existing value if set. The parameter name and value
424      * are expected to be unescaped and may contain non ASCII characters.
425      * <p>
426      * Please note query parameters and custom query component are mutually exclusive. This method
427      * will remove custom query if present.
428      * </p>
429      */
430     public URIBuilder setParameter(final String param, final String value) {
431         if (this.queryParams == null) {
432             this.queryParams = new ArrayList<NameValuePair>();
433         }
434         if (!this.queryParams.isEmpty()) {
435             for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
436                 final NameValuePair nvp = it.next();
437                 if (nvp.getName().equals(param)) {
438                     it.remove();
439                 }
440             }
441         }
442         this.queryParams.add(new BasicNameValuePair(param, value));
443         this.encodedQuery = null;
444         this.encodedSchemeSpecificPart = null;
445         this.query = null;
446         return this;
447     }
448 
449     /**
450      * Clears URI query parameters.
451      *
452      * @since 4.3
453      */
454     public URIBuilder clearParameters() {
455         this.queryParams = null;
456         this.encodedQuery = null;
457         this.encodedSchemeSpecificPart = null;
458         return this;
459     }
460 
461     /**
462      * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII
463      * characters.
464      * <p>
465      * Please note query parameters and custom query component are mutually exclusive. This method
466      * will remove query parameters if present.
467      * </p>
468      *
469      * @since 4.3
470      */
471     public URIBuilder setCustomQuery(final String query) {
472         this.query = query;
473         this.encodedQuery = null;
474         this.encodedSchemeSpecificPart = null;
475         this.queryParams = null;
476         return this;
477     }
478 
479     /**
480      * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
481      * characters.
482      */
483     public URIBuilder setFragment(final String fragment) {
484         this.fragment = fragment;
485         this.encodedFragment = null;
486         return this;
487     }
488 
489     /**
490      * @since 4.3
491      */
492     public boolean isAbsolute() {
493         return this.scheme != null;
494     }
495 
496     /**
497      * @since 4.3
498      */
499     public boolean isOpaque() {
500         return isPathEmpty();
501     }
502 
503     public String getScheme() {
504         return this.scheme;
505     }
506 
507     public String getUserInfo() {
508         return this.userInfo;
509     }
510 
511     public String getHost() {
512         return this.host;
513     }
514 
515     public int getPort() {
516         return this.port;
517     }
518 
519     /**
520      * @since 4.5.8
521      */
522     public boolean isPathEmpty() {
523         return (this.pathSegments == null || this.pathSegments.isEmpty()) &&
524                (this.encodedPath == null || this.encodedPath.isEmpty());
525     }
526 
527     /**
528      * @since 4.5.8
529      */
530     public List<String> getPathSegments() {
531         return this.pathSegments != null ? new ArrayList<String>(this.pathSegments) : Collections.<String>emptyList();
532     }
533 
534     public String getPath() {
535         if (this.pathSegments == null) {
536             return null;
537         }
538         final StringBuilder result = new StringBuilder();
539         for (final String segment : this.pathSegments) {
540             result.append('/').append(segment);
541         }
542         return result.toString();
543     }
544 
545     /**
546      * @since 4.5.8
547      */
548     public boolean isQueryEmpty() {
549         return (this.queryParams == null || this.queryParams.isEmpty()) && this.encodedQuery == null;
550     }
551 
552     public List<NameValuePair> getQueryParams() {
553         return this.queryParams != null ? new ArrayList<NameValuePair>(this.queryParams) : Collections.<NameValuePair>emptyList();
554     }
555 
556     public String getFragment() {
557         return this.fragment;
558     }
559 
560     @Override
561     public String toString() {
562         return buildString();
563     }
564 
565 }