Skip to content

Commit

Permalink
Merge changes from topic 'bug_27590872'
Browse files Browse the repository at this point in the history
* changes:
  Reapply an upstream OkHttp change to Android for HttpUrl
  Reapply an upstream OkHttp change to Android for HttpUrl
  Apply upstream OkHttp HttpUrl fix
  Apply upstream OkHttp HttpUrl fix
  Apply upstream OkHttp HttpUrl fix
  • Loading branch information
nfuller authored and Gerrit Code Review committed Mar 22, 2016
2 parents 12013c1 + 3281dee commit 02220ee
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 95 deletions.
201 changes: 188 additions & 13 deletions okhttp-tests/src/test/java/com/squareup/okhttp/HttpUrlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,20 @@ public final class HttpUrlTest {
assertEquals(HttpUrl.parse("http://user@host/path"), HttpUrl.parse("http://user@host/path"));
}

/** Given multiple '@' characters, the last one is the delimiter. */
@Test public void authorityWithMultipleAtSigns() throws Exception {
assertEquals(HttpUrl.parse("http://foo%40bar@baz/path"),
HttpUrl.parse("http://foo@bar@baz/path"));
assertEquals(HttpUrl.parse("http://foo:pass1%40bar%3Apass2@baz/path"),
HttpUrl.parse("http://foo:pass1@bar:pass2@baz/path"));
HttpUrl httpUrl = HttpUrl.parse("http://foo@bar@baz/path");
assertEquals("foo@bar", httpUrl.username());
assertEquals("", httpUrl.password());
assertEquals(HttpUrl.parse("http://foo%40bar@baz/path"), httpUrl);
}

/** Given multiple ':' characters, the first one is the delimiter. */
@Test public void authorityWithMultipleColons() throws Exception {
HttpUrl httpUrl = HttpUrl.parse("http://foo:pass1@bar:pass2@baz/path");
assertEquals("foo", httpUrl.username());
assertEquals("pass1@bar:pass2", httpUrl.password());
assertEquals(HttpUrl.parse("http://foo:pass1%40bar%3Apass2@baz/path"), httpUrl);
}

@Test public void usernameAndPassword() throws Exception {
Expand Down Expand Up @@ -457,8 +466,40 @@ public final class HttpUrlTest {
new UrlComponentEncodingTester()
.override(Encoding.IDENTITY, ' ', '"', '#', '<', '>', '?', '`')
.skipForUri('%', ' ', '"', '#', '<', '>', '\\', '^', '`', '{', '|', '}')
.identityForNonAscii()
.test(Component.FRAGMENT);
// TODO(jwilson): don't percent-encode non-ASCII characters. (But do encode control characters!)
}

@Test public void fragmentNonAscii() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#Σ");
assertEquals("http://host/#Σ", url.toString());
assertEquals("Σ", url.fragment());
assertEquals("Σ", url.encodedFragment());
assertEquals("http://host/#Σ", url.uri().toString());
}

@Test public void fragmentNonAsciiThatOffendsJavaNetUri() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#\u0080");
assertEquals("http://host/#\u0080", url.toString());
assertEquals("\u0080", url.fragment());
assertEquals("\u0080", url.encodedFragment());
assertEquals(new URI("http://host/#"), url.uri()); // Control characters may be stripped!
}

@Test public void fragmentPercentEncodedNonAscii() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#%C2%80");
assertEquals("http://host/#%C2%80", url.toString());
assertEquals("\u0080", url.fragment());
assertEquals("%C2%80", url.encodedFragment());
assertEquals("http://host/#%C2%80", url.uri().toString());
}

@Test public void fragmentPercentEncodedPartialCodePoint() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#%80");
assertEquals("http://host/#%80", url.toString());
assertEquals("\ufffd", url.fragment()); // Unicode replacement character.
assertEquals("%80", url.encodedFragment());
assertEquals("http://host/#%80", url.uri().toString());
}

