Skip to content

Commit 01d6fb1

Browse files
Jake SwensonJakeWharton
authored andcommitted
Do not encode '/' in path replacement when encoded=true.
1 parent 3c195a0 commit 01d6fb1

File tree

2 files changed

+52
-11
lines changed

2 files changed

+52
-11
lines changed

retrofit/src/main/java/retrofit2/RequestBuilder.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
final class RequestBuilder {
3030
private static final char[] HEX_DIGITS =
3131
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
32-
private static final String PATH_SEGMENT_ENCODE_SET = " \"<>^`{}|/\\?#";
32+
private static final String PATH_SEGMENT_ALWAYS_ENCODE_SET = " \"<>^`{}|\\?#";
3333

3434
private final String method;
3535

@@ -85,20 +85,20 @@ void addPathParam(String name, String value, boolean encoded) {
8585
// The relative URL is cleared when the first query parameter is set.
8686
throw new AssertionError();
8787
}
88-
relativeUrl = relativeUrl.replace("{" + name + "}", canonicalize(value, encoded));
88+
relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));
8989
}
9090

91-
private static String canonicalize(String input, boolean alreadyEncoded) {
91+
private static String canonicalizeForPath(String input, boolean alreadyEncoded) {
9292
int codePoint;
9393
for (int i = 0, limit = input.length(); i < limit; i += Character.charCount(codePoint)) {
9494
codePoint = input.codePointAt(i);
9595
if (codePoint < 0x20 || codePoint >= 0x7f
96-
|| PATH_SEGMENT_ENCODE_SET.indexOf(codePoint) != -1
97-
|| (codePoint == '%' && !alreadyEncoded)) {
96+
|| PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
97+
|| (!alreadyEncoded && (codePoint == '/' || codePoint == '%'))) {
9898
// Slow path: the character at i requires encoding!
9999
Buffer out = new Buffer();
100100
out.writeUtf8(input, 0, i);
101-
canonicalize(out, input, i, limit, alreadyEncoded);
101+
canonicalizeForPath(out, input, i, limit, alreadyEncoded);
102102
return out.readUtf8();
103103
}
104104
}
@@ -107,7 +107,7 @@ private static String canonicalize(String input, boolean alreadyEncoded) {
107107
return input;
108108
}
109109

110-
private static void canonicalize(Buffer out, String input, int pos, int limit,
110+
private static void canonicalizeForPath(Buffer out, String input, int pos, int limit,
111111
boolean alreadyEncoded) {
112112
Buffer utf8Buffer = null; // Lazily allocated.
113113
int codePoint;
@@ -116,10 +116,9 @@ private static void canonicalize(Buffer out, String input, int pos, int limit,
116116
if (alreadyEncoded
117117
&& (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) {
118118
// Skip this character.
119-
} else if (codePoint < 0x20
120-
|| codePoint >= 0x7f
121-
|| PATH_SEGMENT_ENCODE_SET.indexOf(codePoint) != -1
122-
|| (codePoint == '%' && !alreadyEncoded)) {
119+
} else if (codePoint < 0x20 || codePoint >= 0x7f
120+
|| PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1
121+
|| (!alreadyEncoded && (codePoint == '/' || codePoint == '%'))) {
123122
// Percent encode this character.
124123
if (utf8Buffer == null) {
125124
utf8Buffer = new Buffer();

retrofit/src/test/java/retrofit2/RequestBuilderTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,48 @@ Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) {
670670
assertThat(request.body()).isNull();
671671
}
672672

673+
@Test public void getWithEncodedPathSegments() {
674+
class Example {
675+
@GET("/foo/bar/{ping}/") //
676+
Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) {
677+
return null;
678+
}
679+
}
680+
Request request = buildRequest(Example.class, "baz/pong/more");
681+
assertThat(request.method()).isEqualTo("GET");
682+
assertThat(request.headers().size()).isZero();
683+
assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz/pong/more/");
684+
assertThat(request.body()).isNull();
685+
}
686+
687+
@Test public void getWithUnencodedPathSegmentsPreventsRequestSplitting() {
688+
class Example {
689+
@GET("/foo/bar/{ping}/") //
690+
Call<ResponseBody> method(@Path(value = "ping", encoded = false) String ping) {
691+
return null;
692+
}
693+
}
694+
Request request = buildRequest(Example.class, "baz/\r\nheader: blue");
695+
assertThat(request.method()).isEqualTo("GET");
696+
assertThat(request.headers().size()).isZero();
697+
assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz%2F%0D%0Aheader:%20blue/");
698+
assertThat(request.body()).isNull();
699+
}
700+
701+
@Test public void getWithEncodedPathStillPreventsRequestSplitting() {
702+
class Example {
703+
@GET("/foo/bar/{ping}/") //
704+
Call<ResponseBody> method(@Path(value = "ping", encoded = true) String ping) {
705+
return null;
706+
}
707+
}
708+
Request request = buildRequest(Example.class, "baz/\r\npong");
709+
assertThat(request.method()).isEqualTo("GET");
710+
assertThat(request.headers().size()).isZero();
711+
assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/baz/pong/");
712+
assertThat(request.body()).isNull();
713+
}
714+
673715
@Test public void pathParamRequired() {
674716
class Example {
675717
@GET("/foo/bar/{ping}/") //

0 commit comments

Comments
 (0)