• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.okhttp;
17 
18 import com.squareup.okhttp.UrlComponentEncodingTester.Component;
19 import com.squareup.okhttp.UrlComponentEncodingTester.Encoding;
20 import java.net.MalformedURLException;
21 import java.net.URI;
22 import java.net.URL;
23 import java.net.UnknownHostException;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.LinkedHashSet;
27 import org.junit.Ignore;
28 import org.junit.Test;
29 
30 import static java.util.Collections.singletonList;
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertNull;
34 import static org.junit.Assert.fail;
35 
36 public final class HttpUrlTest {
parseTrimsAsciiWhitespace()37   @Test public void parseTrimsAsciiWhitespace() throws Exception {
38     HttpUrl expected = HttpUrl.parse("http://host/");
39     assertEquals(expected, HttpUrl.parse("http://host/\f\n\t \r")); // Leading.
40     assertEquals(expected, HttpUrl.parse("\r\n\f \thttp://host/")); // Trailing.
41     assertEquals(expected, HttpUrl.parse(" http://host/ ")); // Both.
42     assertEquals(expected, HttpUrl.parse("    http://host/    ")); // Both.
43     assertEquals(expected, HttpUrl.parse("http://host/").resolve("   "));
44     assertEquals(expected, HttpUrl.parse("http://host/").resolve("  .  "));
45   }
46 
parseHostAsciiNonPrintable()47   @Test public void parseHostAsciiNonPrintable() throws Exception {
48     String host = "host\u0001";
49     assertNull(HttpUrl.parse("http://" + host + "/"));
50   }
51 
parseDoesNotTrimOtherWhitespaceCharacters()52   @Test public void parseDoesNotTrimOtherWhitespaceCharacters() throws Exception {
53     // Whitespace characters list from Google's Guava team: http://goo.gl/IcR9RD
54     assertEquals("/%0B", HttpUrl.parse("http://h/\u000b").encodedPath()); // line tabulation
55     assertEquals("/%1C", HttpUrl.parse("http://h/\u001c").encodedPath()); // information separator 4
56     assertEquals("/%1D", HttpUrl.parse("http://h/\u001d").encodedPath()); // information separator 3
57     assertEquals("/%1E", HttpUrl.parse("http://h/\u001e").encodedPath()); // information separator 2
58     assertEquals("/%1F", HttpUrl.parse("http://h/\u001f").encodedPath()); // information separator 1
59     assertEquals("/%C2%85", HttpUrl.parse("http://h/\u0085").encodedPath()); // next line
60     assertEquals("/%C2%A0", HttpUrl.parse("http://h/\u00a0").encodedPath()); // non-breaking space
61     assertEquals("/%E1%9A%80", HttpUrl.parse("http://h/\u1680").encodedPath()); // ogham space mark
62     assertEquals("/%E1%A0%8E", HttpUrl.parse("http://h/\u180e").encodedPath()); // mongolian vowel separator
63     assertEquals("/%E2%80%80", HttpUrl.parse("http://h/\u2000").encodedPath()); // en quad
64     assertEquals("/%E2%80%81", HttpUrl.parse("http://h/\u2001").encodedPath()); // em quad
65     assertEquals("/%E2%80%82", HttpUrl.parse("http://h/\u2002").encodedPath()); // en space
66     assertEquals("/%E2%80%83", HttpUrl.parse("http://h/\u2003").encodedPath()); // em space
67     assertEquals("/%E2%80%84", HttpUrl.parse("http://h/\u2004").encodedPath()); // three-per-em space
68     assertEquals("/%E2%80%85", HttpUrl.parse("http://h/\u2005").encodedPath()); // four-per-em space
69     assertEquals("/%E2%80%86", HttpUrl.parse("http://h/\u2006").encodedPath()); // six-per-em space
70     assertEquals("/%E2%80%87", HttpUrl.parse("http://h/\u2007").encodedPath()); // figure space
71     assertEquals("/%E2%80%88", HttpUrl.parse("http://h/\u2008").encodedPath()); // punctuation space
72     assertEquals("/%E2%80%89", HttpUrl.parse("http://h/\u2009").encodedPath()); // thin space
73     assertEquals("/%E2%80%8A", HttpUrl.parse("http://h/\u200a").encodedPath()); // hair space
74     assertEquals("/%E2%80%8B", HttpUrl.parse("http://h/\u200b").encodedPath()); // zero-width space
75     assertEquals("/%E2%80%8C", HttpUrl.parse("http://h/\u200c").encodedPath()); // zero-width non-joiner
76     assertEquals("/%E2%80%8D", HttpUrl.parse("http://h/\u200d").encodedPath()); // zero-width joiner
77     assertEquals("/%E2%80%8E", HttpUrl.parse("http://h/\u200e").encodedPath()); // left-to-right mark
78     assertEquals("/%E2%80%8F", HttpUrl.parse("http://h/\u200f").encodedPath()); // right-to-left mark
79     assertEquals("/%E2%80%A8", HttpUrl.parse("http://h/\u2028").encodedPath()); // line separator
80     assertEquals("/%E2%80%A9", HttpUrl.parse("http://h/\u2029").encodedPath()); // paragraph separator
81     assertEquals("/%E2%80%AF", HttpUrl.parse("http://h/\u202f").encodedPath()); // narrow non-breaking space
82     assertEquals("/%E2%81%9F", HttpUrl.parse("http://h/\u205f").encodedPath()); // medium mathematical space
83     assertEquals("/%E3%80%80", HttpUrl.parse("http://h/\u3000").encodedPath()); // ideographic space
84   }
85 
scheme()86   @Test public void scheme() throws Exception {
87     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host/"));
88     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("Http://host/"));
89     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host/"));
90     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("HTTP://host/"));
91     assertEquals(HttpUrl.parse("https://host/"), HttpUrl.parse("https://host/"));
92     assertEquals(HttpUrl.parse("https://host/"), HttpUrl.parse("HTTPS://host/"));
93     assertEquals(HttpUrl.Builder.ParseResult.UNSUPPORTED_SCHEME,
94         new HttpUrl.Builder().parse(null, "image640://480.png"));
95     assertEquals(null, HttpUrl.parse("httpp://host/"));
96     assertEquals(null, HttpUrl.parse("0ttp://host/"));
97     assertEquals(null, HttpUrl.parse("ht+tp://host/"));
98     assertEquals(null, HttpUrl.parse("ht.tp://host/"));
99     assertEquals(null, HttpUrl.parse("ht-tp://host/"));
100     assertEquals(null, HttpUrl.parse("ht1tp://host/"));
101     assertEquals(null, HttpUrl.parse("httpss://host/"));
102   }
103 
parseNoScheme()104   @Test public void parseNoScheme() throws Exception {
105     assertEquals(null, HttpUrl.parse("//host"));
106     assertEquals(null, HttpUrl.parse("/path"));
107     assertEquals(null, HttpUrl.parse("path"));
108     assertEquals(null, HttpUrl.parse("?query"));
109     assertEquals(null, HttpUrl.parse("#fragment"));
110   }
111 
resolveNoScheme()112   @Test public void resolveNoScheme() throws Exception {
113     HttpUrl base = HttpUrl.parse("http://host/a/b");
114     // ANDROID-BEGIN: http://b/29983827
115     // assertEquals(HttpUrl.parse("http://host2/"), base.resolve("//host2"));
116     assertEquals(HttpUrl.parse("http://host2"), base.resolve("//host2"));
117     // ANDROID-END: http://b/29983827
118     assertEquals(HttpUrl.parse("http://host2"), base.resolve("//host2"));
119     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("/path"));
120     assertEquals(HttpUrl.parse("http://host/a/path"), base.resolve("path"));
121     assertEquals(HttpUrl.parse("http://host/a/b?query"), base.resolve("?query"));
122     assertEquals(HttpUrl.parse("http://host/a/b#fragment"), base.resolve("#fragment"));
123     assertEquals(HttpUrl.parse("http://host/a/b"), base.resolve(""));
124     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("\\path"));
125   }
126 
127   // ANDROID-BEGIN: http://b/29983827
encodedPath_pathVariations()128   @Test public void encodedPath_pathVariations() throws Exception {
129     assertEquals("", HttpUrl.parse("http://example.com").encodedPath());
130     assertEquals("", HttpUrl.parse("http://example.com#fragment").encodedPath());
131     assertEquals("", HttpUrl.parse("http://example.com?arg=value").encodedPath());
132     assertEquals("/", HttpUrl.parse("http://example.com/").encodedPath());
133     assertEquals("/", HttpUrl.parse("http://example.com/#fragment").encodedPath());
134     assertEquals("/", HttpUrl.parse("http://example.com/?arg=value").encodedPath());
135     assertEquals("/foo", HttpUrl.parse("http://example.com/foo").encodedPath());
136     assertEquals("/foo/", HttpUrl.parse("http://example.com/foo/").encodedPath());
137   }
138 
newBuilder_pathVariations()139   @Test public void newBuilder_pathVariations() throws Exception {
140     assertNewBuilderRoundtrip("http://example.com");
141     assertNewBuilderRoundtrip("http://example.com/");
142     assertNewBuilderRoundtrip("http://example.com/foo");
143     assertNewBuilderRoundtrip("http://example.com/foo/");
144   }
145 
assertNewBuilderRoundtrip(String urlString)146   private void assertNewBuilderRoundtrip(String urlString) {
147     HttpUrl url = HttpUrl.parse(urlString);
148     assertEquals(url, url.newBuilder().build());
149   }
150 
equals_emptyPathNotSameAsSlash()151   @Test public void equals_emptyPathNotSameAsSlash() throws Exception {
152     assertFalse(HttpUrl.parse("http://example.com").equals(HttpUrl.parse("http://example.com/")));
153   }
154 
parse_pathVariations()155   @Test public void parse_pathVariations() throws Exception {
156     parseThenAssertToStringEquals("http://example.com");
157     parseThenAssertToStringEquals("https://example.com");
158     parseThenAssertToStringEquals("http://example.com?value=42");
159     parseThenAssertToStringEquals("http://example.com:3434");
160     parseThenAssertToStringEquals("http://example.com#hello");
161 
162     parseThenAssertToStringEquals("http://example.com/");
163     parseThenAssertToStringEquals("https://example.com/");
164     parseThenAssertToStringEquals("http://example.com/foo/bar");
165     parseThenAssertToStringEquals("http://example.com/foo/bar/");
166     parseThenAssertToStringEquals("http://example.com/foo/bar?value=100");
167     parseThenAssertToStringEquals("http://example.com/?value=200");
168 
169     {
170       HttpUrl httpUrl = HttpUrl.parse("http://example.com/foo/..");
171       assertEquals("http://example.com/", httpUrl.toString());
172     }
173 
174     {
175       HttpUrl httpUrl = HttpUrl.parse("http://example.com/..");
176       assertEquals("http://example.com/", httpUrl.toString());
177     }
178   }
179 
parseThenAssertToStringEquals(String url)180   private static void parseThenAssertToStringEquals(String url) {
181     HttpUrl httpUrl = HttpUrl.parse(url);
182     assertEquals(url, httpUrl.toString());
183   }
184 
resolve_pathVariations()185   @Test public void resolve_pathVariations() throws Exception {
186     HttpUrl baseWithoutSlash = HttpUrl.parse("http://example.com");
187     assertResolveResult("http://example.com#section", baseWithoutSlash, "#section");
188     assertResolveResult("http://example.com?attitude=friendly", baseWithoutSlash,
189             "?attitude=friendly");
190     assertResolveResult("http://example.com", baseWithoutSlash, "");
191     assertResolveResult("http://example.com/", baseWithoutSlash, "/");
192     assertResolveResult("http://example.com/foo", baseWithoutSlash, "/foo");
193     assertResolveResult("http://example.com/foo", baseWithoutSlash, "foo");
194 
195     assertResolveResult("http://other.com", baseWithoutSlash, "http://other.com");
196     assertResolveResult("http://other.com/", baseWithoutSlash, "http://other.com/");
197     assertResolveResult("http://other.com/foo", baseWithoutSlash, "http://other.com/foo");
198 
199     HttpUrl baseWithSlash = HttpUrl.parse("http://example.com/");
200     assertResolveResult("http://example.com/#section", baseWithSlash, "#section");
201     assertResolveResult("http://example.com/?attitude=friendly", baseWithSlash,
202             "?attitude=friendly");
203     assertResolveResult("http://example.com/", baseWithSlash, "");
204     assertResolveResult("http://example.com/", baseWithSlash, "/");
205     assertResolveResult("http://example.com/foo", baseWithSlash, "/foo");
206     assertResolveResult("http://example.com/foo", baseWithSlash, "foo");
207 
208     assertResolveResult("http://other.com", baseWithSlash, "http://other.com");
209     assertResolveResult("http://other.com/", baseWithSlash, "http://other.com/");
210     assertResolveResult("http://other.com/foo", baseWithSlash, "http://other.com/foo");
211   }
212 
assertResolveResult(String expected, HttpUrl base, String link)213   private void assertResolveResult(String expected, HttpUrl base, String link) {
214     assertEquals(HttpUrl.parse(expected), base.resolve(link));
215     assertEquals(expected, base.resolve(link).toString());
216   }
217   // ANDROID-END: http://b/29983827
218 
resolveUnsupportedScheme()219   @Test public void resolveUnsupportedScheme() throws Exception {
220     HttpUrl base = HttpUrl.parse("http://a/");
221     assertEquals(null, base.resolve("ftp://b"));
222     assertEquals(null, base.resolve("ht+tp://b"));
223     assertEquals(null, base.resolve("ht-tp://b"));
224     assertEquals(null, base.resolve("ht.tp://b"));
225   }
226 
resolveSchemeLikePath()227   @Test public void resolveSchemeLikePath() throws Exception {
228     HttpUrl base = HttpUrl.parse("http://a/");
229     assertEquals(HttpUrl.parse("http://a/http//b/"), base.resolve("http//b/"));
230     assertEquals(HttpUrl.parse("http://a/ht+tp//b/"), base.resolve("ht+tp//b/"));
231     assertEquals(HttpUrl.parse("http://a/ht-tp//b/"), base.resolve("ht-tp//b/"));
232     assertEquals(HttpUrl.parse("http://a/ht.tp//b/"), base.resolve("ht.tp//b/"));
233   }
234 
parseAuthoritySlashCountDoesntMatter()235   @Test public void parseAuthoritySlashCountDoesntMatter() throws Exception {
236     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:host/path"));
237     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/host/path"));
238     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\host/path"));
239     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://host/path"));
240     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\/host/path"));
241     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\host/path"));
242     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\host/path"));
243     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:///host/path"));
244     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\//host/path"));
245     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\/host/path"));
246     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://\\host/path"));
247     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\/host/path"));
248     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\\\host/path"));
249     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\\\host/path"));
250     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:////host/path"));
251   }
252 
resolveAuthoritySlashCountDoesntMatterWithDifferentScheme()253   @Test public void resolveAuthoritySlashCountDoesntMatterWithDifferentScheme() throws Exception {
254     HttpUrl base = HttpUrl.parse("https://a/b/c");
255     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:host/path"));
256     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/host/path"));
257     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\host/path"));
258     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://host/path"));
259     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\/host/path"));
260     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\host/path"));
261     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\host/path"));
262     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:///host/path"));
263     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\//host/path"));
264     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\/host/path"));
265     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://\\host/path"));
266     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\/host/path"));
267     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\\\host/path"));
268     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\\\host/path"));
269     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:////host/path"));
270   }
271 
resolveAuthoritySlashCountMattersWithSameScheme()272   @Test public void resolveAuthoritySlashCountMattersWithSameScheme() throws Exception {
273     HttpUrl base = HttpUrl.parse("http://a/b/c");
274     assertEquals(HttpUrl.parse("http://a/b/host/path"), base.resolve("http:host/path"));
275     assertEquals(HttpUrl.parse("http://a/host/path"), base.resolve("http:/host/path"));
276     assertEquals(HttpUrl.parse("http://a/host/path"), base.resolve("http:\\host/path"));
277     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://host/path"));
278     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\/host/path"));
279     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\host/path"));
280     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\host/path"));
281     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:///host/path"));
282     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\//host/path"));
283     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\/host/path"));
284     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://\\host/path"));
285     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\/host/path"));
286     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\\\host/path"));
287     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\\\host/path"));
288     assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:////host/path"));
289   }
290 
username()291   @Test public void username() throws Exception {
292     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://@host/path"));
293     assertEquals(HttpUrl.parse("http://user@host/path"), HttpUrl.parse("http://user@host/path"));
294   }
295 
296   /** Given multiple '@' characters, the last one is the delimiter. */
authorityWithMultipleAtSigns()297   @Test public void authorityWithMultipleAtSigns() throws Exception {
298     HttpUrl httpUrl = HttpUrl.parse("http://foo@bar@baz/path");
299     assertEquals("foo@bar", httpUrl.username());
300     assertEquals("", httpUrl.password());
301     assertEquals(HttpUrl.parse("http://foo%40bar@baz/path"), httpUrl);
302   }
303 
304   /** Given multiple ':' characters, the first one is the delimiter. */
authorityWithMultipleColons()305   @Test public void authorityWithMultipleColons() throws Exception {
306     HttpUrl httpUrl = HttpUrl.parse("http://foo:pass1@bar:pass2@baz/path");
307     assertEquals("foo", httpUrl.username());
308     assertEquals("pass1@bar:pass2", httpUrl.password());
309     assertEquals(HttpUrl.parse("http://foo:pass1%40bar%3Apass2@baz/path"), httpUrl);
310   }
311 
usernameAndPassword()312   @Test public void usernameAndPassword() throws Exception {
313     assertEquals(HttpUrl.parse("http://username:password@host/path"),
314         HttpUrl.parse("http://username:password@host/path"));
315     assertEquals(HttpUrl.parse("http://username@host/path"),
316         HttpUrl.parse("http://username:@host/path"));
317   }
318 
passwordWithEmptyUsername()319   @Test public void passwordWithEmptyUsername() throws Exception {
320     // Chrome doesn't mind, but Firefox rejects URLs with empty usernames and non-empty passwords.
321     assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://:@host/path"));
322     assertEquals("password%40", HttpUrl.parse("http://:password@@host/path").encodedPassword());
323   }
324 
unprintableCharactersArePercentEncoded()325   @Test public void unprintableCharactersArePercentEncoded() throws Exception {
326     assertEquals("/%00", HttpUrl.parse("http://host/\u0000").encodedPath());
327     assertEquals("/%08", HttpUrl.parse("http://host/\u0008").encodedPath());
328     assertEquals("/%EF%BF%BD", HttpUrl.parse("http://host/\ufffd").encodedPath());
329   }
330 
usernameCharacters()331   @Test public void usernameCharacters() throws Exception {
332     new UrlComponentEncodingTester()
333         .override(Encoding.PERCENT, '[', ']', '{', '}', '|', '^', '\'', ';', '=', '@')
334         .override(Encoding.SKIP, ':', '/', '\\', '?', '#')
335         .skipForUri('%')
336         .test(Component.USER);
337   }
338 
passwordCharacters()339   @Test public void passwordCharacters() throws Exception {
340     new UrlComponentEncodingTester()
341         .override(Encoding.PERCENT, '[', ']', '{', '}', '|', '^', '\'', ':', ';', '=', '@')
342         .override(Encoding.SKIP, '/', '\\', '?', '#')
343         .skipForUri('%')
344         .test(Component.PASSWORD);
345   }
346 
hostContainsIllegalCharacter()347   @Test public void hostContainsIllegalCharacter() throws Exception {
348     assertEquals(null, HttpUrl.parse("http://\n/"));
349     assertEquals(null, HttpUrl.parse("http:// /"));
350     assertEquals(null, HttpUrl.parse("http://%20/"));
351   }
352 
hostnameLowercaseCharactersMappedDirectly()353   @Test public void hostnameLowercaseCharactersMappedDirectly() throws Exception {
354     assertEquals("abcd", HttpUrl.parse("http://abcd").host());
355     assertEquals("xn--4xa", HttpUrl.parse("http://σ").host());
356   }
357 
hostnameUppercaseCharactersConvertedToLowercase()358   @Test public void hostnameUppercaseCharactersConvertedToLowercase() throws Exception {
359     assertEquals("abcd", HttpUrl.parse("http://ABCD").host());
360     assertEquals("xn--4xa", HttpUrl.parse("http://Σ").host());
361   }
362 
hostnameIgnoredCharacters()363   @Test public void hostnameIgnoredCharacters() throws Exception {
364     // The soft hyphen (­) should be ignored.
365     assertEquals("abcd", HttpUrl.parse("http://AB\u00adCD").host());
366   }
367 
hostnameMultipleCharacterMapping()368   @Test public void hostnameMultipleCharacterMapping() throws Exception {
369     // Map the single character telephone symbol (℡) to the string "tel".
370     assertEquals("tel", HttpUrl.parse("http://\u2121").host());
371   }
372 
hostnameMappingLastMappedCodePoint()373   @Test public void hostnameMappingLastMappedCodePoint() throws Exception {
374     assertEquals("xn--pu5l", HttpUrl.parse("http://\uD87E\uDE1D").host());
375   }
376 
377   @Ignore("The java.net.IDN implementation doesn't ignore characters that it should.")
hostnameMappingLastIgnoredCodePoint()378   @Test public void hostnameMappingLastIgnoredCodePoint() throws Exception {
379     assertEquals("abcd", HttpUrl.parse("http://ab\uDB40\uDDEFcd").host());
380   }
381 
hostnameMappingLastDisallowedCodePoint()382   @Test public void hostnameMappingLastDisallowedCodePoint() throws Exception {
383     assertEquals(null, HttpUrl.parse("http://\uDBFF\uDFFF"));
384   }
385 
hostIpv6()386   @Test public void hostIpv6() throws Exception {
387     // Square braces are absent from host()...
388     assertEquals("::1", HttpUrl.parse("http://[::1]/").host());
389 
390     // ... but they're included in toString().
391     assertEquals("http://[::1]/", HttpUrl.parse("http://[::1]/").toString());
392 
393     // IPv6 colons don't interfere with port numbers or passwords.
394     assertEquals(8080, HttpUrl.parse("http://[::1]:8080/").port());
395     assertEquals("password", HttpUrl.parse("http://user:password@[::1]/").password());
396     assertEquals("::1", HttpUrl.parse("http://user:password@[::1]:8080/").host());
397 
398     // Permit the contents of IPv6 addresses to be percent-encoded...
399     assertEquals("::1", HttpUrl.parse("http://[%3A%3A%31]/").host());
400 
401     // Including the Square braces themselves! (This is what Chrome does.)
402     assertEquals("::1", HttpUrl.parse("http://%5B%3A%3A1%5D/").host());
403   }
404 
hostIpv6AddressDifferentFormats()405   @Test public void hostIpv6AddressDifferentFormats() throws Exception {
406     // Multiple representations of the same address; see http://tools.ietf.org/html/rfc5952.
407     String a3 = "2001:db8::1:0:0:1";
408     assertEquals(a3, HttpUrl.parse("http://[2001:db8:0:0:1:0:0:1]").host());
409     assertEquals(a3, HttpUrl.parse("http://[2001:0db8:0:0:1:0:0:1]").host());
410     assertEquals(a3, HttpUrl.parse("http://[2001:db8::1:0:0:1]").host());
411     assertEquals(a3, HttpUrl.parse("http://[2001:db8::0:1:0:0:1]").host());
412     assertEquals(a3, HttpUrl.parse("http://[2001:0db8::1:0:0:1]").host());
413     assertEquals(a3, HttpUrl.parse("http://[2001:db8:0:0:1::1]").host());
414     assertEquals(a3, HttpUrl.parse("http://[2001:db8:0000:0:1::1]").host());
415     assertEquals(a3, HttpUrl.parse("http://[2001:DB8:0:0:1::1]").host());
416   }
417 
hostIpv6AddressLeadingCompression()418   @Test public void hostIpv6AddressLeadingCompression() throws Exception {
419     assertEquals("::1", HttpUrl.parse("http://[::0001]").host());
420     assertEquals("::1", HttpUrl.parse("http://[0000::0001]").host());
421     assertEquals("::1", HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001]").host());
422     assertEquals("::1", HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000::0001]").host());
423   }
424 
hostIpv6AddressTrailingCompression()425   @Test public void hostIpv6AddressTrailingCompression() throws Exception {
426     assertEquals("1::", HttpUrl.parse("http://[0001:0000::]").host());
427     assertEquals("1::", HttpUrl.parse("http://[0001::0000]").host());
428     assertEquals("1::", HttpUrl.parse("http://[0001::]").host());
429     assertEquals("1::", HttpUrl.parse("http://[1::]").host());
430   }
431 
hostIpv6AddressTooManyDigitsInGroup()432   @Test public void hostIpv6AddressTooManyDigitsInGroup() throws Exception {
433     assertEquals(null, HttpUrl.parse("http://[00000:0000:0000:0000:0000:0000:0000:0001]"));
434     assertEquals(null, HttpUrl.parse("http://[::00001]"));
435   }
436 
hostIpv6AddressMisplacedColons()437   @Test public void hostIpv6AddressMisplacedColons() throws Exception {
438     assertEquals(null, HttpUrl.parse("http://[:0000:0000:0000:0000:0000:0000:0000:0001]"));
439     assertEquals(null, HttpUrl.parse("http://[:::0000:0000:0000:0000:0000:0000:0000:0001]"));
440     assertEquals(null, HttpUrl.parse("http://[:1]"));
441     assertEquals(null, HttpUrl.parse("http://[:::1]"));
442     assertEquals(null, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0001:]"));
443     assertEquals(null, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001:]"));
444     assertEquals(null, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001::]"));
445     assertEquals(null, HttpUrl.parse("http://[0000:0000:0000:0000:0000:0000:0000:0001:::]"));
446     assertEquals(null, HttpUrl.parse("http://[1:]"));
447     assertEquals(null, HttpUrl.parse("http://[1:::]"));
448     assertEquals(null, HttpUrl.parse("http://[1:::1]"));
449     assertEquals(null, HttpUrl.parse("http://[00000:0000:0000:0000::0000:0000:0000:0001]"));
450   }
451 
hostIpv6AddressTooManyGroups()452   @Test public void hostIpv6AddressTooManyGroups() throws Exception {
453     assertEquals(null, HttpUrl.parse("http://[00000:0000:0000:0000:0000:0000:0000:0000:0001]"));
454   }
455 
hostIpv6AddressTooMuchCompression()456   @Test public void hostIpv6AddressTooMuchCompression() throws Exception {
457     assertEquals(null, HttpUrl.parse("http://[0000::0000:0000:0000:0000::0001]"));
458     assertEquals(null, HttpUrl.parse("http://[::0000:0000:0000:0000::0001]"));
459   }
460 
hostIpv6ScopedAddress()461   @Test public void hostIpv6ScopedAddress() throws Exception {
462     // java.net.InetAddress parses scoped addresses. These aren't valid in URLs.
463     assertEquals(null, HttpUrl.parse("http://[::1%2544]"));
464   }
465 
hostIpv6WithIpv4Suffix()466   @Test public void hostIpv6WithIpv4Suffix() throws Exception {
467     assertEquals("::1:ffff:ffff", HttpUrl.parse("http://[::1:255.255.255.255]/").host());
468     assertEquals("::1:0:0", HttpUrl.parse("http://[0:0:0:0:0:1:0.0.0.0]/").host());
469   }
470 
hostIpv6WithIpv4SuffixWithOctalPrefix()471   @Test public void hostIpv6WithIpv4SuffixWithOctalPrefix() throws Exception {
472     // Chrome interprets a leading '0' as octal; Firefox rejects them. (We reject them.)
473     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.0.0.000000]/"));
474     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.010.0.010]/"));
475     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.0.0.000001]/"));
476   }
477 
hostIpv6WithIpv4SuffixWithHexadecimalPrefix()478   @Test public void hostIpv6WithIpv4SuffixWithHexadecimalPrefix() throws Exception {
479     // Chrome interprets a leading '0x' as hexadecimal; Firefox rejects them. (We reject them.)
480     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.0x10.0.0x10]/"));
481   }
482 
hostIpv6WithMalformedIpv4Suffix()483   @Test public void hostIpv6WithMalformedIpv4Suffix() throws Exception {
484     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.0:0.0]/"));
485     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:0.0-0.0]/"));
486     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:.255.255.255]/"));
487     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:255..255.255]/"));
488     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:255.255..255]/"));
489     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:0:1:255.255]/"));
490     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:256.255.255.255]/"));
491     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:ff.255.255.255]/"));
492     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:0:1:255.255.255.255]/"));
493     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:1:255.255.255.255]/"));
494     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:1:0.0.0.0:1]/"));
495     assertEquals(null, HttpUrl.parse("http://[0:0.0.0.0:1:0:0:0:0:1]/"));
496     assertEquals(null, HttpUrl.parse("http://[0.0.0.0:0:0:0:0:0:1]/"));
497   }
498 
hostIpv6WithIncompleteIpv4Suffix()499   @Test public void hostIpv6WithIncompleteIpv4Suffix() throws Exception {
500     // To Chrome & Safari these are well-formed; Firefox disagrees. (We're consistent with Firefox).
501     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:255.255.255.]/"));
502     assertEquals(null, HttpUrl.parse("http://[0:0:0:0:0:1:255.255.255]/"));
503   }
504 
hostIpv6CanonicalForm()505   @Test public void hostIpv6CanonicalForm() throws Exception {
506     assertEquals("abcd:ef01:2345:6789:abcd:ef01:2345:6789",
507         HttpUrl.parse("http://[abcd:ef01:2345:6789:abcd:ef01:2345:6789]/").host());
508     assertEquals("a::b:0:0:0", HttpUrl.parse("http://[a:0:0:0:b:0:0:0]/").host());
509     assertEquals("a:b:0:0:c::", HttpUrl.parse("http://[a:b:0:0:c:0:0:0]/").host());
510     assertEquals("a:b::c:0:0", HttpUrl.parse("http://[a:b:0:0:0:c:0:0]/").host());
511     assertEquals("a::b:0:0:0", HttpUrl.parse("http://[a:0:0:0:b:0:0:0]/").host());
512     assertEquals("::a:b:0:0:0", HttpUrl.parse("http://[0:0:0:a:b:0:0:0]/").host());
513     assertEquals("::a:0:0:0:b", HttpUrl.parse("http://[0:0:0:a:0:0:0:b]/").host());
514     assertEquals("::a:b:c:d:e:f:1", HttpUrl.parse("http://[0:a:b:c:d:e:f:1]/").host());
515     assertEquals("a:b:c:d:e:f:1::", HttpUrl.parse("http://[a:b:c:d:e:f:1:0]/").host());
516     assertEquals("ff01::101", HttpUrl.parse("http://[FF01:0:0:0:0:0:0:101]/").host());
517     assertEquals("1::", HttpUrl.parse("http://[1:0:0:0:0:0:0:0]/").host());
518     assertEquals("::1", HttpUrl.parse("http://[0:0:0:0:0:0:0:1]/").host());
519     assertEquals("::", HttpUrl.parse("http://[0:0:0:0:0:0:0:0]/").host());
520   }
521 
hostIpv4CanonicalForm()522   @Test public void hostIpv4CanonicalForm() throws Exception {
523     assertEquals("255.255.255.255", HttpUrl.parse("http://255.255.255.255/").host());
524     assertEquals("1.2.3.4", HttpUrl.parse("http://1.2.3.4/").host());
525     assertEquals("0.0.0.0", HttpUrl.parse("http://0.0.0.0/").host());
526   }
527 
528   @Ignore("java.net.IDN strips trailing trailing dots on Java 7, but not on Java 8.")
hostWithTrailingDot()529   @Test public void hostWithTrailingDot() throws Exception {
530     assertEquals("host.", HttpUrl.parse("http://host./").host());
531   }
532 
port()533   @Test public void port() throws Exception {
534     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host:80/"));
535     assertEquals(HttpUrl.parse("http://host:99/"), HttpUrl.parse("http://host:99/"));
536     assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host:/"));
537     assertEquals(65535, HttpUrl.parse("http://host:65535/").port());
538     assertEquals(null, HttpUrl.parse("http://host:0/"));
539     assertEquals(null, HttpUrl.parse("http://host:65536/"));
540     assertEquals(null, HttpUrl.parse("http://host:-1/"));
541     assertEquals(null, HttpUrl.parse("http://host:a/"));
542     assertEquals(null, HttpUrl.parse("http://host:%39%39/"));
543   }
544 
pathCharacters()545   @Test public void pathCharacters() throws Exception {
546     new UrlComponentEncodingTester()
547         .override(Encoding.PERCENT, '^', '{', '}', '|')
548         .override(Encoding.SKIP, '\\', '?', '#')
549         .skipForUri('%', '[', ']')
550         .test(Component.PATH);
551   }
552 
queryCharacters()553   @Test public void queryCharacters() throws Exception {
554     new UrlComponentEncodingTester()
555         .override(Encoding.IDENTITY, '?', '`')
556         // ANDROID-CHANGED: http://b/30405333
557         // .override(Encoding.PERCENT, '\'')
558         .override(Encoding.IDENTITY, '\'')
559         // ANDROID-CHANGED end.
560         .override(Encoding.SKIP, '#', '+')
561         .skipForUri('%', '\\', '^', '`', '{', '|', '}')
562         .test(Component.QUERY);
563   }
564 
fragmentCharacters()565   @Test public void fragmentCharacters() throws Exception {
566     new UrlComponentEncodingTester()
567         .override(Encoding.IDENTITY, ' ', '"', '#', '<', '>', '?', '`')
568         .skipForUri('%', ' ', '"', '#', '<', '>', '\\', '^', '`', '{', '|', '}')
569         .identityForNonAscii()
570         .test(Component.FRAGMENT);
571   }
572 
fragmentNonAscii()573   @Test public void fragmentNonAscii() throws Exception {
574     HttpUrl url = HttpUrl.parse("http://host/#Σ");
575     assertEquals("http://host/#Σ", url.toString());
576     assertEquals("Σ", url.fragment());
577     assertEquals("Σ", url.encodedFragment());
578     assertEquals("http://host/#Σ", url.uri().toString());
579   }
580 
fragmentNonAsciiThatOffendsJavaNetUri()581   @Test public void fragmentNonAsciiThatOffendsJavaNetUri() throws Exception {
582     HttpUrl url = HttpUrl.parse("http://host/#\u0080");
583     assertEquals("http://host/#\u0080", url.toString());
584     assertEquals("\u0080", url.fragment());
585     assertEquals("\u0080", url.encodedFragment());
586     assertEquals(new URI("http://host/#"), url.uri()); // Control characters may be stripped!
587   }
588 
fragmentPercentEncodedNonAscii()589   @Test public void fragmentPercentEncodedNonAscii() throws Exception {
590     HttpUrl url = HttpUrl.parse("http://host/#%C2%80");
591     assertEquals("http://host/#%C2%80", url.toString());
592     assertEquals("\u0080", url.fragment());
593     assertEquals("%C2%80", url.encodedFragment());
594     assertEquals("http://host/#%C2%80", url.uri().toString());
595   }
596 
fragmentPercentEncodedPartialCodePoint()597   @Test public void fragmentPercentEncodedPartialCodePoint() throws Exception {
598     HttpUrl url = HttpUrl.parse("http://host/#%80");
599     assertEquals("http://host/#%80", url.toString());
600     assertEquals("\ufffd", url.fragment()); // Unicode replacement character.
601     assertEquals("%80", url.encodedFragment());
602     assertEquals("http://host/#%80", url.uri().toString());
603   }
604 
relativePath()605   @Test public void relativePath() throws Exception {
606     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
607     assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("d/e/f"));
608     assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("../../d/e/f"));
609     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve(".."));
610     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../.."));
611     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../.."));
612     assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("."));
613     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("././.."));
614     assertEquals(HttpUrl.parse("http://host/a/b/c/"), base.resolve("c/d/../e/../"));
615     assertEquals(HttpUrl.parse("http://host/a/b/..e/"), base.resolve("..e/"));
616     assertEquals(HttpUrl.parse("http://host/a/b/e/f../"), base.resolve("e/f../"));
617     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2E."));
618     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve(".%2E"));
619     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2E%2E"));
620     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2e."));
621     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve(".%2e"));
622     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2e%2e"));
623     assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("%2E"));
624     assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("%2e"));
625   }
626 
relativePathWithTrailingSlash()627   @Test public void relativePathWithTrailingSlash() throws Exception {
628     HttpUrl base = HttpUrl.parse("http://host/a/b/c/");
629     assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve(".."));
630     assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("../"));
631     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("../.."));
632     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("../../"));
633     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../.."));
634     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../../"));
635     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../../.."));
636     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../../../"));
637     assertEquals(HttpUrl.parse("http://host/a"), base.resolve("../../../../a"));
638     assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../../../a/.."));
639     assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("../../../../a/b/.."));
640   }
641 
pathWithBackslash()642   @Test public void pathWithBackslash() throws Exception {
643     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
644     assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("d\\e\\f"));
645     assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("../..\\d\\e\\f"));
646     assertEquals(HttpUrl.parse("http://host/"), base.resolve("..\\.."));
647   }
648 
relativePathWithSameScheme()649   @Test public void relativePathWithSameScheme() throws Exception {
650     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
651     assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("http:d/e/f"));
652     assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("http:../../d/e/f"));
653   }
654 
decodeUsername()655   @Test public void decodeUsername() {
656     assertEquals("user", HttpUrl.parse("http://user@host/").username());
657     assertEquals("\uD83C\uDF69", HttpUrl.parse("http://%F0%9F%8D%A9@host/").username());
658   }
659 
decodePassword()660   @Test public void decodePassword() {
661     assertEquals("password", HttpUrl.parse("http://user:password@host/").password());
662     assertEquals("", HttpUrl.parse("http://user:@host/").password());
663     assertEquals("\uD83C\uDF69", HttpUrl.parse("http://user:%F0%9F%8D%A9@host/").password());
664   }
665 
decodeSlashCharacterInDecodedPathSegment()666   @Test public void decodeSlashCharacterInDecodedPathSegment() {
667     assertEquals(Arrays.asList("a/b/c"),
668         HttpUrl.parse("http://host/a%2Fb%2Fc").pathSegments());
669   }
670 
decodeEmptyPathSegments()671   @Test public void decodeEmptyPathSegments() {
672     assertEquals(Arrays.asList(""),
673         HttpUrl.parse("http://host/").pathSegments());
674   }
675 
percentDecode()676   @Test public void percentDecode() throws Exception {
677     assertEquals(Arrays.asList("\u0000"),
678         HttpUrl.parse("http://host/%00").pathSegments());
679     assertEquals(Arrays.asList("a", "\u2603", "c"),
680         HttpUrl.parse("http://host/a/%E2%98%83/c").pathSegments());
681     assertEquals(Arrays.asList("a", "\uD83C\uDF69", "c"),
682         HttpUrl.parse("http://host/a/%F0%9F%8D%A9/c").pathSegments());
683     assertEquals(Arrays.asList("a", "b", "c"),
684         HttpUrl.parse("http://host/a/%62/c").pathSegments());
685     assertEquals(Arrays.asList("a", "z", "c"),
686         HttpUrl.parse("http://host/a/%7A/c").pathSegments());
687     assertEquals(Arrays.asList("a", "z", "c"),
688         HttpUrl.parse("http://host/a/%7a/c").pathSegments());
689   }
690 
malformedPercentEncoding()691   @Test public void malformedPercentEncoding() {
692     assertEquals(Arrays.asList("a%f", "b"),
693         HttpUrl.parse("http://host/a%f/b").pathSegments());
694     assertEquals(Arrays.asList("%", "b"),
695         HttpUrl.parse("http://host/%/b").pathSegments());
696     assertEquals(Arrays.asList("%"),
697         HttpUrl.parse("http://host/%").pathSegments());
698   }
699 
malformedUtf8Encoding()700   @Test public void malformedUtf8Encoding() {
701     // Replace a partial UTF-8 sequence with the Unicode replacement character.
702     assertEquals(Arrays.asList("a", "\ufffdx", "c"),
703         HttpUrl.parse("http://host/a/%E2%98x/c").pathSegments());
704   }
705 
incompleteUrlComposition()706   @Test public void incompleteUrlComposition() throws Exception {
707     try {
708       new HttpUrl.Builder().scheme("http").build();
709       fail();
710     } catch (IllegalStateException expected) {
711       assertEquals("host == null", expected.getMessage());
712     }
713     try {
714       new HttpUrl.Builder().host("host").build();
715       fail();
716     } catch (IllegalStateException expected) {
717       assertEquals("scheme == null", expected.getMessage());
718     }
719   }
720 
minimalUrlComposition()721   @Test public void minimalUrlComposition() throws Exception {
722     HttpUrl url = new HttpUrl.Builder().scheme("http").host("host").build();
723     assertEquals("http://host/", url.toString());
724     assertEquals("http", url.scheme());
725     assertEquals("", url.username());
726     assertEquals("", url.password());
727     assertEquals("host", url.host());
728     assertEquals(80, url.port());
729     assertEquals("/", url.encodedPath());
730     assertEquals(null, url.query());
731     assertEquals(null, url.fragment());
732   }
733 
fullUrlComposition()734   @Test public void fullUrlComposition() throws Exception {
735     HttpUrl url = new HttpUrl.Builder()
736         .scheme("http")
737         .username("username")
738         .password("password")
739         .host("host")
740         .port(8080)
741         .addPathSegment("path")
742         .query("query")
743         .fragment("fragment")
744         .build();
745     assertEquals("http://username:password@host:8080/path?query#fragment", url.toString());
746     assertEquals("http", url.scheme());
747     assertEquals("username", url.username());
748     assertEquals("password", url.password());
749     assertEquals("host", url.host());
750     assertEquals(8080, url.port());
751     assertEquals("/path", url.encodedPath());
752     assertEquals("query", url.query());
753     assertEquals("fragment", url.fragment());
754   }
755 
changingSchemeChangesDefaultPort()756   @Test public void changingSchemeChangesDefaultPort() throws Exception {
757     assertEquals(443, HttpUrl.parse("http://example.com")
758         .newBuilder()
759         .scheme("https")
760         .build().port());
761 
762     assertEquals(80, HttpUrl.parse("https://example.com")
763         .newBuilder()
764         .scheme("http")
765         .build().port());
766 
767     assertEquals(1234, HttpUrl.parse("https://example.com:1234")
768         .newBuilder()
769         .scheme("http")
770         .build().port());
771   }
772 
composeEncodesWhitespace()773   @Test public void composeEncodesWhitespace() throws Exception {
774     HttpUrl url = new HttpUrl.Builder()
775         .scheme("http")
776         .username("a\r\n\f\t b")
777         .password("c\r\n\f\t d")
778         .host("host")
779         .addPathSegment("e\r\n\f\t f")
780         .query("g\r\n\f\t h")
781         .fragment("i\r\n\f\t j")
782         .build();
783     assertEquals("http://a%0D%0A%0C%09%20b:c%0D%0A%0C%09%20d@host"
784         + "/e%0D%0A%0C%09%20f?g%0D%0A%0C%09%20h#i%0D%0A%0C%09 j", url.toString());
785     assertEquals("a\r\n\f\t b", url.username());
786     assertEquals("c\r\n\f\t d", url.password());
787     assertEquals("e\r\n\f\t f", url.pathSegments().get(0));
788     assertEquals("g\r\n\f\t h", url.query());
789     assertEquals("i\r\n\f\t j", url.fragment());
790   }
791 
composeFromUnencodedComponents()792   @Test public void composeFromUnencodedComponents() throws Exception {
793     HttpUrl url = new HttpUrl.Builder()
794         .scheme("http")
795         .username("a:\u0001@/\\?#%b")
796         .password("c:\u0001@/\\?#%d")
797         .host("ef")
798         .port(8080)
799         .addPathSegment("g:\u0001@/\\?#%h")
800         .query("i:\u0001@/\\?#%j")
801         .fragment("k:\u0001@/\\?#%l")
802         .build();
803     assertEquals("http://a%3A%01%40%2F%5C%3F%23%25b:c%3A%01%40%2F%5C%3F%23%25d@ef:8080/"
804         + "g:%01@%2F%5C%3F%23%25h?i:%01@/\\?%23%25j#k:%01@/\\?#%25l", url.toString());
805     assertEquals("http", url.scheme());
806     assertEquals("a:\u0001@/\\?#%b", url.username());
807     assertEquals("c:\u0001@/\\?#%d", url.password());
808     assertEquals(Arrays.asList("g:\u0001@/\\?#%h"), url.pathSegments());
809     assertEquals("i:\u0001@/\\?#%j", url.query());
810     assertEquals("k:\u0001@/\\?#%l", url.fragment());
811     assertEquals("a%3A%01%40%2F%5C%3F%23%25b", url.encodedUsername());
812     assertEquals("c%3A%01%40%2F%5C%3F%23%25d", url.encodedPassword());
813     assertEquals("/g:%01@%2F%5C%3F%23%25h", url.encodedPath());
814     assertEquals("i:%01@/\\?%23%25j", url.encodedQuery());
815     assertEquals("k:%01@/\\?#%25l", url.encodedFragment());
816   }
817 
composeFromEncodedComponents()818   @Test public void composeFromEncodedComponents() throws Exception {
819     HttpUrl url = new HttpUrl.Builder()
820         .scheme("http")
821         .encodedUsername("a:\u0001@/\\?#%25b")
822         .encodedPassword("c:\u0001@/\\?#%25d")
823         .host("ef")
824         .port(8080)
825         .addEncodedPathSegment("g:\u0001@/\\?#%25h")
826         .encodedQuery("i:\u0001@/\\?#%25j")
827         .encodedFragment("k:\u0001@/\\?#%25l")
828         .build();
829     assertEquals("http://a%3A%01%40%2F%5C%3F%23%25b:c%3A%01%40%2F%5C%3F%23%25d@ef:8080/"
830         + "g:%01@%2F%5C%3F%23%25h?i:%01@/\\?%23%25j#k:%01@/\\?#%25l", url.toString());
831     assertEquals("http", url.scheme());
832     assertEquals("a:\u0001@/\\?#%b", url.username());
833     assertEquals("c:\u0001@/\\?#%d", url.password());
834     assertEquals(Arrays.asList("g:\u0001@/\\?#%h"), url.pathSegments());
835     assertEquals("i:\u0001@/\\?#%j", url.query());
836     assertEquals("k:\u0001@/\\?#%l", url.fragment());
837     assertEquals("a%3A%01%40%2F%5C%3F%23%25b", url.encodedUsername());
838     assertEquals("c%3A%01%40%2F%5C%3F%23%25d", url.encodedPassword());
839     assertEquals("/g:%01@%2F%5C%3F%23%25h", url.encodedPath());
840     assertEquals("i:%01@/\\?%23%25j", url.encodedQuery());
841     assertEquals("k:%01@/\\?#%25l", url.encodedFragment());
842   }
843 
composeWithEncodedPath()844   @Test public void composeWithEncodedPath() throws Exception {
845     HttpUrl url = new HttpUrl.Builder()
846         .scheme("http")
847         .host("host")
848         .encodedPath("/a%2Fb/c")
849         .build();
850     assertEquals("http://host/a%2Fb/c", url.toString());
851     assertEquals("/a%2Fb/c", url.encodedPath());
852     assertEquals(Arrays.asList("a/b", "c"), url.pathSegments());
853   }
854 
composeMixingPathSegments()855   @Test public void composeMixingPathSegments() throws Exception {
856     HttpUrl url = new HttpUrl.Builder()
857         .scheme("http")
858         .host("host")
859         .encodedPath("/a%2fb/c")
860         .addPathSegment("d%25e")
861         .addEncodedPathSegment("f%25g")
862         .build();
863     assertEquals("http://host/a%2fb/c/d%2525e/f%25g", url.toString());
864     assertEquals("/a%2fb/c/d%2525e/f%25g", url.encodedPath());
865     assertEquals(Arrays.asList("a%2fb", "c", "d%2525e", "f%25g"), url.encodedPathSegments());
866     assertEquals(Arrays.asList("a/b", "c", "d%25e", "f%g"), url.pathSegments());
867   }
868 
composeWithAddSegment()869   @Test public void composeWithAddSegment() throws Exception {
870     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
871     assertEquals("/a/b/c/", base.newBuilder().addPathSegment("").build().encodedPath());
872     assertEquals("/a/b/c/d",
873         base.newBuilder().addPathSegment("").addPathSegment("d").build().encodedPath());
874     assertEquals("/a/b/", base.newBuilder().addPathSegment("..").build().encodedPath());
875     assertEquals("/a/b/", base.newBuilder().addPathSegment("").addPathSegment("..").build()
876         .encodedPath());
877     assertEquals("/a/b/c/", base.newBuilder().addPathSegment("").addPathSegment("").build()
878         .encodedPath());
879   }
880 
pathSize()881   @Test public void pathSize() throws Exception {
882     assertEquals(1, HttpUrl.parse("http://host/").pathSize());
883     assertEquals(3, HttpUrl.parse("http://host/a/b/c").pathSize());
884   }
885 
addPathSegmentDotDoesNothing()886   @Test public void addPathSegmentDotDoesNothing() throws Exception {
887     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
888     assertEquals("/a/b/c", base.newBuilder().addPathSegment(".").build().encodedPath());
889   }
890 
addPathSegmentEncodes()891   @Test public void addPathSegmentEncodes() throws Exception {
892     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
893     assertEquals("/a/b/c/%252e",
894         base.newBuilder().addPathSegment("%2e").build().encodedPath());
895     assertEquals("/a/b/c/%252e%252e",
896         base.newBuilder().addPathSegment("%2e%2e").build().encodedPath());
897   }
898 
addPathSegmentDotDotPopsDirectory()899   @Test public void addPathSegmentDotDotPopsDirectory() throws Exception {
900     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
901     assertEquals("/a/b/", base.newBuilder().addPathSegment("..").build().encodedPath());
902   }
903 
addPathSegmentDotAndIgnoredCharacter()904   @Test public void addPathSegmentDotAndIgnoredCharacter() throws Exception {
905     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
906     assertEquals("/a/b/c/.%0A", base.newBuilder().addPathSegment(".\n").build().encodedPath());
907   }
908 
addEncodedPathSegmentDotAndIgnoredCharacter()909   @Test public void addEncodedPathSegmentDotAndIgnoredCharacter() throws Exception {
910     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
911     assertEquals("/a/b/c", base.newBuilder().addEncodedPathSegment(".\n").build().encodedPath());
912   }
913 
addEncodedPathSegmentDotDotAndIgnoredCharacter()914   @Test public void addEncodedPathSegmentDotDotAndIgnoredCharacter() throws Exception {
915     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
916     assertEquals("/a/b/", base.newBuilder().addEncodedPathSegment("..\n").build().encodedPath());
917   }
918 
setPathSegment()919   @Test public void setPathSegment() throws Exception {
920     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
921     assertEquals("/d/b/c", base.newBuilder().setPathSegment(0, "d").build().encodedPath());
922     assertEquals("/a/d/c", base.newBuilder().setPathSegment(1, "d").build().encodedPath());
923     assertEquals("/a/b/d", base.newBuilder().setPathSegment(2, "d").build().encodedPath());
924   }
925 
setPathSegmentEncodes()926   @Test public void setPathSegmentEncodes() throws Exception {
927     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
928     assertEquals("/%2525/b/c", base.newBuilder().setPathSegment(0, "%25").build().encodedPath());
929     assertEquals("/.%0A/b/c", base.newBuilder().setPathSegment(0, ".\n").build().encodedPath());
930     assertEquals("/%252e/b/c", base.newBuilder().setPathSegment(0, "%2e").build().encodedPath());
931   }
932 
setPathSegmentAcceptsEmpty()933   @Test public void setPathSegmentAcceptsEmpty() throws Exception {
934     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
935     assertEquals("//b/c", base.newBuilder().setPathSegment(0, "").build().encodedPath());
936     assertEquals("/a/b/", base.newBuilder().setPathSegment(2, "").build().encodedPath());
937   }
938 
setPathSegmentRejectsDot()939   @Test public void setPathSegmentRejectsDot() throws Exception {
940     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
941     try {
942       base.newBuilder().setPathSegment(0, ".");
943       fail();
944     } catch (IllegalArgumentException expected) {
945     }
946   }
947 
setPathSegmentRejectsDotDot()948   @Test public void setPathSegmentRejectsDotDot() throws Exception {
949     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
950     try {
951       base.newBuilder().setPathSegment(0, "..");
952       fail();
953     } catch (IllegalArgumentException expected) {
954     }
955   }
956 
setPathSegmentWithSlash()957   @Test public void setPathSegmentWithSlash() throws Exception {
958     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
959     HttpUrl url = base.newBuilder().setPathSegment(1, "/").build();
960     assertEquals("/a/%2F/c", url.encodedPath());
961   }
962 
setPathSegmentOutOfBounds()963   @Test public void setPathSegmentOutOfBounds() throws Exception {
964     try {
965       new HttpUrl.Builder().setPathSegment(1, "a");
966       fail();
967     } catch (IndexOutOfBoundsException expected) {
968     }
969   }
970 
setEncodedPathSegmentEncodes()971   @Test public void setEncodedPathSegmentEncodes() throws Exception {
972     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
973     assertEquals("/%25/b/c",
974         base.newBuilder().setEncodedPathSegment(0, "%25").build().encodedPath());
975   }
976 
setEncodedPathSegmentRejectsDot()977   @Test public void setEncodedPathSegmentRejectsDot() throws Exception {
978     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
979     try {
980       base.newBuilder().setEncodedPathSegment(0, ".");
981       fail();
982     } catch (IllegalArgumentException expected) {
983     }
984   }
985 
setEncodedPathSegmentRejectsDotAndIgnoredCharacter()986   @Test public void setEncodedPathSegmentRejectsDotAndIgnoredCharacter() throws Exception {
987     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
988     try {
989       base.newBuilder().setEncodedPathSegment(0, ".\n");
990       fail();
991     } catch (IllegalArgumentException expected) {
992     }
993   }
994 
setEncodedPathSegmentRejectsDotDot()995   @Test public void setEncodedPathSegmentRejectsDotDot() throws Exception {
996     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
997     try {
998       base.newBuilder().setEncodedPathSegment(0, "..");
999       fail();
1000     } catch (IllegalArgumentException expected) {
1001     }
1002   }
1003 
setEncodedPathSegmentRejectsDotDotAndIgnoredCharacter()1004   @Test public void setEncodedPathSegmentRejectsDotDotAndIgnoredCharacter() throws Exception {
1005     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
1006     try {
1007       base.newBuilder().setEncodedPathSegment(0, "..\n");
1008       fail();
1009     } catch (IllegalArgumentException expected) {
1010     }
1011   }
1012 
setEncodedPathSegmentWithSlash()1013   @Test public void setEncodedPathSegmentWithSlash() throws Exception {
1014     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
1015     HttpUrl url = base.newBuilder().setEncodedPathSegment(1, "/").build();
1016     assertEquals("/a/%2F/c", url.encodedPath());
1017   }
1018 
setEncodedPathSegmentOutOfBounds()1019   @Test public void setEncodedPathSegmentOutOfBounds() throws Exception {
1020     try {
1021       new HttpUrl.Builder().setEncodedPathSegment(1, "a");
1022       fail();
1023     } catch (IndexOutOfBoundsException expected) {
1024     }
1025   }
1026 
removePathSegment()1027   @Test public void removePathSegment() throws Exception {
1028     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
1029     HttpUrl url = base.newBuilder()
1030         .removePathSegment(0)
1031         .build();
1032     assertEquals("/b/c", url.encodedPath());
1033   }
1034 
removePathSegmentDoesntRemovePath()1035   @Test public void removePathSegmentDoesntRemovePath() throws Exception {
1036     HttpUrl base = HttpUrl.parse("http://host/a/b/c");
1037     HttpUrl url = base.newBuilder()
1038         .removePathSegment(0)
1039         .removePathSegment(0)
1040         .removePathSegment(0)
1041         .build();
1042     // ANDROID-BEGIN: http://b/29983827 Behavior changed. Test name is now incorrect.
1043     // assertEquals(Arrays.asList(""), url.pathSegments());
1044     // assertEquals("/", url.encodedPath());
1045     assertEquals(Collections.emptyList(), url.pathSegments());
1046     assertEquals("http://host", url.toString());
1047     // ANDROID-END: http://b/29983827
1048   }
1049 
removePathSegmentOutOfBounds()1050   @Test public void removePathSegmentOutOfBounds() throws Exception {
1051     try {
1052       new HttpUrl.Builder().removePathSegment(1);
1053       fail();
1054     } catch (IndexOutOfBoundsException expected) {
1055     }
1056   }
1057 
toJavaNetUrl()1058   @Test public void toJavaNetUrl() throws Exception {
1059     HttpUrl httpUrl = HttpUrl.parse("http://username:password@host/path?query#fragment");
1060     URL javaNetUrl = httpUrl.url();
1061     assertEquals("http://username:password@host/path?query#fragment", javaNetUrl.toString());
1062   }
1063 
toUri()1064   @Test public void toUri() throws Exception {
1065     HttpUrl httpUrl = HttpUrl.parse("http://username:password@host/path?query#fragment");
1066     URI uri = httpUrl.uri();
1067     assertEquals("http://username:password@host/path?query#fragment", uri.toString());
1068   }
1069 
toUriSpecialQueryCharacters()1070   @Test public void toUriSpecialQueryCharacters() throws Exception {
1071     HttpUrl httpUrl = HttpUrl.parse("http://host/?d=abc!@[]^`{}|\\");
1072     URI uri = httpUrl.uri();
1073     assertEquals("http://host/?d=abc!@[]%5E%60%7B%7D%7C%5C", uri.toString());
1074   }
1075 
toUriWithUsernameNoPassword()1076   @Test public void toUriWithUsernameNoPassword() throws Exception {
1077     HttpUrl httpUrl = new HttpUrl.Builder()
1078         .scheme("http")
1079         .username("user")
1080         .host("host")
1081         .build();
1082     assertEquals("http://user@host/", httpUrl.toString());
1083     assertEquals("http://user@host/", httpUrl.uri().toString());
1084   }
1085 
toUriUsernameSpecialCharacters()1086   @Test public void toUriUsernameSpecialCharacters() throws Exception {
1087     HttpUrl url = new HttpUrl.Builder()
1088         .scheme("http")
1089         .host("host")
1090         .username("=[]:;\"~|?#@^/$%*")
1091         .build();
1092     assertEquals("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.toString());
1093     assertEquals("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.uri().toString());
1094   }
1095 
toUriPasswordSpecialCharacters()1096   @Test public void toUriPasswordSpecialCharacters() throws Exception {
1097     HttpUrl url = new HttpUrl.Builder()
1098         .scheme("http")
1099         .host("host")
1100         .username("user")
1101         .password("=[]:;\"~|?#@^/$%*")
1102         .build();
1103     assertEquals("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/", url.toString());
1104     assertEquals("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/",
1105         url.uri().toString());
1106   }
1107 
toUriPathSpecialCharacters()1108   @Test public void toUriPathSpecialCharacters() throws Exception {
1109     HttpUrl url = new HttpUrl.Builder()
1110         .scheme("http")
1111         .host("host")
1112         .addPathSegment("=[]:;\"~|?#@^/$%*")
1113         .build();
1114     assertEquals("http://host/=[]:;%22~%7C%3F%23@%5E%2F$%25*", url.toString());
1115     assertEquals("http://host/=%5B%5D:;%22~%7C%3F%23@%5E%2F$%25*", url.uri().toString());
1116   }
1117 
toUriQueryParameterNameSpecialCharacters()1118   @Test public void toUriQueryParameterNameSpecialCharacters() throws Exception {
1119     HttpUrl url = new HttpUrl.Builder()
1120         .scheme("http")
1121         .host("host")
1122         .addQueryParameter("=[]:;\"~|?#@^/$%*", "a")
1123         .build();
1124     assertEquals("http://host/?%3D[]:;%22~|?%23@^/$%25*=a", url.toString());
1125     assertEquals("http://host/?%3D[]:;%22~%7C?%23@%5E/$%25*=a", url.uri().toString());
1126   }
1127 
toUriQueryParameterValueSpecialCharacters()1128   @Test public void toUriQueryParameterValueSpecialCharacters() throws Exception {
1129     HttpUrl url = new HttpUrl.Builder()
1130         .scheme("http")
1131         .host("host")
1132         .addQueryParameter("a", "=[]:;\"~|?#@^/$%*")
1133         .build();
1134     assertEquals("http://host/?a=%3D[]:;%22~|?%23@^/$%25*", url.toString());
1135     assertEquals("http://host/?a=%3D[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
1136   }
1137 
toUriQueryValueSpecialCharacters()1138   @Test public void toUriQueryValueSpecialCharacters() throws Exception {
1139     HttpUrl url = new HttpUrl.Builder()
1140         .scheme("http")
1141         .host("host")
1142         .query("=[]:;\"~|?#@^/$%*")
1143         .build();
1144     assertEquals("http://host/?=[]:;%22~|?%23@^/$%25*", url.toString());
1145     assertEquals("http://host/?=[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
1146   }
1147 
toUriFragmentSpecialCharacters()1148   @Test public void toUriFragmentSpecialCharacters() throws Exception {
1149     HttpUrl url = new HttpUrl.Builder()
1150         .scheme("http")
1151         .host("host")
1152         .fragment("=[]:;\"~|?#@^/$%*")
1153         .build();
1154     assertEquals("http://host/#=[]:;\"~|?#@^/$%25*", url.toString());
1155     assertEquals("http://host/#=[]:;%22~%7C?%23@%5E/$%25*", url.uri().toString());
1156   }
1157 
toUriWithControlCharacters()1158   @Test public void toUriWithControlCharacters() throws Exception {
1159     // Percent-encoded in the path.
1160     assertEquals(new URI("http://host/a%00b"), HttpUrl.parse("http://host/a\u0000b").uri());
1161     assertEquals(new URI("http://host/a%C2%80b"), HttpUrl.parse("http://host/a\u0080b").uri());
1162     assertEquals(new URI("http://host/a%C2%9Fb"), HttpUrl.parse("http://host/a\u009fb").uri());
1163     // Percent-encoded in the query.
1164     assertEquals(new URI("http://host/?a%00b"), HttpUrl.parse("http://host/?a\u0000b").uri());
1165     assertEquals(new URI("http://host/?a%C2%80b"), HttpUrl.parse("http://host/?a\u0080b").uri());
1166     assertEquals(new URI("http://host/?a%C2%9Fb"), HttpUrl.parse("http://host/?a\u009fb").uri());
1167     // Stripped from the fragment.
1168     assertEquals(new URI("http://host/#a%00b"), HttpUrl.parse("http://host/#a\u0000b").uri());
1169     assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u0080b").uri());
1170     assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u009fb").uri());
1171   }
1172 
toUriWithSpaceCharacters()1173   @Test public void toUriWithSpaceCharacters() throws Exception {
1174     // Percent-encoded in the path.
1175     assertEquals(new URI("http://host/a%0Bb"), HttpUrl.parse("http://host/a\u000bb").uri());
1176     assertEquals(new URI("http://host/a%20b"), HttpUrl.parse("http://host/a b").uri());
1177     assertEquals(new URI("http://host/a%E2%80%89b"), HttpUrl.parse("http://host/a\u2009b").uri());
1178     assertEquals(new URI("http://host/a%E3%80%80b"), HttpUrl.parse("http://host/a\u3000b").uri());
1179     // Percent-encoded in the query.
1180     assertEquals(new URI("http://host/?a%0Bb"), HttpUrl.parse("http://host/?a\u000bb").uri());
1181     assertEquals(new URI("http://host/?a%20b"), HttpUrl.parse("http://host/?a b").uri());
1182     assertEquals(new URI("http://host/?a%E2%80%89b"), HttpUrl.parse("http://host/?a\u2009b").uri());
1183     assertEquals(new URI("http://host/?a%E3%80%80b"), HttpUrl.parse("http://host/?a\u3000b").uri());
1184     // Stripped from the fragment.
1185     assertEquals(new URI("http://host/#a%0Bb"), HttpUrl.parse("http://host/#a\u000bb").uri());
1186     assertEquals(new URI("http://host/#a%20b"), HttpUrl.parse("http://host/#a b").uri());
1187     assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u2009b").uri());
1188     assertEquals(new URI("http://host/#ab"), HttpUrl.parse("http://host/#a\u3000b").uri());
1189   }
1190 
toUriWithNonHexPercentEscape()1191   @Test public void toUriWithNonHexPercentEscape() throws Exception {
1192     assertEquals(new URI("http://host/%25xx"), HttpUrl.parse("http://host/%xx").uri());
1193   }
1194 
toUriWithTruncatedPercentEscape()1195   @Test public void toUriWithTruncatedPercentEscape() throws Exception {
1196     assertEquals(new URI("http://host/%25a"), HttpUrl.parse("http://host/%a").uri());
1197     assertEquals(new URI("http://host/%25"), HttpUrl.parse("http://host/%").uri());
1198   }
1199 
fromJavaNetUrl()1200   @Test public void fromJavaNetUrl() throws Exception {
1201     URL javaNetUrl = new URL("http://username:password@host/path?query#fragment");
1202     HttpUrl httpUrl = HttpUrl.get(javaNetUrl);
1203     assertEquals("http://username:password@host/path?query#fragment", httpUrl.toString());
1204   }
1205 
1206   // ANDROID-BEGIN
1207   @Ignore // Android's URL implementation does not support mailto:
1208   // ANDROID-END
fromJavaNetUrlUnsupportedScheme()1209   @Test public void fromJavaNetUrlUnsupportedScheme() throws Exception {
1210     URL javaNetUrl = new URL("mailto:user@example.com");
1211     assertEquals(null, HttpUrl.get(javaNetUrl));
1212   }
1213 
fromUri()1214   @Test public void fromUri() throws Exception {
1215     URI uri = new URI("http://username:password@host/path?query#fragment");
1216     HttpUrl httpUrl = HttpUrl.get(uri);
1217     assertEquals("http://username:password@host/path?query#fragment", httpUrl.toString());
1218   }
1219 
fromUriUnsupportedScheme()1220   @Test public void fromUriUnsupportedScheme() throws Exception {
1221     URI uri = new URI("mailto:user@example.com");
1222     assertEquals(null, HttpUrl.get(uri));
1223   }
1224 
fromUriPartial()1225   @Test public void fromUriPartial() throws Exception {
1226     URI uri = new URI("/path");
1227     assertEquals(null, HttpUrl.get(uri));
1228   }
1229 
fromJavaNetUrl_checked()1230   @Test public void fromJavaNetUrl_checked() throws Exception {
1231     HttpUrl httpUrl = HttpUrl.getChecked("http://username:password@host/path?query#fragment");
1232     assertEquals("http://username:password@host/path?query#fragment", httpUrl.toString());
1233   }
1234 
fromJavaNetUrlUnsupportedScheme_checked()1235   @Test public void fromJavaNetUrlUnsupportedScheme_checked() throws Exception {
1236     try {
1237       HttpUrl.getChecked("mailto:user@example.com");
1238       fail();
1239     } catch (MalformedURLException e) {
1240     }
1241   }
1242 
fromJavaNetUrlBadHost_checked()1243   @Test public void fromJavaNetUrlBadHost_checked() throws Exception {
1244     try {
1245       HttpUrl.getChecked("http://hostw ithspace/");
1246       fail();
1247     } catch (UnknownHostException expected) {
1248     }
1249   }
1250 
composeQueryWithComponents()1251   @Test public void composeQueryWithComponents() throws Exception {
1252     HttpUrl base = HttpUrl.parse("http://host/");
1253     HttpUrl url = base.newBuilder().addQueryParameter("a+=& b", "c+=& d").build();
1254     assertEquals("http://host/?a%2B%3D%26%20b=c%2B%3D%26%20d", url.toString());
1255     assertEquals("c+=& d", url.queryParameterValue(0));
1256     assertEquals("a+=& b", url.queryParameterName(0));
1257     assertEquals("c+=& d", url.queryParameter("a+=& b"));
1258     assertEquals(Collections.singleton("a+=& b"), url.queryParameterNames());
1259     assertEquals(singletonList("c+=& d"), url.queryParameterValues("a+=& b"));
1260     assertEquals(1, url.querySize());
1261     assertEquals("a+=& b=c+=& d", url.query()); // Ambiguous! (Though working as designed.)
1262     assertEquals("a%2B%3D%26%20b=c%2B%3D%26%20d", url.encodedQuery());
1263   }
1264 
composeQueryWithEncodedComponents()1265   @Test public void composeQueryWithEncodedComponents() throws Exception {
1266     HttpUrl base = HttpUrl.parse("http://host/");
1267     HttpUrl url = base.newBuilder().addEncodedQueryParameter("a+=& b", "c+=& d").build();
1268     assertEquals("http://host/?a+%3D%26%20b=c+%3D%26%20d", url.toString());
1269     assertEquals("c =& d", url.queryParameter("a =& b"));
1270   }
1271 
composeQueryRemoveQueryParameter()1272   @Test public void composeQueryRemoveQueryParameter() throws Exception {
1273     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1274         .addQueryParameter("a+=& b", "c+=& d")
1275         .removeAllQueryParameters("a+=& b")
1276         .build();
1277     assertEquals("http://host/", url.toString());
1278     assertEquals(null, url.queryParameter("a+=& b"));
1279   }
1280 
composeQueryRemoveEncodedQueryParameter()1281   @Test public void composeQueryRemoveEncodedQueryParameter() throws Exception {
1282     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1283         .addEncodedQueryParameter("a+=& b", "c+=& d")
1284         .removeAllEncodedQueryParameters("a+=& b")
1285         .build();
1286     assertEquals("http://host/", url.toString());
1287     assertEquals(null, url.queryParameter("a =& b"));
1288   }
1289 
composeQuerySetQueryParameter()1290   @Test public void composeQuerySetQueryParameter() throws Exception {
1291     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1292         .addQueryParameter("a+=& b", "c+=& d")
1293         .setQueryParameter("a+=& b", "ef")
1294         .build();
1295     assertEquals("http://host/?a%2B%3D%26%20b=ef", url.toString());
1296     assertEquals("ef", url.queryParameter("a+=& b"));
1297   }
1298 
composeQuerySetEncodedQueryParameter()1299   @Test public void composeQuerySetEncodedQueryParameter() throws Exception {
1300     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1301         .addEncodedQueryParameter("a+=& b", "c+=& d")
1302         .setEncodedQueryParameter("a+=& b", "ef")
1303         .build();
1304     assertEquals("http://host/?a+%3D%26%20b=ef", url.toString());
1305     assertEquals("ef", url.queryParameter("a =& b"));
1306   }
1307 
composeQueryMultipleEncodedValuesForParameter()1308   @Test public void composeQueryMultipleEncodedValuesForParameter() throws Exception {
1309     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1310         .addQueryParameter("a+=& b", "c+=& d")
1311         .addQueryParameter("a+=& b", "e+=& f")
1312         .build();
1313     assertEquals("http://host/?a%2B%3D%26%20b=c%2B%3D%26%20d&a%2B%3D%26%20b=e%2B%3D%26%20f",
1314         url.toString());
1315     assertEquals(2, url.querySize());
1316     assertEquals(Collections.singleton("a+=& b"), url.queryParameterNames());
1317     assertEquals(Arrays.asList("c+=& d", "e+=& f"), url.queryParameterValues("a+=& b"));
1318   }
1319 
absentQueryIsZeroNameValuePairs()1320   @Test public void absentQueryIsZeroNameValuePairs() throws Exception {
1321     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1322         .query(null)
1323         .build();
1324     assertEquals(0, url.querySize());
1325   }
1326 
emptyQueryIsSingleNameValuePairWithEmptyKey()1327   @Test public void emptyQueryIsSingleNameValuePairWithEmptyKey() throws Exception {
1328     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1329         .query("")
1330         .build();
1331     assertEquals(1, url.querySize());
1332     assertEquals("", url.queryParameterName(0));
1333     assertEquals(null, url.queryParameterValue(0));
1334   }
1335 
ampersandQueryIsTwoNameValuePairsWithEmptyKeys()1336   @Test public void ampersandQueryIsTwoNameValuePairsWithEmptyKeys() throws Exception {
1337     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1338         .query("&")
1339         .build();
1340     assertEquals(2, url.querySize());
1341     assertEquals("", url.queryParameterName(0));
1342     assertEquals(null, url.queryParameterValue(0));
1343     assertEquals("", url.queryParameterName(1));
1344     assertEquals(null, url.queryParameterValue(1));
1345   }
1346 
removeAllDoesNotRemoveQueryIfNoParametersWereRemoved()1347   @Test public void removeAllDoesNotRemoveQueryIfNoParametersWereRemoved() throws Exception {
1348     HttpUrl url = HttpUrl.parse("http://host/").newBuilder()
1349         .query("")
1350         .removeAllQueryParameters("a")
1351         .build();
1352     assertEquals("http://host/?", url.toString());
1353   }
1354 
queryParametersWithoutValues()1355   @Test public void queryParametersWithoutValues() throws Exception {
1356     HttpUrl url = HttpUrl.parse("http://host/?foo&bar&baz");
1357     assertEquals(3, url.querySize());
1358     assertEquals(new LinkedHashSet<>(Arrays.asList("foo", "bar", "baz")),
1359         url.queryParameterNames());
1360     assertEquals(null, url.queryParameterValue(0));
1361     assertEquals(null, url.queryParameterValue(1));
1362     assertEquals(null, url.queryParameterValue(2));
1363     assertEquals(singletonList((String) null), url.queryParameterValues("foo"));
1364     assertEquals(singletonList((String) null), url.queryParameterValues("bar"));
1365     assertEquals(singletonList((String) null), url.queryParameterValues("baz"));
1366   }
1367 
queryParametersWithEmptyValues()1368   @Test public void queryParametersWithEmptyValues() throws Exception {
1369     HttpUrl url = HttpUrl.parse("http://host/?foo=&bar=&baz=");
1370     assertEquals(3, url.querySize());
1371     assertEquals(new LinkedHashSet<>(Arrays.asList("foo", "bar", "baz")),
1372         url.queryParameterNames());
1373     assertEquals("", url.queryParameterValue(0));
1374     assertEquals("", url.queryParameterValue(1));
1375     assertEquals("", url.queryParameterValue(2));
1376     assertEquals(singletonList(""), url.queryParameterValues("foo"));
1377     assertEquals(singletonList(""), url.queryParameterValues("bar"));
1378     assertEquals(singletonList(""), url.queryParameterValues("baz"));
1379   }
1380 
queryParametersWithRepeatedName()1381   @Test public void queryParametersWithRepeatedName() throws Exception {
1382     HttpUrl url = HttpUrl.parse("http://host/?foo[]=1&foo[]=2&foo[]=3");
1383     assertEquals(3, url.querySize());
1384     assertEquals(Collections.singleton("foo[]"), url.queryParameterNames());
1385     assertEquals("1", url.queryParameterValue(0));
1386     assertEquals("2", url.queryParameterValue(1));
1387     assertEquals("3", url.queryParameterValue(2));
1388     assertEquals(Arrays.asList("1", "2", "3"), url.queryParameterValues("foo[]"));
1389   }
1390 
queryParameterLookupWithNonCanonicalEncoding()1391   @Test public void queryParameterLookupWithNonCanonicalEncoding() throws Exception {
1392     HttpUrl url = HttpUrl.parse("http://host/?%6d=m&+=%20");
1393     assertEquals("m", url.queryParameterName(0));
1394     assertEquals(" ", url.queryParameterName(1));
1395     assertEquals("m", url.queryParameter("m"));
1396     assertEquals(" ", url.queryParameter(" "));
1397   }
1398 
roundTripBuilder()1399   @Test public void roundTripBuilder() throws Exception {
1400     HttpUrl url = new HttpUrl.Builder()
1401         .scheme("http")
1402         .username("%")
1403         .password("%")
1404         .host("host")
1405         .addPathSegment("%")
1406         .query("%")
1407         .fragment("%")
1408         .build();
1409     assertEquals("http://%25:%25@host/%25?%25#%25", url.toString());
1410     assertEquals("http://%25:%25@host/%25?%25#%25", url.newBuilder().build().toString());
1411     assertEquals("http://%25:%25@host/%25?%25", url.resolve("").toString());
1412   }
1413 
1414   /**
1415    * Although HttpUrl prefers percent-encodings in uppercase, it should preserve the exact
1416    * structure of the original encoding.
1417    */
rawEncodingRetained()1418   @Test public void rawEncodingRetained() throws Exception {
1419     String urlString = "http://%6d%6D:%6d%6D@host/%6d%6D?%6d%6D#%6d%6D";
1420     HttpUrl url = HttpUrl.parse(urlString);
1421     assertEquals("%6d%6D", url.encodedUsername());
1422     assertEquals("%6d%6D", url.encodedPassword());
1423     assertEquals("/%6d%6D", url.encodedPath());
1424     assertEquals(Arrays.asList("%6d%6D"), url.encodedPathSegments());
1425     assertEquals("%6d%6D", url.encodedQuery());
1426     assertEquals("%6d%6D", url.encodedFragment());
1427     assertEquals(urlString, url.toString());
1428     assertEquals(urlString, url.newBuilder().build().toString());
1429     assertEquals("http://%6d%6D:%6d%6D@host/%6d%6D?%6d%6D", url.resolve("").toString());
1430   }
1431 
clearFragment()1432   @Test public void clearFragment() throws Exception {
1433     HttpUrl url = HttpUrl.parse("http://host/#fragment")
1434         .newBuilder()
1435         .fragment(null)
1436         .build();
1437     assertEquals("http://host/", url.toString());
1438     assertEquals(null, url.fragment());
1439     assertEquals(null, url.encodedFragment());
1440   }
1441 
clearEncodedFragment()1442   @Test public void clearEncodedFragment() throws Exception {
1443     HttpUrl url = HttpUrl.parse("http://host/#fragment")
1444         .newBuilder()
1445         .encodedFragment(null)
1446         .build();
1447     assertEquals("http://host/", url.toString());
1448     assertEquals(null, url.fragment());
1449     assertEquals(null, url.encodedFragment());
1450   }
1451 }
1452