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