Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add flag to allow UrlEncodedContent to use UriPath escaping #1100

Merged
Merged
Expand Up @@ -42,20 +42,41 @@
*
* <p>Implementation is not thread-safe.
*
* @since 1.0
* @author Yaniv Inbar
* @since 1.0
*/
public class UrlEncodedContent extends AbstractHttpContent {

/** Key name/value data. */
private Object data;

/** @param data key name/value data */
/** Use URI Path encoder flag. False by default (use legacy and deprecated escapeUri) */
private boolean uriPathEncodingFlag;

/**
* Initialize the UrlEncodedContent with the legacy and deprecated escapeUri encoder
* @param data key name/value data
* */
public UrlEncodedContent(Object data) {
super(UrlEncodedParser.MEDIA_TYPE);
setData(data);
this.uriPathEncodingFlag = false;
}

/**
* Initialize the UrlEncodedContent with our without the legacy and deprecated escapeUri encoder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our --> or?

* @param data key name/value data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the key is a name and a value? That's surprising.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy paste of the legacy comment (see line 56 of the file.) I kept the legacy signature and behavior, and I added mine with additional parameter. This one is unchanged (and I guess its description also!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you want me to improve both or if keeping the existing description is ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I look at this the weirder it is. Why is this an object instead of a Map?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree; it's strange and I don't know why to use an Object instead of a Map! However to ensure the compatibility with other app that depends on this library, I kept the existing signature.

* @param useUriPathEncoding Escapes the string value so it can be safely included in URI path segments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Escapes --> escapes

* For details on escaping URIs, see <a href="http://tools.ietf.org/html/rfc3986#section-2.4">RFC 3986 -
* section 2.4</a>
*/
public UrlEncodedContent(Object data, Boolean useUriPathEncoding) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boolean --> boolean
per Effective Java

super(UrlEncodedParser.MEDIA_TYPE);
setData(data);
this.uriPathEncodingFlag = useUriPathEncoding;
}

@Override
public void writeTo(OutputStream out) throws IOException {
Writer writer = new BufferedWriter(new OutputStreamWriter(out, getCharset()));
boolean first = true;
Expand All @@ -66,10 +87,10 @@ public void writeTo(OutputStream out) throws IOException {
Class<? extends Object> valueClass = value.getClass();
if (value instanceof Iterable<?> || valueClass.isArray()) {
for (Object repeatedValue : Types.iterableOf(value)) {
first = appendParam(first, writer, name, repeatedValue);
first = appendParam(first, writer, name, repeatedValue, this.uriPathEncodingFlag);
}
} else {
first = appendParam(first, writer, name, value);
first = appendParam(first, writer, name, value, this.uriPathEncodingFlag);
}
}
}
Expand Down Expand Up @@ -125,7 +146,8 @@ public static UrlEncodedContent getContent(HttpRequest request) {
return result;
}

private static boolean appendParam(boolean first, Writer writer, String name, Object value)
private static boolean appendParam(
boolean first, Writer writer, String name, Object value, boolean uriPathEncodingFlag)
throws IOException {
// ignore nulls
if (value == null || Data.isNull(value)) {
Expand All @@ -139,8 +161,13 @@ private static boolean appendParam(boolean first, Writer writer, String name, Ob
}
writer.write(name);
String stringValue =
CharEscapers.escapeUri(
value instanceof Enum<?> ? FieldInfo.of((Enum<?>) value).getName() : value.toString());
value instanceof Enum<?> ? FieldInfo.of((Enum<?>) value).getName() : value.toString();

if (uriPathEncodingFlag) {
stringValue = CharEscapers.escapeUriPath(stringValue);
} else {
stringValue = CharEscapers.escapeUri(stringValue);
}
if (stringValue.length() != 0) {
writer.write("=");
writer.write(stringValue);
Expand Down
Expand Up @@ -33,19 +33,32 @@
public class UrlEncodedContentTest extends TestCase {

public void testWriteTo() throws IOException {
subtestWriteTo("a=x", ArrayMap.of("a", "x"));
subtestWriteTo("noval", ArrayMap.of("noval", ""));
subtestWriteTo("multi=a&multi=b&multi=c", ArrayMap.of("multi", Arrays.asList("a", "b", "c")));
subtestWriteTo("multi=a&multi=b&multi=c", ArrayMap.of("multi", new String[] {"a", "b", "c"}));
subtestWriteTo("a=x", ArrayMap.of("a", "x"), false);
subtestWriteTo("noval", ArrayMap.of("noval", ""), false);
subtestWriteTo(
"multi=a&multi=b&multi=c", ArrayMap.of("multi", Arrays.asList("a", "b", "c")), false);
subtestWriteTo(
"multi=a&multi=b&multi=c", ArrayMap.of("multi", new String[] {"a", "b", "c"}), false);
// https://github.com/googleapis/google-http-java-client/issues/202
final Map<String, String> params = new LinkedHashMap<String, String>();
params.put("username", "un");
params.put("password", "password123;{}");
subtestWriteTo("username=un&password=password123%3B%7B%7D", params);
subtestWriteTo("username=un&password=password123%3B%7B%7D", params, false);
subtestWriteTo("additionkey=add%2Btion", ArrayMap.of("additionkey", "add+tion"), false);

subtestWriteTo("a=x", ArrayMap.of("a", "x"), true);
subtestWriteTo("noval", ArrayMap.of("noval", ""), true);
subtestWriteTo(
"multi=a&multi=b&multi=c", ArrayMap.of("multi", Arrays.asList("a", "b", "c")), true);
subtestWriteTo(
"multi=a&multi=b&multi=c", ArrayMap.of("multi", new String[] {"a", "b", "c"}), true);
subtestWriteTo("username=un&password=password123;%7B%7D", params, true);
subtestWriteTo("additionkey=add+tion", ArrayMap.of("additionkey", "add+tion"), true);
}

private void subtestWriteTo(String expected, Object data) throws IOException {
UrlEncodedContent content = new UrlEncodedContent(data);
private void subtestWriteTo(String expected, Object data, boolean useEscapeUriPathEncoding)
throws IOException {
UrlEncodedContent content = new UrlEncodedContent(data, useEscapeUriPathEncoding);
ByteArrayOutputStream out = new ByteArrayOutputStream();
content.writeTo(out);
assertEquals(expected, out.toString());
Expand Down