@Test public void relativePath() throws Exception {
Expand Down Expand Up @@ -928,14 +969,128 @@ public final class HttpUrlTest {
assertEquals("http://host/?d=abc!@[]%5E%60%7B%7D%7C%5C", uri.toString());
}

@Test public void toUriForbiddenCharacter() throws Exception {
HttpUrl httpUrl = HttpUrl.parse("http://host/a[b");
try {
httpUrl.uri();
fail();
} catch (IllegalStateException expected) {
assertEquals("not valid as a java.net.URI: http://host/a[b", expected.getMessage());
}
@Test public void toUriWithUsernameNoPassword() throws Exception {
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("http")
.username("user")
.host("host")
.build();
assertEquals("http://user@host/", httpUrl.toString());
assertEquals("http://user@host/", httpUrl.uri().toString());
}

@Test public void toUriUsernameSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.username("=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.toString());
assertEquals("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.uri().toString());
}

@Test public void toUriPasswordSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.username("user")
.password("=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.toString());
assertEquals("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/",
url.uri().toString());
}

@Test public void toUriPathSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.addPathSegment("=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://host/=[]:;%22~%7C%3F%23@%5E%2F$%25*", url.toString());
assertEquals("http://host/=%5B%5D:;%22~%7C%3F%23@%5E%2F$%25*", url.uri().toString());
}

@Test public void toUriQueryParameterNameSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.addQueryParameter("=[]:;\"~|?#@^/$%*", "a")
.build();
assertEquals("http://host/?%3D[]:;%22~|?%23@^/$%25*=a", url.toString());
assertEquals("http://host/?%3D[]:;%22~%7C?%23@%5E/$%25*=a", url.uri().toString());
}

@Test public void toUriQueryParameterValueSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.addQueryParameter("a", "=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://host/?a=%3D[]:;%22~|?%23@^/$%25*", url.toString());
assertEquals("http://host/?a=%3D[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
}

@Test public void toUriQueryValueSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.query("=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://host/?=[]:;%22~|?%23@^/$%25*", url.toString());
assertEquals("http://host/?=[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
}

@Test public void toUriFragmentSpecialCharacters() throws Exception {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("host")
.fragment("=[]:;\"~|?#@^/$%*")
.build();
assertEquals("http://host/#=[]:;\"~|?#@^/$%25*", url.toString());
assertEquals("http://host/#=[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
}

@Test public void toUriWithControlCharacters() throws Exception {
// Percent-encoded in the path.
assertEquals(new URI("http://host/a%00b"), HttpUrl.parse("http://host/a\u0000b").uri());
assertEquals(new URI("http://host/a%C2%80b"), HttpUrl.parse("http://host/a\u0080b").uri());
assertEquals(new URI("http://host/a%C2%9Fb"), HttpUrl.parse("http://host/a\u009fb").uri());
// Percent-encoded in the query.
assertEquals(new URI("http://host/?a%00b"), HttpUrl.parse("http://host/?a\u0000b").uri());
assertEquals(new URI("http://host/?a%C2%80b"), HttpUrl.parse("http://host/?a\u0080b").uri());
assertEquals(new URI("http://host/?a%C2%9Fb"), HttpUrl.parse("http://host/?a\u009fb").uri());
// Stripped from the fragment.
assertEquals(new URI("http://host/#a%00b"), HttpUrl.parse("http://host/#a\u0000b").uri());
assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u0080b").uri());
assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u009fb").uri());
}

@Test public void toUriWithSpaceCharacters() throws Exception {
// Percent-encoded in the path.
assertEquals(new URI("http://host/a%0Bb"), HttpUrl.parse("http://host/a\u000bb").uri());
assertEquals(new URI("http://host/a%20b"), HttpUrl.parse("http://host/a b").uri());
assertEquals(new URI("http://host/a%E2%80%89b"), HttpUrl.parse("http://host/a\u2009b").uri());
assertEquals(new URI("http://host/a%E3%80%80b"), HttpUrl.parse("http://host/a\u3000b").uri());
// Percent-encoded in the query.
assertEquals(new URI("http://host/?a%0Bb"), HttpUrl.parse("http://host/?a\u000bb").uri());
assertEquals(new URI("http://host/?a%20b"), HttpUrl.parse("http://host/?a b").uri());
assertEquals(new URI("http://host/?a%E2%80%89b"), HttpUrl.parse("http://host/?a\u2009b").uri());
assertEquals(new URI("http://host/?a%E3%80%80b"), HttpUrl.parse("http://host/?a\u3000b").uri());
// Stripped from the fragment.
assertEquals(new URI("http://host/#a%0Bb"), HttpUrl.parse("http://host/#a\u000bb").uri());
assertEquals(new URI("http://host/#a%20b"), HttpUrl.parse("http://host/#a b").uri());
assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u2009b").uri());
assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u3000b").uri());
}

@Test public void toUriWithNonHexPercentEscape() throws Exception {
assertEquals(new URI("http://host/%25xx"), HttpUrl.parse("http://host/%xx").uri());
}

@Test public void toUriWithTruncatedPercentEscape() throws Exception {
assertEquals(new URI("http://host/%25a"), HttpUrl.parse("http://host/%a").uri());
assertEquals(new URI("http://host/%25"), HttpUrl.parse("http://host/%").uri());
}

@Test public void fromJavaNetUrl() throws Exception {
Expand Down Expand Up @@ -1169,4 +1324,24 @@ public final class HttpUrlTest {
assertEquals(urlString, url.newBuilder().build().toString());
assertEquals("http://%6d%6D:%6d%6D@host/%6d%6D?%6d%6D", url.resolve("").toString());
}

@Test public void clearFragment() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#fragment")
.newBuilder()
.fragment(null)
.build();
assertEquals("http://host/", url.toString());
assertEquals(null, url.fragment());
assertEquals(null, url.encodedFragment());
}

@Test public void clearEncodedFragment() throws Exception {
HttpUrl url = HttpUrl.parse("http://host/#fragment")
.newBuilder()
.encodedFragment(null)
.build();
assertEquals("http://host/", url.toString());
assertEquals(null, url.fragment());
assertEquals(null, url.encodedFragment());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2429,7 +2429,7 @@ private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws I
}

@Test public void malformedUrlThrowsUnknownHostException() throws IOException {
connection = client.open(new URL("http:///foo.html"));
connection = client.open(new URL("http://./foo.html"));
try {
connection.connect();
fail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@

/** Tests how each code point is encoded and decoded in the context of each URL component. */
class UrlComponentEncodingTester {
private static final int UNICODE_2 = 0x07ff; // Arbitrary code point that's 2 bytes in UTF-8.
private static final int UNICODE_3 = 0xffff; // Arbitrary code point that's 3 bytes in UTF-8.
private static final int UNICODE_4 = 0x10ffff; // Arbitrary code point that's 4 bytes in UTF-8.

/**
* The default encode set for the ASCII range. The specific rules vary per-component: for example,
* '?' may be identity-encoded in a fragment, but must be percent-encoded in a path.
Expand Down Expand Up @@ -164,11 +168,14 @@ class UrlComponentEncodingTester {
map.put((int) '}', Encoding.IDENTITY);
map.put((int) '~', Encoding.IDENTITY);
map.put( 0x7f, Encoding.PERCENT); // Delete
map.put( UNICODE_2, Encoding.PERCENT);
map.put( UNICODE_3, Encoding.PERCENT);
map.put( UNICODE_4, Encoding.PERCENT);
defaultEncodings = Collections.unmodifiableMap(map);
}

private final Map<Integer, Encoding> encodings;
private final StringBuilder skipForUri = new StringBuilder();
private final StringBuilder uriEscapedCodePoints = new StringBuilder();

public UrlComponentEncodingTester() {
this.encodings = new LinkedHashMap<>(defaultEncodings);
Expand All @@ -181,12 +188,19 @@ public UrlComponentEncodingTester override(Encoding encoding, int... codePoints)
return this;
}

public UrlComponentEncodingTester identityForNonAscii() {
encodings.put(UNICODE_2, Encoding.IDENTITY);
encodings.put(UNICODE_3, Encoding.IDENTITY);
encodings.put(UNICODE_4, Encoding.IDENTITY);
return this;
}

/**
* Configure a character to be skipped but only for conversion to and from {@code java.net.URI}.
* That class is more strict than the others.
*/
public UrlComponentEncodingTester skipForUri(int... codePoints) {
skipForUri.append(new String(codePoints, 0, codePoints.length));
uriEscapedCodePoints.append(new String(codePoints, 0, codePoints.length));
return this;
}

Expand All @@ -202,9 +216,10 @@ public UrlComponentEncodingTester test(Component component) {
testToUrl(codePoint, encoding, component);
testFromUrl(codePoint, encoding, component);

if (skipForUri.indexOf(Encoding.IDENTITY.encode(codePoint)) == -1) {
testToUri(codePoint, encoding, component);
testFromUri(codePoint, encoding, component);
if (codePoint != '%') {
boolean uriEscaped = uriEscapedCodePoints.indexOf(
Encoding.IDENTITY.encode(codePoint)) != -1;
testUri(codePoint, encoding, component, uriEscaped);
}
}
return this;
Expand Down Expand Up @@ -261,21 +276,29 @@ private void testFromUrl(int codePoint, Encoding encoding, Component component)
}
}

private void testToUri(int codePoint, Encoding encoding, Component component) {
private void testUri(
int codePoint, Encoding encoding, Component component, boolean uriEscaped) {
String string = new String(new int[] { codePoint }, 0, 1);
String encoded = encoding.encode(codePoint);
HttpUrl httpUrl = HttpUrl.parse(component.urlString(encoded));
URI uri = httpUrl.uri();
if (!uri.toString().equals(uri.toString())) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
}
}

private void testFromUri(int codePoint, Encoding encoding, Component component) {
String encoded = encoding.encode(codePoint);
HttpUrl httpUrl = HttpUrl.parse(component.urlString(encoded));
HttpUrl toAndFromUri = HttpUrl.get(httpUrl.uri());
if (!toAndFromUri.equals(httpUrl)) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
HttpUrl toAndFromUri = HttpUrl.get(uri);
if (uriEscaped) {
// The URI has more escaping than the HttpURL. Check that the decoded values still match.
if (uri.toString().equals(httpUrl.toString())) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
}
if (!component.get(toAndFromUri).equals(string)) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
}
} else {
// Check that the URI and HttpURL have the exact same escaping.
if (!toAndFromUri.equals(httpUrl)) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
}
if (!uri.toString().equals(httpUrl.toString())) {
fail(String.format("Encoding %s %#x using %s", component, codePoint, encoding));
}
}
}

Expand Down Expand Up @@ -358,10 +381,11 @@ public enum Component {
return query.substring(1, query.length() - 1);
}
@Override public void set(HttpUrl.Builder builder, String value) {
builder.query(value);
builder.query("a" + value + "z");
}
@Override public String get(HttpUrl url) {
return url.query();
String query = url.query();
return query.substring(1, query.length() - 1);
}
},
FRAGMENT {
Expand All @@ -373,10 +397,11 @@ public enum Component {
return fragment.substring(1, fragment.length() - 1);
}
@Override public void set(HttpUrl.Builder builder, String value) {
builder.fragment(value);
builder.fragment("a" + value + "z");
}
@Override public String get(HttpUrl url) {
return url.fragment();
String fragment = url.fragment();
return fragment.substring(1, fragment.length() - 1);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ public static List<Object[]> parameters() {
"Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>",
"Parsing: <http://f:\n/c> against <http://example.org/foo/bar>",
"Parsing: <http://f:999999/c> against <http://example.org/foo/bar>",
"Parsing: <#β> against <http://example.org/foo/bar>",
"Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>",
"Parsing: <http://192.0x00A80001> against <about:blank>",
// This test fails on Java 7 but passes on Java 8. See HttpUrlTest.hostWithTrailingDot().
"Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>",
Expand Down
Loading

0 comments on commit 02220ee

Please sign in to comment.