View Javadoc

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 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   * {@link URI} builder for HTTP requests.
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 fragment;
62      private String encodedFragment;
63  
64      /**
65       * Constructs an empty instance.
66       */
67      public URIBuilder() {
68          super();
69          this.port = -1;
70      }
71  
72      /**
73       * Construct an instance from the string which must be a valid URI.
74       * 
75       * @param string a valid URI in string form
76       * @throws URISyntaxException if the input is not a valid URI
77       */
78      public URIBuilder(final String string) throws URISyntaxException {
79          super();
80          digestURI(new URI(string));
81      }
82  
83      /**
84       * Construct an instance from the provided URI.
85       * @param uri
86       */
87      public URIBuilder(final URI uri) {
88          super();
89          digestURI(uri);
90      }
91  
92      private List <NameValuePair> parseQuery(final String query, final Charset charset) {
93          if (query != null && query.length() > 0) {
94              return URLEncodedUtils.parse(query, charset);
95          }
96          return null;
97      }
98  
99      /**
100      * Builds a {@link URI} instance.
101      */
102     public URI build() throws URISyntaxException {
103         return new URI(buildString());
104     }
105     
106     private String buildString() {
107         StringBuilder sb = new StringBuilder();
108         if (this.scheme != null) {
109             sb.append(this.scheme).append(':');
110         }
111         if (this.encodedSchemeSpecificPart != null) {
112             sb.append(this.encodedSchemeSpecificPart);
113         } else {
114             if (this.encodedAuthority != null) {
115                 sb.append("//").append(this.encodedAuthority);
116             } else if (this.host != null) {
117                 sb.append("//");
118                 if (this.encodedUserInfo != null) {
119                     sb.append(this.encodedUserInfo).append("@");
120                 } else if (this.userInfo != null) {
121                     sb.append(encodeUserInfo(this.userInfo)).append("@");
122                 }
123                 if (InetAddressUtils.isIPv6Address(this.host)) {
124                     sb.append("[").append(this.host).append("]");
125                 } else {
126                     sb.append(this.host);
127                 }
128                 if (this.port >= 0) {
129                     sb.append(":").append(this.port);
130                 }
131             }
132             if (this.encodedPath != null) {
133                 sb.append(normalizePath(this.encodedPath));
134             } else if (this.path != null) {
135                 sb.append(encodePath(normalizePath(this.path)));
136             }
137             if (this.encodedQuery != null) {
138                 sb.append("?").append(this.encodedQuery);
139             } else if (this.queryParams != null) {
140                 sb.append("?").append(encodeQuery(this.queryParams));
141             }
142         }
143         if (this.encodedFragment != null) {
144             sb.append("#").append(this.encodedFragment);
145         } else if (this.fragment != null) {
146             sb.append("#").append(encodeFragment(this.fragment));
147         }
148         return sb.toString();
149     }
150 
151     private void digestURI(final URI uri) {
152         this.scheme = uri.getScheme();
153         this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
154         this.encodedAuthority = uri.getRawAuthority();
155         this.host = uri.getHost();
156         this.port = uri.getPort();
157         this.encodedUserInfo = uri.getRawUserInfo();
158         this.userInfo = uri.getUserInfo();
159         this.encodedPath = uri.getRawPath();
160         this.path = uri.getPath();
161         this.encodedQuery = uri.getRawQuery();
162         this.queryParams = parseQuery(uri.getRawQuery(), Consts.UTF_8);
163         this.encodedFragment = uri.getRawFragment();
164         this.fragment = uri.getFragment();
165     }
166 
167     private String encodeUserInfo(final String userInfo) {
168         return URLEncodedUtils.encUserInfo(userInfo, Consts.UTF_8);
169     }
170 
171     private String encodePath(final String path) {
172         return URLEncodedUtils.encPath(path, Consts.UTF_8);
173     }
174 
175     private String encodeQuery(final List<NameValuePair> params) {
176         return URLEncodedUtils.format(params, Consts.UTF_8);
177     }
178 
179     private String encodeFragment(final String fragment) {
180         return URLEncodedUtils.encFragment(fragment, Consts.UTF_8);
181     }
182 
183     /**
184      * Sets URI scheme.
185      */
186     public URIBuilder setScheme(final String scheme) {
187         this.scheme = scheme;
188         return this;
189     }
190 
191     /**
192      * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
193      * characters.
194      */
195     public URIBuilder setUserInfo(final String userInfo) {
196         this.userInfo = userInfo;
197         this.encodedSchemeSpecificPart = null;
198         this.encodedAuthority = null;
199         this.encodedUserInfo = null;
200         return this;
201     }
202 
203     /**
204      * Sets URI user info as a combination of username and password. These values are expected to
205      * be unescaped and may contain non ASCII characters.
206      */
207     public URIBuilder setUserInfo(final String username, final String password) {
208         return setUserInfo(username + ':' + password);
209     }
210 
211     /**
212      * Sets URI host.
213      */
214     public URIBuilder setHost(final String host) {
215         this.host = host;
216         this.encodedSchemeSpecificPart = null;
217         this.encodedAuthority = null;
218         return this;
219     }
220 
221     /**
222      * Sets URI port.
223      */
224     public URIBuilder setPort(final int port) {
225         this.port = port < 0 ? -1 : port;
226         this.encodedSchemeSpecificPart = null;
227         this.encodedAuthority = null;
228         return this;
229     }
230 
231     /**
232      * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
233      */
234     public URIBuilder setPath(final String path) {
235         this.path = path;
236         this.encodedSchemeSpecificPart = null;
237         this.encodedPath = null;
238         return this;
239     }
240 
241     /**
242      * Removes URI query.
243      */
244     public URIBuilder removeQuery() {
245         this.queryParams = null;
246         this.encodedQuery = null;
247         this.encodedSchemeSpecificPart = null;
248         return this;
249     }
250 
251     /**
252      * Sets URI query.
253      * <p>
254      * The value is expected to be encoded form data.
255      */
256     public URIBuilder setQuery(final String query) {
257         this.queryParams = parseQuery(query, Consts.UTF_8);
258         this.encodedQuery = null;
259         this.encodedSchemeSpecificPart = null;
260         return this;
261     }
262 
263     /**
264      * Adds parameter to URI query. The parameter name and value are expected to be unescaped
265      * and may contain non ASCII characters.
266      */
267     public URIBuilder addParameter(final String param, final String value) {
268         if (this.queryParams == null) {
269             this.queryParams = new ArrayList<NameValuePair>();
270         }
271         this.queryParams.add(new BasicNameValuePair(param, value));
272         this.encodedQuery = null;
273         this.encodedSchemeSpecificPart = null;
274         return this;
275     }
276 
277     /**
278      * Sets parameter of URI query overriding existing value if set. The parameter name and value
279      * are expected to be unescaped and may contain non ASCII characters.
280      */
281     public URIBuilder setParameter(final String param, final String value) {
282         if (this.queryParams == null) {
283             this.queryParams = new ArrayList<NameValuePair>();
284         }
285         if (!this.queryParams.isEmpty()) {
286             for (Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
287                 NameValuePair nvp = it.next();
288                 if (nvp.getName().equals(param)) {
289                     it.remove();
290                 }
291             }
292         }
293         this.queryParams.add(new BasicNameValuePair(param, value));
294         this.encodedQuery = null;
295         this.encodedSchemeSpecificPart = null;
296         return this;
297     }
298 
299     /**
300      * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
301      * characters.
302      */
303     public URIBuilder setFragment(final String fragment) {
304         this.fragment = fragment;
305         this.encodedFragment = null;
306         return this;
307     }
308 
309     public String getScheme() {
310         return this.scheme;
311     }
312 
313     public String getUserInfo() {
314         return this.userInfo;
315     }
316 
317     public String getHost() {
318         return this.host;
319     }
320 
321     public int getPort() {
322         return this.port;
323     }
324 
325     public String getPath() {
326         return this.path;
327     }
328 
329     public List<NameValuePair> getQueryParams() {
330         if (this.queryParams != null) {
331             return new ArrayList<NameValuePair>(this.queryParams);
332         } else {
333             return new ArrayList<NameValuePair>();
334         }
335     }
336 
337     public String getFragment() {
338         return this.fragment;
339     }
340 
341     @Override
342     public String toString() {
343         return buildString();
344     }
345 
346     private static String normalizePath(String path) {
347         if (path == null) {
348             return null;
349         }
350         int n = 0;
351         for (; n < path.length(); n++) {
352             if (path.charAt(n) != '/') {
353                 break;
354             }
355         }
356         if (n > 1) {
357             path = path.substring(n - 1);
358         }
359         return path;
360     }
361 
362 }