diff --git a/google-http-client/src/main/java/com/google/api/client/http/GenericUrl.java b/google-http-client/src/main/java/com/google/api/client/http/GenericUrl.java index e18810c89..68c5ea6c3 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/GenericUrl.java +++ b/google-http-client/src/main/java/com/google/api/client/http/GenericUrl.java @@ -80,6 +80,13 @@ public class GenericUrl extends GenericData { /** Fragment component or {@code null} for none. */ private String fragment; + /** + * If true, the URL string originally given is used as is (without encoding, decoding and + * escaping) whenever referenced; otherwise, part of the URL string may be encoded or decoded as + * deemed appropriate or necessary. + */ + private boolean verbatim; + public GenericUrl() {} /** @@ -99,9 +106,26 @@ public GenericUrl() {} * @throws IllegalArgumentException if URL has a syntax error */ public GenericUrl(String encodedUrl) { - this(parseURL(encodedUrl)); + this(encodedUrl, false); + } + + /** + * Constructs from an encoded URL. + * + *
Any known query parameters with pre-defined fields as data keys are parsed based on + * their data type. Any unrecognized query parameter are always parsed as a string. + * + *
Any {@link MalformedURLException} is wrapped in an {@link IllegalArgumentException}.
+ *
+ * @param encodedUrl encoded URL, including any existing query parameters that should be parsed
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
+ * @throws IllegalArgumentException if URL has a syntax error
+ */
+ public GenericUrl(String encodedUrl, boolean verbatim) {
+ this(parseURL(encodedUrl), verbatim);
}
+
/**
* Constructs from a URI.
*
@@ -109,6 +133,16 @@ public GenericUrl(String encodedUrl) {
* @since 1.14
*/
public GenericUrl(URI uri) {
+ this(uri, false);
+ }
+
+ /**
+ * Constructs from a URI.
+ *
+ * @param uri URI
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
+ */
+ public GenericUrl(URI uri, boolean verbatim) {
this(
uri.getScheme(),
uri.getHost(),
@@ -116,7 +150,8 @@ public GenericUrl(URI uri) {
uri.getRawPath(),
uri.getRawFragment(),
uri.getRawQuery(),
- uri.getRawUserInfo());
+ uri.getRawUserInfo(),
+ verbatim);
}
/**
@@ -126,6 +161,17 @@ public GenericUrl(URI uri) {
* @since 1.14
*/
public GenericUrl(URL url) {
+ this(url, false);
+ }
+
+ /**
+ * Constructs from a URL.
+ *
+ * @param url URL
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
+ * @since 1.14
+ */
+ public GenericUrl(URL url, boolean verbatim) {
this(
url.getProtocol(),
url.getHost(),
@@ -133,7 +179,8 @@ public GenericUrl(URL url) {
url.getPath(),
url.getRef(),
url.getQuery(),
- url.getUserInfo());
+ url.getUserInfo(),
+ verbatim);
}
private GenericUrl(
@@ -143,16 +190,26 @@ private GenericUrl(
String path,
String fragment,
String query,
- String userInfo) {
+ String userInfo,
+ boolean verbatim) {
this.scheme = scheme.toLowerCase(Locale.US);
this.host = host;
this.port = port;
- this.pathParts = toPathParts(path);
- this.fragment = fragment != null ? CharEscapers.decodeUri(fragment) : null;
- if (query != null) {
- UrlEncodedParser.parse(query, this);
- }
- this.userInfo = userInfo != null ? CharEscapers.decodeUri(userInfo) : null;
+ this.pathParts = toPathParts(path, verbatim);
+ this.verbatim = verbatim;
+ if (verbatim) {
+ this.fragment = fragment;
+ if (query != null) {
+ UrlEncodedParser.parse(query, this, false);
+ }
+ this.userInfo = userInfo;
+ } else {
+ this.fragment = fragment != null ? CharEscapers.decodeUri(fragment) : null;
+ if (query != null) {
+ UrlEncodedParser.parse(query, this);
+ }
+ this.userInfo = userInfo != null ? CharEscapers.decodeUri(userInfo) : null;
+ }
}
@Override
@@ -333,7 +390,7 @@ public final String buildAuthority() {
buf.append(Preconditions.checkNotNull(scheme));
buf.append("://");
if (userInfo != null) {
- buf.append(CharEscapers.escapeUriUserInfo(userInfo)).append('@');
+ buf.append(verbatim ? userInfo : CharEscapers.escapeUriUserInfo(userInfo)).append('@');
}
buf.append(Preconditions.checkNotNull(host));
int port = this.port;
@@ -357,12 +414,12 @@ public final String buildRelativeUrl() {
if (pathParts != null) {
appendRawPathFromParts(buf);
}
- addQueryParams(entrySet(), buf);
+ addQueryParams(entrySet(), buf, verbatim);
// URL fragment
String fragment = this.fragment;
if (fragment != null) {
- buf.append('#').append(URI_FRAGMENT_ESCAPER.escape(fragment));
+ buf.append('#').append(verbatim ? fragment : URI_FRAGMENT_ESCAPER.escape(fragment));
}
return buf.toString();
}
@@ -467,7 +524,7 @@ public String getRawPath() {
* @param encodedPath raw encoded path or {@code null} to set {@link #pathParts} to {@code null}
*/
public void setRawPath(String encodedPath) {
- pathParts = toPathParts(encodedPath);
+ pathParts = toPathParts(encodedPath, verbatim);
}
/**
@@ -482,7 +539,7 @@ public void setRawPath(String encodedPath) {
*/
public void appendRawPath(String encodedPath) {
if (encodedPath != null && encodedPath.length() != 0) {
- List The default value is {@code false}.
+ */
+ public HttpRequest setUseRawRedirectUrls(boolean useRawRedirectUrls) {
+ this.useRawRedirectUrls = useRawRedirectUrls;
+ return this;
+ }
+
/**
* Returns whether to throw an exception at the end of {@link #execute()} on an HTTP error code
* (non-2XX) after all retries and response handlers have been exhausted.
@@ -1159,7 +1179,7 @@ public boolean handleRedirect(int statusCode, HttpHeaders responseHeaders) {
&& HttpStatusCodes.isRedirect(statusCode)
&& redirectLocation != null) {
// resolve the redirect location relative to the current location
- setUrl(new GenericUrl(url.toURL(redirectLocation)));
+ setUrl(new GenericUrl(url.toURL(redirectLocation), useRawRedirectUrls));
// on 303 change method to GET
if (statusCode == HttpStatusCodes.STATUS_CODE_SEE_OTHER) {
setRequestMethod(HttpMethods.GET);
diff --git a/google-http-client/src/main/java/com/google/api/client/http/UriTemplate.java b/google-http-client/src/main/java/com/google/api/client/http/UriTemplate.java
index f3e7d63d1..fcf25fa49 100644
--- a/google-http-client/src/main/java/com/google/api/client/http/UriTemplate.java
+++ b/google-http-client/src/main/java/com/google/api/client/http/UriTemplate.java
@@ -318,7 +318,7 @@ public static String expand(
}
if (addUnusedParamsAsQueryParams) {
// Add the parameters remaining in the variableMap as query parameters.
- GenericUrl.addQueryParams(variableMap.entrySet(), pathBuf);
+ GenericUrl.addQueryParams(variableMap.entrySet(), pathBuf, false);
}
return pathBuf.toString();
}
diff --git a/google-http-client/src/main/java/com/google/api/client/http/UrlEncodedParser.java b/google-http-client/src/main/java/com/google/api/client/http/UrlEncodedParser.java
index cd5e8a63a..fb5ec5375 100644
--- a/google-http-client/src/main/java/com/google/api/client/http/UrlEncodedParser.java
+++ b/google-http-client/src/main/java/com/google/api/client/http/UrlEncodedParser.java
@@ -73,7 +73,6 @@ public class UrlEncodedParser implements ObjectParser {
*/
public static final String MEDIA_TYPE =
new HttpMediaType(UrlEncodedParser.CONTENT_TYPE).setCharsetParameter(Charsets.UTF_8).build();
-
/**
* Parses the given URL-encoded content into the given data object of data key name/value pairs
* using {@link #parse(Reader, Object)}.
@@ -82,17 +81,28 @@ public class UrlEncodedParser implements ObjectParser {
* @param data data key name/value pairs
*/
public static void parse(String content, Object data) {
+ parse(content, data, true);
+ }
+
+ /**
+ * Parses the given URL-encoded content into the given data object of data key name/value pairs
+ * using {@link #parse(Reader, Object)}.
+ *
+ * @param content URL-encoded content or {@code null} to ignore content
+ * @param data data key name/value pairs
+ * @param decodeEnabled flag that specifies whether decoding should be enabled.
+ */
+ public static void parse(String content, Object data, boolean decodeEnabled) {
if (content == null) {
return;
}
try {
- parse(new StringReader(content), data);
+ parse(new StringReader(content), data, decodeEnabled);
} catch (IOException exception) {
// I/O exception not expected on a string
throw Throwables.propagate(exception);
}
}
-
/**
* Parses the given URL-encoded content into the given data object of data key name/value pairs,
* including support for repeating data key names.
@@ -113,7 +123,32 @@ public static void parse(String content, Object data) {
* @param data data key name/value pairs
* @since 1.14
*/
- public static void parse(Reader reader, Object data) throws IOException {
+ public static void parse(Reader reader, Object data) throws IOException {
+ parse(reader, data, true);
+ }
+
+ /**
+ * Parses the given URL-encoded content into the given data object of data key name/value pairs,
+ * including support for repeating data key names.
+ *
+ * Declared fields of a "primitive" type (as defined by {@link Data#isPrimitive(Type)} are
+ * parsed using {@link Data#parsePrimitiveValue(Type, String)} where the {@link Class} parameter
+ * is the declared field class. Declared fields of type {@link Collection} are used to support
+ * repeating data key names, so each member of the collection is an additional data key value.
+ * They are parsed the same as "primitive" fields, except that the generic type parameter of the
+ * collection is used as the {@link Class} parameter.
+ *
+ * If there is no declared field for an input parameter name, it is ignored unless the
+ * input {@code data} parameter is a {@link Map}. If it is a map, the parameter value is
+ * stored either as a string, or as a {@link ArrayList}<String> in the case of repeated
+ * parameters.
+ *
+ * @param reader URL-encoded reader
+ * @param data data key name/value pairs
+ * @param decodeEnabled flag that specifies whether data should be decoded.
+ * @since 1.14
+ */
+ public static void parse(Reader reader, Object data, boolean decodeEnabled) throws IOException {
Class> clazz = data.getClass();
ClassInfo classInfo = ClassInfo.of(clazz);
List