diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java b/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java index 421e78e1a..d861576a7 100644 --- a/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java +++ b/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java @@ -58,6 +58,7 @@ public class URIBuilder { private String encodedPath; private String encodedQuery; private List queryParams; + private String query; private String fragment; private String encodedFragment; @@ -136,14 +137,23 @@ public class URIBuilder { } if (this.encodedQuery != null) { sb.append("?").append(this.encodedQuery); - } else if (this.queryParams != null) { - sb.append("?").append(encodeQuery(this.queryParams)); + } else if (this.query != null || this.queryParams != null) { + sb.append("?"); + if (this.query != null) { + sb.append(encodeUric(this.query)); + } + if (this.queryParams != null) { + if (this.query != null) { + sb.append("&"); + } + sb.append(encodeUrlForm(this.queryParams)); + } } } if (this.encodedFragment != null) { sb.append("#").append(this.encodedFragment); } else if (this.fragment != null) { - sb.append("#").append(encodeFragment(this.fragment)); + sb.append("#").append(encodeUric(this.fragment)); } return sb.toString(); } @@ -172,12 +182,12 @@ public class URIBuilder { return URLEncodedUtils.encPath(path, Consts.UTF_8); } - private String encodeQuery(final List params) { + private String encodeUrlForm(final List params) { return URLEncodedUtils.format(params, Consts.UTF_8); } - private String encodeFragment(final String fragment) { - return URLEncodedUtils.encFragment(fragment, Consts.UTF_8); + private String encodeUric(final String fragment) { + return URLEncodedUtils.encUric(fragment, Consts.UTF_8); } /** @@ -243,6 +253,7 @@ public class URIBuilder { */ public URIBuilder removeQuery() { this.queryParams = null; + this.query = null; this.encodedQuery = null; this.encodedSchemeSpecificPart = null; return this; @@ -252,14 +263,58 @@ public class URIBuilder { * Sets URI query. *

* The value is expected to be encoded form data. + * + * @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)} + * + * @see URLEncodedUtils#parse */ + @Deprecated public URIBuilder setQuery(final String query) { this.queryParams = parseQuery(query, Consts.UTF_8); + this.query = null; this.encodedQuery = null; this.encodedSchemeSpecificPart = null; return this; } - + + /** + * Sets URI query parameters. The parameter name / values are expected to be unescaped + * and may contain non ASCII characters. + * + * @since 4.3 + */ + public URIBuilder setParameters(final List nvps) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } else { + this.queryParams.clear(); + } + this.queryParams.addAll(nvps); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + + /** + * Sets URI query parameters. The parameter name / values are expected to be unescaped + * and may contain non ASCII characters. + * + * @since 4.3 + */ + public URIBuilder setParameters(final NameValuePair... nvps) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } else { + this.queryParams.clear(); + } + for (NameValuePair nvp: nvps) { + this.queryParams.add(nvp); + } + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + /** * Adds parameter to URI query. The parameter name and value are expected to be unescaped * and may contain non ASCII characters. @@ -296,6 +351,31 @@ public class URIBuilder { return this; } + /** + * Clears URI query parameters. + * + * @since 4.3 + */ + public URIBuilder clearParameters() { + this.queryParams = null; + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + + /** + * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII + * characters. Please note, this method does NOT override query parameters if set. + * + * @since 4.3 + */ + public URIBuilder setCustomQuery(final String query) { + this.query = query; + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + /** * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII * characters. diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java b/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java index 800d0cce2..56b0905da 100644 --- a/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java +++ b/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java @@ -271,12 +271,15 @@ public class URLEncodedUtils { * These are the additional characters allowed by userinfo. */ private static final BitSet PUNCT = new BitSet(256); - /** Characters which are safe to use in userinfo, i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */ + /** Characters which are safe to use in userinfo, + * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */ private static final BitSet USERINFO = new BitSet(256); - /** Characters which are safe to use in a path, i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */ + /** Characters which are safe to use in a path, + * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */ private static final BitSet PATHSAFE = new BitSet(256); - /** Characters which are safe to use in a fragment, i.e. {@link #RESERVED} plus {@link #UNRESERVED} */ - private static final BitSet FRAGMENT = new BitSet(256); + /** Characters which are safe to use in a query or a fragment, + * i.e. {@link #RESERVED} plus {@link #UNRESERVED} */ + private static final BitSet URIC = new BitSet(256); /** * Reserved characters, i.e. {@code ;/?:@&=+$,[]} @@ -355,8 +358,8 @@ public class URLEncodedUtils { RESERVED.set('['); // added by RFC 2732 RESERVED.set(']'); // added by RFC 2732 - FRAGMENT.or(RESERVED); - FRAGMENT.or(UNRESERVED); + URIC.or(RESERVED); + URIC.or(UNRESERVED); } private static final int RADIX = 16; @@ -516,16 +519,16 @@ public class URLEncodedUtils { } /** - * Encode a String using the {@link #FRAGMENT} set of characters. + * Encode a String using the {@link #URIC} set of characters. *

- * Used by URIBuilder to encode the userinfo segment. + * Used by URIBuilder to encode the query and fragment segments. * * @param content the string to encode, does not convert space to '+' * @param charset the charset to use * @return the encoded string */ - static String encFragment(final String content, final Charset charset) { - return urlencode(content, charset, FRAGMENT, false); + static String encUric(final String content, final Charset charset) { + return urlencode(content, charset, URIC, false); } /** diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java b/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java index aa55e1dec..eb6d217fd 100644 --- a/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java +++ b/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java @@ -81,7 +81,7 @@ public class TestURIBuilder { @Test public void testOpaqueUriMutation() throws Exception { URI uri = new URI("stuff", "some-stuff", "fragment"); - URIBuilder uribuilder = new URIBuilder(uri).setQuery("param1¶m2=stuff").setFragment(null); + URIBuilder uribuilder = new URIBuilder(uri).setCustomQuery("param1¶m2=stuff").setFragment(null); Assert.assertEquals(new URI("stuff:?param1¶m2=stuff"), uribuilder.build()); } @@ -153,6 +153,18 @@ public class TestURIBuilder { Assert.assertEquals(uri1, uri2); } + @Test + public void testQueryAndParameterEncoding() throws Exception { + URI uri1 = new URI("https://somehost.com/stuff?this&that" + + "¶m1=12345¶m2=67890"); + URI uri2 = new URIBuilder("https://somehost.com/stuff") + .setCustomQuery("this&that") + .clearParameters() + .addParameter("param1","12345") + .addParameter("param2","67890").build(); + Assert.assertEquals(uri1, uri2); + } + @Test public void testPathEncoding() throws Exception { URI uri1 = new URI("https://somehost.com/some%20path%20with%20blanks/"); @@ -177,7 +189,7 @@ public class TestURIBuilder { .setHost(host) .setUserInfo(specials) .setPath(specials) - .addParameter(specials, null) // hack to bypass parsing of query data + .setCustomQuery(specials) .setFragment(specials) .build(); @@ -198,18 +210,15 @@ public class TestURIBuilder { // Check that the encoded URI generated by URI builder agrees with that generated by using URI directly final String scheme="https"; final String host="localhost"; - final String specials="/ abcd!$&*()_-+.,=:;'~<>/@[]|#^%\"{}\\`xyz"; // N.B. excludes \u00a3`\u00ac\u00a6 - final String formdatasafe = "abcd-_.*zyz"; - URI uri = new URI(scheme, specials, host, 80, specials, - formdatasafe, // TODO replace with specials when supported - specials); + final String specials="/ abcd!$&*()_-+.,=:;'~<>/@[]|#^%\"{}\\`xyz"; // N.B. excludes \u00a3\u00ac\u00a6 + URI uri = new URI(scheme, specials, host, 80, specials, specials, specials); URI bld = new URIBuilder() .setScheme(scheme) .setHost(host) .setUserInfo(specials) .setPath(specials) - .addParameter(formdatasafe, null) // TODO replace with specials when supported + .setCustomQuery(specials) .setFragment(specials) .build();