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.util.Arrays; 21import org.junit.Test; 22 23import static org.junit.Assert.assertEquals; 24 25public final class HttpUrlTest { 26 @Test public void parseTrimsAsciiWhitespace() throws Exception { 27 HttpUrl expected = HttpUrl.parse("http://host/"); 28 assertEquals(expected, HttpUrl.parse("http://host/\f\n\t \r")); // Leading. 29 assertEquals(expected, HttpUrl.parse("\r\n\f \thttp://host/")); // Trailing. 30 assertEquals(expected, HttpUrl.parse(" http://host/ ")); // Both. 31 assertEquals(expected, HttpUrl.parse(" http://host/ ")); // Both. 32 assertEquals(expected, HttpUrl.parse("http://host/").resolve(" ")); 33 assertEquals(expected, HttpUrl.parse("http://host/").resolve(" . ")); 34 } 35 36 @Test public void parseDoesNotTrimOtherWhitespaceCharacters() throws Exception { 37 // Whitespace characters list from Google's Guava team: http://goo.gl/IcR9RD 38 assertEquals("/%0B", HttpUrl.parse("http://h/\u000b").path()); // line tabulation 39 assertEquals("/%1C", HttpUrl.parse("http://h/\u001c").path()); // information separator 4 40 assertEquals("/%1D", HttpUrl.parse("http://h/\u001d").path()); // information separator 3 41 assertEquals("/%1E", HttpUrl.parse("http://h/\u001e").path()); // information separator 2 42 assertEquals("/%1F", HttpUrl.parse("http://h/\u001f").path()); // information separator 1 43 assertEquals("/%C2%85", HttpUrl.parse("http://h/\u0085").path()); // next line 44 assertEquals("/%C2%A0", HttpUrl.parse("http://h/\u00a0").path()); // non-breaking space 45 assertEquals("/%E1%9A%80", HttpUrl.parse("http://h/\u1680").path()); // ogham space mark 46 assertEquals("/%E1%A0%8E", HttpUrl.parse("http://h/\u180e").path()); // mongolian vowel separator 47 assertEquals("/%E2%80%80", HttpUrl.parse("http://h/\u2000").path()); // en quad 48 assertEquals("/%E2%80%81", HttpUrl.parse("http://h/\u2001").path()); // em quad 49 assertEquals("/%E2%80%82", HttpUrl.parse("http://h/\u2002").path()); // en space 50 assertEquals("/%E2%80%83", HttpUrl.parse("http://h/\u2003").path()); // em space 51 assertEquals("/%E2%80%84", HttpUrl.parse("http://h/\u2004").path()); // three-per-em space 52 assertEquals("/%E2%80%85", HttpUrl.parse("http://h/\u2005").path()); // four-per-em space 53 assertEquals("/%E2%80%86", HttpUrl.parse("http://h/\u2006").path()); // six-per-em space 54 assertEquals("/%E2%80%87", HttpUrl.parse("http://h/\u2007").path()); // figure space 55 assertEquals("/%E2%80%88", HttpUrl.parse("http://h/\u2008").path()); // punctuation space 56 assertEquals("/%E2%80%89", HttpUrl.parse("http://h/\u2009").path()); // thin space 57 assertEquals("/%E2%80%8A", HttpUrl.parse("http://h/\u200a").path()); // hair space 58 assertEquals("/%E2%80%8B", HttpUrl.parse("http://h/\u200b").path()); // zero-width space 59 assertEquals("/%E2%80%8C", HttpUrl.parse("http://h/\u200c").path()); // zero-width non-joiner 60 assertEquals("/%E2%80%8D", HttpUrl.parse("http://h/\u200d").path()); // zero-width joiner 61 assertEquals("/%E2%80%8E", HttpUrl.parse("http://h/\u200e").path()); // left-to-right mark 62 assertEquals("/%E2%80%8F", HttpUrl.parse("http://h/\u200f").path()); // right-to-left mark 63 assertEquals("/%E2%80%A8", HttpUrl.parse("http://h/\u2028").path()); // line separator 64 assertEquals("/%E2%80%A9", HttpUrl.parse("http://h/\u2029").path()); // paragraph separator 65 assertEquals("/%E2%80%AF", HttpUrl.parse("http://h/\u202f").path()); // narrow non-breaking space 66 assertEquals("/%E2%81%9F", HttpUrl.parse("http://h/\u205f").path()); // medium mathematical space 67 assertEquals("/%E3%80%80", HttpUrl.parse("http://h/\u3000").path()); // ideographic space 68 } 69 70 @Test public void scheme() throws Exception { 71 assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host/")); 72 assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("Http://host/")); 73 assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host/")); 74 assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("HTTP://host/")); 75 assertEquals(HttpUrl.parse("https://host/"), HttpUrl.parse("https://host/")); 76 assertEquals(HttpUrl.parse("https://host/"), HttpUrl.parse("HTTPS://host/")); 77 assertEquals(null, HttpUrl.parse("httpp://host/")); 78 assertEquals(null, HttpUrl.parse("0ttp://host/")); 79 assertEquals(null, HttpUrl.parse("ht+tp://host/")); 80 assertEquals(null, HttpUrl.parse("ht.tp://host/")); 81 assertEquals(null, HttpUrl.parse("ht-tp://host/")); 82 assertEquals(null, HttpUrl.parse("ht1tp://host/")); 83 assertEquals(null, HttpUrl.parse("httpss://host/")); 84 } 85 86 @Test public void parseNoScheme() throws Exception { 87 assertEquals(null, HttpUrl.parse("//host")); 88 assertEquals(null, HttpUrl.parse("/path")); 89 assertEquals(null, HttpUrl.parse("path")); 90 assertEquals(null, HttpUrl.parse("?query")); 91 assertEquals(null, HttpUrl.parse("#fragment")); 92 } 93 94 @Test public void resolveNoScheme() throws Exception { 95 HttpUrl base = HttpUrl.parse("http://host/a/b"); 96 assertEquals(HttpUrl.parse("http://host2/"), base.resolve("//host2")); 97 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("/path")); 98 assertEquals(HttpUrl.parse("http://host/a/path"), base.resolve("path")); 99 assertEquals(HttpUrl.parse("http://host/a/b?query"), base.resolve("?query")); 100 assertEquals(HttpUrl.parse("http://host/a/b#fragment"), base.resolve("#fragment")); 101 assertEquals(HttpUrl.parse("http://host/a/b"), base.resolve("")); 102 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("\\path")); 103 } 104 105 @Test public void resolveUnsupportedScheme() throws Exception { 106 HttpUrl base = HttpUrl.parse("http://a/"); 107 assertEquals(null, base.resolve("ftp://b")); 108 assertEquals(null, base.resolve("ht+tp://b")); 109 assertEquals(null, base.resolve("ht-tp://b")); 110 assertEquals(null, base.resolve("ht.tp://b")); 111 } 112 113 @Test public void resolveSchemeLikePath() throws Exception { 114 HttpUrl base = HttpUrl.parse("http://a/"); 115 assertEquals(HttpUrl.parse("http://a/http//b/"), base.resolve("http//b/")); 116 assertEquals(HttpUrl.parse("http://a/ht+tp//b/"), base.resolve("ht+tp//b/")); 117 assertEquals(HttpUrl.parse("http://a/ht-tp//b/"), base.resolve("ht-tp//b/")); 118 assertEquals(HttpUrl.parse("http://a/ht.tp//b/"), base.resolve("ht.tp//b/")); 119 } 120 121 @Test public void parseAuthoritySlashCountDoesntMatter() throws Exception { 122 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:host/path")); 123 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/host/path")); 124 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\host/path")); 125 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://host/path")); 126 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\/host/path")); 127 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\host/path")); 128 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\host/path")); 129 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:///host/path")); 130 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\//host/path")); 131 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\/host/path")); 132 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://\\host/path")); 133 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\/host/path")); 134 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:/\\\\host/path")); 135 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:\\\\\\host/path")); 136 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http:////host/path")); 137 } 138 139 @Test public void resolveAuthoritySlashCountDoesntMatterWithDifferentScheme() throws Exception { 140 HttpUrl base = HttpUrl.parse("https://a/b/c"); 141 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:host/path")); 142 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/host/path")); 143 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\host/path")); 144 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://host/path")); 145 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\/host/path")); 146 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\host/path")); 147 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\host/path")); 148 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:///host/path")); 149 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\//host/path")); 150 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\/host/path")); 151 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http://\\host/path")); 152 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\/host/path")); 153 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:/\\\\host/path")); 154 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\\\host/path")); 155 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:////host/path")); 156 } 157 158 @Test public void resolveAuthoritySlashCountMattersWithSameScheme() throws Exception { 159 HttpUrl base = HttpUrl.parse("http://a/b/c"); 160 assertEquals(HttpUrl.parse("http://a/b/host/path"), base.resolve("http:host/path")); 161 assertEquals(HttpUrl.parse("http://a/host/path"), base.resolve("http:/host/path")); 162 assertEquals(HttpUrl.parse("http://a/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 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:\\\\\\host/path")); 174 assertEquals(HttpUrl.parse("http://host/path"), base.resolve("http:////host/path")); 175 } 176 177 @Test public void username() throws Exception { 178 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://@host/path")); 179 assertEquals(HttpUrl.parse("http://user@host/path"), HttpUrl.parse("http://user@host/path")); 180 } 181 182 @Test public void authorityWithMultipleAtSigns() throws Exception { 183 assertEquals(HttpUrl.parse("http://foo%40bar@baz/path"), 184 HttpUrl.parse("http://foo@bar@baz/path")); 185 assertEquals(HttpUrl.parse("http://foo:pass1%40bar%3Apass2@baz/path"), 186 HttpUrl.parse("http://foo:pass1@bar:pass2@baz/path")); 187 } 188 189 @Test public void usernameAndPassword() throws Exception { 190 assertEquals(HttpUrl.parse("http://username:password@host/path"), 191 HttpUrl.parse("http://username:password@host/path")); 192 assertEquals(HttpUrl.parse("http://username@host/path"), 193 HttpUrl.parse("http://username:@host/path")); 194 } 195 196 @Test public void passwordWithEmptyUsername() throws Exception { 197 // Chrome doesn't mind, but Firefox rejects URLs with empty usernames and non-empty passwords. 198 assertEquals(HttpUrl.parse("http://host/path"), HttpUrl.parse("http://:@host/path")); 199 assertEquals("password%40", HttpUrl.parse("http://:password@@host/path").password()); 200 } 201 202 @Test public void unprintableCharactersArePercentEncoded() throws Exception { 203 assertEquals("/%00", HttpUrl.parse("http://host/\u0000").path()); 204 assertEquals("/%08", HttpUrl.parse("http://host/\u0008").path()); 205 assertEquals("/%EF%BF%BD", HttpUrl.parse("http://host/\ufffd").path()); 206 } 207 208 @Test public void usernameCharacters() throws Exception { 209 new UrlComponentEncodingTester() 210 .override(Encoding.PERCENT, '[', ']', '{', '}', '|', '^', '\'', ';', '=', '@') 211 .override(Encoding.SKIP, ':', '/', '\\', '?', '#') 212 .test(Component.USER); 213 } 214 215 @Test public void passwordCharacters() throws Exception { 216 new UrlComponentEncodingTester() 217 .override(Encoding.PERCENT, '[', ']', '{', '}', '|', '^', '\'', ':', ';', '=', '@') 218 .override(Encoding.SKIP, '/', '\\', '?', '#') 219 .test(Component.PASSWORD); 220 } 221 222 @Test public void hostContainsIllegalCharacter() throws Exception { 223 assertEquals(null, HttpUrl.parse("http://\n/")); 224 assertEquals(null, HttpUrl.parse("http:// /")); 225 assertEquals(null, HttpUrl.parse("http://%20/")); 226 } 227 228 @Test public void hostIpv6() throws Exception { 229 // Square braces are absent from host()... 230 assertEquals("::1", HttpUrl.parse("http://[::1]/").host()); 231 232 // ... but they're included in toString(). 233 assertEquals("http://[::1]/", HttpUrl.parse("http://[::1]/").toString()); 234 235 // IPv6 colons don't interfere with port numbers or passwords. 236 assertEquals(8080, HttpUrl.parse("http://[::1]:8080/").port()); 237 assertEquals("password", HttpUrl.parse("http://user:password@[::1]/").password()); 238 assertEquals("::1", HttpUrl.parse("http://user:password@[::1]:8080/").host()); 239 240 // Permit the contents of IPv6 addresses to be percent-encoded... 241 assertEquals("::1", HttpUrl.parse("http://[%3A%3A%31]/").host()); 242 243 // Including the Square braces themselves! (This is what Chrome does.) 244 assertEquals("::1", HttpUrl.parse("http://%5B%3A%3A1%5D/").host()); 245 } 246 247 @Test public void port() throws Exception { 248 assertEquals(HttpUrl.parse("http://host/"), HttpUrl.parse("http://host:80/")); 249 assertEquals(HttpUrl.parse("http://host:99/"), HttpUrl.parse("http://host:99/")); 250 assertEquals(65535, HttpUrl.parse("http://host:65535/").port()); 251 assertEquals(null, HttpUrl.parse("http://host:0/")); 252 assertEquals(null, HttpUrl.parse("http://host:65536/")); 253 assertEquals(null, HttpUrl.parse("http://host:-1/")); 254 assertEquals(null, HttpUrl.parse("http://host:a/")); 255 assertEquals(null, HttpUrl.parse("http://host:%39%39/")); 256 } 257 258 @Test public void pathCharacters() throws Exception { 259 new UrlComponentEncodingTester() 260 .override(Encoding.PERCENT, '^', '{', '}', '|') 261 .override(Encoding.SKIP, '\\', '?', '#') 262 .test(Component.PATH); 263 } 264 265 @Test public void queryCharacters() throws Exception { 266 new UrlComponentEncodingTester() 267 .override(Encoding.IDENTITY, '?', '`') 268 .override(Encoding.PERCENT, '\'') 269 .override(Encoding.SKIP, '#') 270 .test(Component.QUERY); 271 } 272 273 @Test public void fragmentCharacters() throws Exception { 274 new UrlComponentEncodingTester() 275 .override(Encoding.IDENTITY, ' ', '"', '#', '<', '>', '?', '`') 276 .test(Component.FRAGMENT); 277 // TODO(jwilson): don't percent-encode non-ASCII characters. (But do encode control characters!) 278 } 279 280 @Test public void relativePath() throws Exception { 281 HttpUrl base = HttpUrl.parse("http://host/a/b/c"); 282 assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("d/e/f")); 283 assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("../../d/e/f")); 284 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("..")); 285 assertEquals(HttpUrl.parse("http://host/"), base.resolve("../..")); 286 assertEquals(HttpUrl.parse("http://host/"), base.resolve("../../..")); 287 assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve(".")); 288 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("././..")); 289 assertEquals(HttpUrl.parse("http://host/a/b/c/"), base.resolve("c/d/../e/../")); 290 assertEquals(HttpUrl.parse("http://host/a/b/..e/"), base.resolve("..e/")); 291 assertEquals(HttpUrl.parse("http://host/a/b/e/f../"), base.resolve("e/f../")); 292 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2E.")); 293 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve(".%2E")); 294 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2E%2E")); 295 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2e.")); 296 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve(".%2e")); 297 assertEquals(HttpUrl.parse("http://host/a/"), base.resolve("%2e%2e")); 298 assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("%2E")); 299 assertEquals(HttpUrl.parse("http://host/a/b/"), base.resolve("%2e")); 300 } 301 302 @Test public void pathWithBackslash() throws Exception { 303 HttpUrl base = HttpUrl.parse("http://host/a/b/c"); 304 assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("d\\e\\f")); 305 assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("../..\\d\\e\\f")); 306 assertEquals(HttpUrl.parse("http://host/"), base.resolve("..\\..")); 307 } 308 309 @Test public void relativePathWithSameScheme() throws Exception { 310 HttpUrl base = HttpUrl.parse("http://host/a/b/c"); 311 assertEquals(HttpUrl.parse("http://host/a/b/d/e/f"), base.resolve("http:d/e/f")); 312 assertEquals(HttpUrl.parse("http://host/d/e/f"), base.resolve("http:../../d/e/f")); 313 } 314 315 @Test public void decodeUsername() { 316 assertEquals("user", HttpUrl.parse("http://user@host/").decodeUsername()); 317 assertEquals("\uD83C\uDF69", HttpUrl.parse("http://%F0%9F%8D%A9@host/").decodeUsername()); 318 } 319 320 @Test public void decodePassword() { 321 assertEquals("password", HttpUrl.parse("http://user:password@host/").decodePassword()); 322 assertEquals(null, HttpUrl.parse("http://user:@host/").decodePassword()); 323 assertEquals("\uD83C\uDF69", HttpUrl.parse("http://user:%F0%9F%8D%A9@host/").decodePassword()); 324 } 325 326 @Test public void decodeSlashCharacterInDecodedPathSegment() { 327 assertEquals(Arrays.asList("a/b/c"), 328 HttpUrl.parse("http://host/a%2Fb%2Fc").decodePathSegments()); 329 } 330 331 @Test public void decodeEmptyPathSegments() { 332 assertEquals(Arrays.asList(""), 333 HttpUrl.parse("http://host/").decodePathSegments()); 334 } 335 336 @Test public void percentDecode() throws Exception { 337 assertEquals(Arrays.asList("\u0000"), 338 HttpUrl.parse("http://host/%00").decodePathSegments()); 339 assertEquals(Arrays.asList("a", "\u2603", "c"), 340 HttpUrl.parse("http://host/a/%E2%98%83/c").decodePathSegments()); 341 assertEquals(Arrays.asList("a", "\uD83C\uDF69", "c"), 342 HttpUrl.parse("http://host/a/%F0%9F%8D%A9/c").decodePathSegments()); 343 assertEquals(Arrays.asList("a", "b", "c"), 344 HttpUrl.parse("http://host/a/%62/c").decodePathSegments()); 345 assertEquals(Arrays.asList("a", "z", "c"), 346 HttpUrl.parse("http://host/a/%7A/c").decodePathSegments()); 347 assertEquals(Arrays.asList("a", "z", "c"), 348 HttpUrl.parse("http://host/a/%7a/c").decodePathSegments()); 349 } 350 351 @Test public void malformedPercentEncoding() { 352 assertEquals(Arrays.asList("a%f", "b"), 353 HttpUrl.parse("http://host/a%f/b").decodePathSegments()); 354 assertEquals(Arrays.asList("%", "b"), 355 HttpUrl.parse("http://host/%/b").decodePathSegments()); 356 assertEquals(Arrays.asList("%"), 357 HttpUrl.parse("http://host/%").decodePathSegments()); 358 } 359 360 @Test public void malformedUtf8Encoding() { 361 // Replace a partial UTF-8 sequence with the Unicode replacement character. 362 assertEquals(Arrays.asList("a", "\ufffdx", "c"), 363 HttpUrl.parse("http://host/a/%E2%98x/c").decodePathSegments()); 364 } 365} 366