diff --git a/native-image-tests/src/main/resources/testlist.txt b/native-image-tests/src/main/resources/testlist.txt index 454f7dc1805d..fdf740f21105 100644 --- a/native-image-tests/src/main/resources/testlist.txt +++ b/native-image-tests/src/main/resources/testlist.txt @@ -20,7 +20,6 @@ okhttp3.FormBodyTest okhttp3.HandshakeTest okhttp3.HeadersKotlinTest okhttp3.HeadersTest -okhttp3.HttpUrlGetTest okhttp3.HttpUrlTest okhttp3.InsecureForHostTest okhttp3.InterceptorTest diff --git a/okhttp/src/commonMain/kotlin/okhttp3/HttpUrl.kt b/okhttp/src/commonMain/kotlin/okhttp3/HttpUrl.kt index 510a34085b00..d41a5dc32232 100644 --- a/okhttp/src/commonMain/kotlin/okhttp3/HttpUrl.kt +++ b/okhttp/src/commonMain/kotlin/okhttp3/HttpUrl.kt @@ -16,7 +16,8 @@ package okhttp3 import kotlin.jvm.JvmName -import kotlin.jvm.JvmStatic +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull /** * A uniform resource locator (URL) with a scheme of either `http` or `https`. Use this class to @@ -501,6 +502,11 @@ expect class HttpUrl internal constructor( internal val url: String + /** + * Alternating, decoded query names and values, or null for no query. Names may be empty or + * non-empty, but never null. Values are null if the name has no corresponding '=' separator, or + * empty, or non-empty. + */ internal val queryNamesAndValues: List? /** @@ -595,6 +601,9 @@ expect class HttpUrl internal constructor( internal var encodedQueryNamesAndValues: MutableList? internal var encodedFragment: String? + /** + * @param scheme either "http" or "https". + */ fun scheme(scheme: String): Builder fun username(username: String): Builder @@ -604,6 +613,10 @@ expect class HttpUrl internal constructor( fun encodedPassword(encodedPassword: String): Builder + /** + * @param host either a regular hostname, International Domain Name, IPv4 address, or IPv6 + * address. + */ fun host(host: String): Builder fun port(port: Int): Builder diff --git a/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlCommonTest.kt b/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlCommonTest.kt deleted file mode 100644 index 195e7d541e81..000000000000 --- a/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlCommonTest.kt +++ /dev/null @@ -1,1334 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3 - -import assertk.assertThat -import assertk.assertions.containsExactly -import assertk.assertions.hasMessage -import assertk.assertions.isEqualTo -import assertk.assertions.isNull -import assertk.fail -import kotlin.test.Test -import kotlin.test.assertFailsWith -import okhttp3.HttpUrl.Companion.toHttpUrl - -@Suppress("HttpUrlsUsage") // Don't warn if we should be using https://. -open class HttpUrlCommonTest { - protected open fun parse(url: String): HttpUrl { - return url.toHttpUrl() - } - - protected open fun assertInvalid(string: String, exceptionMessage: String?) { - try { - val result = string.toHttpUrl() - if (exceptionMessage != null) { - fail("Expected failure with $exceptionMessage but got $result") - } else { - fail("Expected failure but got $result") - } - } catch(iae: IllegalArgumentException) { - iae.printStackTrace() - if (exceptionMessage != null) { - assertThat(iae).hasMessage(exceptionMessage) - } - } - } - - @Test - fun parseTrimsAsciiWhitespace() { - val expected = parse("http://host/") - // Leading. - assertThat(parse("http://host/\u000c\n\t \r")).isEqualTo(expected) - // Trailing. - assertThat(parse("\r\n\u000c \thttp://host/")).isEqualTo(expected) - // Both. - assertThat(parse(" http://host/ ")).isEqualTo(expected) - // Both. - assertThat(parse(" http://host/ ")).isEqualTo(expected) - assertThat(parse("http://host/").resolve(" ")).isEqualTo(expected) - assertThat(parse("http://host/").resolve(" . ")).isEqualTo(expected) - } - - @Test - fun parseHostAsciiNonPrintable() { - val host = "host\u0001" - assertInvalid("http://$host/", "Invalid URL host: \"host\u0001\"") - // TODO make exception message escape non-printable characters - } - - @Test - fun parseDoesNotTrimOtherWhitespaceCharacters() { - // Whitespace characters list from Google's Guava team: http://goo.gl/IcR9RD - // line tabulation - assertThat(parse("http://h/\u000b").encodedPath).isEqualTo("/%0B") - // information separator 4 - assertThat(parse("http://h/\u001c").encodedPath).isEqualTo("/%1C") - // information separator 3 - assertThat(parse("http://h/\u001d").encodedPath).isEqualTo("/%1D") - // information separator 2 - assertThat(parse("http://h/\u001e").encodedPath).isEqualTo("/%1E") - // information separator 1 - assertThat(parse("http://h/\u001f").encodedPath).isEqualTo("/%1F") - // next line - assertThat(parse("http://h/\u0085").encodedPath).isEqualTo("/%C2%85") - // non-breaking space - assertThat(parse("http://h/\u00a0").encodedPath).isEqualTo("/%C2%A0") - // ogham space mark - assertThat(parse("http://h/\u1680").encodedPath).isEqualTo("/%E1%9A%80") - // mongolian vowel separator - assertThat(parse("http://h/\u180e").encodedPath).isEqualTo("/%E1%A0%8E") - // en quad - assertThat(parse("http://h/\u2000").encodedPath).isEqualTo("/%E2%80%80") - // em quad - assertThat(parse("http://h/\u2001").encodedPath).isEqualTo("/%E2%80%81") - // en space - assertThat(parse("http://h/\u2002").encodedPath).isEqualTo("/%E2%80%82") - // em space - assertThat(parse("http://h/\u2003").encodedPath).isEqualTo("/%E2%80%83") - // three-per-em space - assertThat(parse("http://h/\u2004").encodedPath).isEqualTo("/%E2%80%84") - // four-per-em space - assertThat(parse("http://h/\u2005").encodedPath).isEqualTo("/%E2%80%85") - // six-per-em space - assertThat(parse("http://h/\u2006").encodedPath).isEqualTo("/%E2%80%86") - // figure space - assertThat(parse("http://h/\u2007").encodedPath).isEqualTo("/%E2%80%87") - // punctuation space - assertThat(parse("http://h/\u2008").encodedPath).isEqualTo("/%E2%80%88") - // thin space - assertThat(parse("http://h/\u2009").encodedPath).isEqualTo("/%E2%80%89") - // hair space - assertThat(parse("http://h/\u200a").encodedPath).isEqualTo("/%E2%80%8A") - // zero-width space - assertThat(parse("http://h/\u200b").encodedPath).isEqualTo("/%E2%80%8B") - // zero-width non-joiner - assertThat(parse("http://h/\u200c").encodedPath).isEqualTo("/%E2%80%8C") - // zero-width joiner - assertThat(parse("http://h/\u200d").encodedPath).isEqualTo("/%E2%80%8D") - // left-to-right mark - assertThat(parse("http://h/\u200e").encodedPath).isEqualTo("/%E2%80%8E") - // right-to-left mark - assertThat(parse("http://h/\u200f").encodedPath).isEqualTo("/%E2%80%8F") - // line separator - assertThat(parse("http://h/\u2028").encodedPath).isEqualTo("/%E2%80%A8") - // paragraph separator - assertThat(parse("http://h/\u2029").encodedPath).isEqualTo("/%E2%80%A9") - // narrow non-breaking space - assertThat(parse("http://h/\u202f").encodedPath).isEqualTo("/%E2%80%AF") - // medium mathematical space - assertThat(parse("http://h/\u205f").encodedPath).isEqualTo("/%E2%81%9F") - // ideographic space - assertThat(parse("http://h/\u3000").encodedPath).isEqualTo("/%E3%80%80") - } - - @Test - fun newBuilderResolve() { - // Non-exhaustive tests because implementation is the same as resolve. - val base = parse("http://host/a/b") - assertThat(base.newBuilder("https://host2")!!.build()) - .isEqualTo(parse("https://host2/")) - assertThat(base.newBuilder("//host2")!!.build()) - .isEqualTo(parse("http://host2/")) - assertThat(base.newBuilder("/path")!!.build()) - .isEqualTo(parse("http://host/path")) - assertThat(base.newBuilder("path")!!.build()) - .isEqualTo(parse("http://host/a/path")) - assertThat(base.newBuilder("?query")!!.build()) - .isEqualTo(parse("http://host/a/b?query")) - assertThat(base.newBuilder("#fragment")!!.build()) - .isEqualTo(parse("http://host/a/b#fragment")) - assertThat(base.newBuilder("")!!.build()).isEqualTo(parse("http://host/a/b")) - assertThat(base.newBuilder("ftp://b")).isNull() - assertThat(base.newBuilder("ht+tp://b")).isNull() - assertThat(base.newBuilder("ht-tp://b")).isNull() - assertThat(base.newBuilder("ht.tp://b")).isNull() - } - - @Test - fun redactedUrl() { - val baseWithPasswordAndUsername = parse("http://username:password@host/a/b#fragment") - val baseWithUsernameOnly = parse("http://username@host/a/b#fragment") - val baseWithPasswordOnly = parse("http://password@host/a/b#fragment") - assertThat(baseWithPasswordAndUsername.redact()).isEqualTo("http://host/...") - assertThat(baseWithUsernameOnly.redact()).isEqualTo("http://host/...") - assertThat(baseWithPasswordOnly.redact()).isEqualTo("http://host/...") - } - - @Test - fun resolveNoScheme() { - val base = parse("http://host/a/b") - assertThat(base.resolve("//host2")).isEqualTo(parse("http://host2/")) - assertThat(base.resolve("/path")).isEqualTo(parse("http://host/path")) - assertThat(base.resolve("path")).isEqualTo(parse("http://host/a/path")) - assertThat(base.resolve("?query")).isEqualTo(parse("http://host/a/b?query")) - assertThat(base.resolve("#fragment")) - .isEqualTo(parse("http://host/a/b#fragment")) - assertThat(base.resolve("")).isEqualTo(parse("http://host/a/b")) - assertThat(base.resolve("\\path")).isEqualTo(parse("http://host/path")) - } - - @Test - fun resolveUnsupportedScheme() { - val base = parse("http://a/") - assertThat(base.resolve("ftp://b")).isNull() - assertThat(base.resolve("ht+tp://b")).isNull() - assertThat(base.resolve("ht-tp://b")).isNull() - assertThat(base.resolve("ht.tp://b")).isNull() - } - - @Test - fun resolveSchemeLikePath() { - val base = parse("http://a/") - assertThat(base.resolve("http//b/")).isEqualTo(parse("http://a/http//b/")) - assertThat(base.resolve("ht+tp//b/")).isEqualTo(parse("http://a/ht+tp//b/")) - assertThat(base.resolve("ht-tp//b/")).isEqualTo(parse("http://a/ht-tp//b/")) - assertThat(base.resolve("ht.tp//b/")).isEqualTo(parse("http://a/ht.tp//b/")) - } - - /** - * https://tools.ietf.org/html/rfc3986#section-5.4.1 - */ - @Test - fun rfc3886NormalExamples() { - val url = parse("http://a/b/c/d;p?q") - // No 'g:' scheme in HttpUrl. - assertThat(url.resolve("g:h")).isNull() - assertThat(url.resolve("g")).isEqualTo(parse("http://a/b/c/g")) - assertThat(url.resolve("./g")).isEqualTo(parse("http://a/b/c/g")) - assertThat(url.resolve("g/")).isEqualTo(parse("http://a/b/c/g/")) - assertThat(url.resolve("/g")).isEqualTo(parse("http://a/g")) - assertThat(url.resolve("//g")).isEqualTo(parse("http://g")) - assertThat(url.resolve("?y")).isEqualTo(parse("http://a/b/c/d;p?y")) - assertThat(url.resolve("g?y")).isEqualTo(parse("http://a/b/c/g?y")) - assertThat(url.resolve("#s")).isEqualTo(parse("http://a/b/c/d;p?q#s")) - assertThat(url.resolve("g#s")).isEqualTo(parse("http://a/b/c/g#s")) - assertThat(url.resolve("g?y#s")).isEqualTo(parse("http://a/b/c/g?y#s")) - assertThat(url.resolve(";x")).isEqualTo(parse("http://a/b/c/;x")) - assertThat(url.resolve("g;x")).isEqualTo(parse("http://a/b/c/g;x")) - assertThat(url.resolve("g;x?y#s")).isEqualTo(parse("http://a/b/c/g;x?y#s")) - assertThat(url.resolve("")).isEqualTo(parse("http://a/b/c/d;p?q")) - assertThat(url.resolve(".")).isEqualTo(parse("http://a/b/c/")) - assertThat(url.resolve("./")).isEqualTo(parse("http://a/b/c/")) - assertThat(url.resolve("..")).isEqualTo(parse("http://a/b/")) - assertThat(url.resolve("../")).isEqualTo(parse("http://a/b/")) - assertThat(url.resolve("../g")).isEqualTo(parse("http://a/b/g")) - assertThat(url.resolve("../..")).isEqualTo(parse("http://a/")) - assertThat(url.resolve("../../")).isEqualTo(parse("http://a/")) - assertThat(url.resolve("../../g")).isEqualTo(parse("http://a/g")) - } - - /** - * https://tools.ietf.org/html/rfc3986#section-5.4.2 - */ - @Test - fun rfc3886AbnormalExamples() { - val url = parse("http://a/b/c/d;p?q") - assertThat(url.resolve("../../../g")).isEqualTo(parse("http://a/g")) - assertThat(url.resolve("../../../../g")).isEqualTo(parse("http://a/g")) - assertThat(url.resolve("/./g")).isEqualTo(parse("http://a/g")) - assertThat(url.resolve("/../g")).isEqualTo(parse("http://a/g")) - assertThat(url.resolve("g.")).isEqualTo(parse("http://a/b/c/g.")) - assertThat(url.resolve(".g")).isEqualTo(parse("http://a/b/c/.g")) - assertThat(url.resolve("g..")).isEqualTo(parse("http://a/b/c/g..")) - assertThat(url.resolve("..g")).isEqualTo(parse("http://a/b/c/..g")) - assertThat(url.resolve("./../g")).isEqualTo(parse("http://a/b/g")) - assertThat(url.resolve("./g/.")).isEqualTo(parse("http://a/b/c/g/")) - assertThat(url.resolve("g/./h")).isEqualTo(parse("http://a/b/c/g/h")) - assertThat(url.resolve("g/../h")).isEqualTo(parse("http://a/b/c/h")) - assertThat(url.resolve("g;x=1/./y")).isEqualTo(parse("http://a/b/c/g;x=1/y")) - assertThat(url.resolve("g;x=1/../y")).isEqualTo(parse("http://a/b/c/y")) - assertThat(url.resolve("g?y/./x")).isEqualTo(parse("http://a/b/c/g?y/./x")) - assertThat(url.resolve("g?y/../x")).isEqualTo(parse("http://a/b/c/g?y/../x")) - assertThat(url.resolve("g#s/./x")).isEqualTo(parse("http://a/b/c/g#s/./x")) - assertThat(url.resolve("g#s/../x")).isEqualTo(parse("http://a/b/c/g#s/../x")) - // "http:g" also okay. - assertThat(url.resolve("http:g")).isEqualTo(parse("http://a/b/c/g")) - } - - @Test - fun parseAuthoritySlashCountDoesntMatter() { - assertThat(parse("http:host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http://host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:/\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:///host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\//host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:/\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http://\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:/\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:\\\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http:////host/path")) - .isEqualTo(parse("http://host/path")) - } - - @Test - fun resolveAuthoritySlashCountDoesntMatterWithDifferentScheme() { - val base = parse("https://a/b/c") - assertThat(base.resolve("http:host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http://host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:///host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\//host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http://\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:////host/path")) - .isEqualTo(parse("http://host/path")) - } - - @Test - fun resolveAuthoritySlashCountMattersWithSameScheme() { - val base = parse("http://a/b/c") - assertThat(base.resolve("http:host/path")) - .isEqualTo(parse("http://a/b/host/path")) - assertThat(base.resolve("http:/host/path")) - .isEqualTo(parse("http://a/host/path")) - assertThat(base.resolve("http:\\host/path")) - .isEqualTo(parse("http://a/host/path")) - assertThat(base.resolve("http://host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:///host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\//host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http://\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\/host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:/\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:\\\\\\host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(base.resolve("http:////host/path")) - .isEqualTo(parse("http://host/path")) - } - - @Test - fun username() { - assertThat(parse("http://@host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http://user@host/path")) - .isEqualTo(parse("http://user@host/path")) - } - - /** - * Given multiple '@' characters, the last one is the delimiter. - */ - @Test - fun authorityWithMultipleAtSigns() { - val httpUrl = parse("http://foo@bar@baz/path") - assertThat(httpUrl.username).isEqualTo("foo@bar") - assertThat(httpUrl.password).isEqualTo("") - assertThat(httpUrl).isEqualTo(parse("http://foo%40bar@baz/path")) - } - - /** - * Given multiple ':' characters, the first one is the delimiter. - */ - @Test - fun authorityWithMultipleColons() { - val httpUrl = parse("http://foo:pass1@bar:pass2@baz/path") - assertThat(httpUrl.username).isEqualTo("foo") - assertThat(httpUrl.password).isEqualTo("pass1@bar:pass2") - assertThat(httpUrl).isEqualTo(parse("http://foo:pass1%40bar%3Apass2@baz/path")) - } - - @Test - fun usernameAndPassword() { - assertThat(parse("http://username:password@host/path")) - .isEqualTo(parse("http://username:password@host/path")) - assertThat(parse("http://username:@host/path")) - .isEqualTo(parse("http://username@host/path")) - } - - @Test - fun passwordWithEmptyUsername() { - // Chrome doesn't mind, but Firefox rejects URLs with empty usernames and non-empty passwords. - assertThat(parse("http://:@host/path")) - .isEqualTo(parse("http://host/path")) - assertThat(parse("http://:password@@host/path").encodedPassword) - .isEqualTo("password%40") - } - - @Test - fun unprintableCharactersArePercentEncoded() { - assertThat(parse("http://host/\u0000").encodedPath).isEqualTo("/%00") - assertThat(parse("http://host/\u0008").encodedPath).isEqualTo("/%08") - assertThat(parse("http://host/\ufffd").encodedPath).isEqualTo("/%EF%BF%BD") - } - - @Test - fun hostContainsIllegalCharacter() { - assertInvalid("http://\n/", "Invalid URL host: \"\n\"") - assertInvalid("http:// /", "Invalid URL host: \" \"") - assertInvalid("http://%20/", "Invalid URL host: \"%20\"") - } - - @Test - fun hostIpv6() { - // Square braces are absent from host()... - assertThat(parse("http://[::1]/").host).isEqualTo("::1") - - // ... but they're included in toString(). - assertThat(parse("http://[::1]/").toString()).isEqualTo("http://[::1]/") - - // IPv6 colons don't interfere with port numbers or passwords. - assertThat(parse("http://[::1]:8080/").port).isEqualTo(8080) - assertThat(parse("http://user:password@[::1]/").password).isEqualTo("password") - assertThat(parse("http://user:password@[::1]:8080/").host).isEqualTo("::1") - - // Permit the contents of IPv6 addresses to be percent-encoded... - assertThat(parse("http://[%3A%3A%31]/").host).isEqualTo("::1") - - // Including the Square braces themselves! (This is what Chrome does.) - assertThat(parse("http://%5B%3A%3A1%5D/").host).isEqualTo("::1") - } - - @Test - fun hostIpv6AddressDifferentFormats() { - // Multiple representations of the same address; see http://tools.ietf.org/html/rfc5952. - val a3 = "2001:db8::1:0:0:1" - assertThat(parse("http://[2001:db8:0:0:1:0:0:1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:0db8:0:0:1:0:0:1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:db8::1:0:0:1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:db8::0:1:0:0:1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:0db8::1:0:0:1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:db8:0:0:1::1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:db8:0000:0:1::1]").host).isEqualTo(a3) - assertThat(parse("http://[2001:DB8:0:0:1::1]").host).isEqualTo(a3) - } - - @Test - fun hostIpv6AddressLeadingCompression() { - assertThat(parse("http://[::0001]").host).isEqualTo("::1") - assertThat(parse("http://[0000::0001]").host).isEqualTo("::1") - assertThat(parse("http://[0000:0000:0000:0000:0000:0000:0000:0001]").host) - .isEqualTo("::1") - assertThat(parse("http://[0000:0000:0000:0000:0000:0000::0001]").host) - .isEqualTo("::1") - } - - @Test - fun hostIpv6AddressTrailingCompression() { - assertThat(parse("http://[0001:0000::]").host).isEqualTo("1::") - assertThat(parse("http://[0001::0000]").host).isEqualTo("1::") - assertThat(parse("http://[0001::]").host).isEqualTo("1::") - assertThat(parse("http://[1::]").host).isEqualTo("1::") - } - - @Test - fun hostIpv6AddressTooManyDigitsInGroup() { - assertInvalid( - "http://[00000:0000:0000:0000:0000:0000:0000:0001]", - "Invalid URL host: \"[00000:0000:0000:0000:0000:0000:0000:0001]\"" - ) - assertInvalid("http://[::00001]", "Invalid URL host: \"[::00001]\"") - } - - @Test - fun hostIpv6AddressMisplacedColons() { - assertInvalid( - "http://[:0000:0000:0000:0000:0000:0000:0000:0001]", - "Invalid URL host: \"[:0000:0000:0000:0000:0000:0000:0000:0001]\"" - ) - assertInvalid( - "http://[:::0000:0000:0000:0000:0000:0000:0000:0001]", - "Invalid URL host: \"[:::0000:0000:0000:0000:0000:0000:0000:0001]\"" - ) - assertInvalid("http://[:1]", "Invalid URL host: \"[:1]\"") - assertInvalid("http://[:::1]", "Invalid URL host: \"[:::1]\"") - assertInvalid( - "http://[0000:0000:0000:0000:0000:0000:0001:]", - "Invalid URL host: \"[0000:0000:0000:0000:0000:0000:0001:]\"" - ) - assertInvalid( - "http://[0000:0000:0000:0000:0000:0000:0000:0001:]", - "Invalid URL host: \"[0000:0000:0000:0000:0000:0000:0000:0001:]\"" - ) - assertInvalid( - "http://[0000:0000:0000:0000:0000:0000:0000:0001::]", - "Invalid URL host: \"[0000:0000:0000:0000:0000:0000:0000:0001::]\"" - ) - assertInvalid( - "http://[0000:0000:0000:0000:0000:0000:0000:0001:::]", - "Invalid URL host: \"[0000:0000:0000:0000:0000:0000:0000:0001:::]\"" - ) - assertInvalid("http://[1:]", "Invalid URL host: \"[1:]\"") - assertInvalid("http://[1:::]", "Invalid URL host: \"[1:::]\"") - assertInvalid("http://[1:::1]", "Invalid URL host: \"[1:::1]\"") - assertInvalid( - "http://[0000:0000:0000:0000::0000:0000:0000:0001]", - "Invalid URL host: \"[0000:0000:0000:0000::0000:0000:0000:0001]\"" - ) - } - - @Test - fun hostIpv6AddressTooManyGroups() { - assertInvalid( - "http://[0000:0000:0000:0000:0000:0000:0000:0000:0001]", - "Invalid URL host: \"[0000:0000:0000:0000:0000:0000:0000:0000:0001]\"" - ) - } - - @Test - fun hostIpv6AddressTooMuchCompression() { - assertInvalid( - "http://[0000::0000:0000:0000:0000::0001]", - "Invalid URL host: \"[0000::0000:0000:0000:0000::0001]\"" - ) - assertInvalid( - "http://[::0000:0000:0000:0000::0001]", - "Invalid URL host: \"[::0000:0000:0000:0000::0001]\"" - ) - } - - @Test - fun hostIpv6ScopedAddress() { - // java.net.InetAddress parses scoped addresses. These aren't valid in URLs. - assertInvalid("http://[::1%2544]", "Invalid URL host: \"[::1%2544]\"") - } - - @Test - fun hostIpv6AddressTooManyLeadingZeros() { - // Guava's been buggy on this case. https://github.com/google/guava/issues/3116 - assertInvalid( - "http://[2001:db8:0:0:1:0:0:00001]", - "Invalid URL host: \"[2001:db8:0:0:1:0:0:00001]\"" - ) - } - - @Test - fun hostIpv6WithIpv4Suffix() { - assertThat(parse("http://[::1:255.255.255.255]/").host) - .isEqualTo("::1:ffff:ffff") - assertThat(parse("http://[0:0:0:0:0:1:0.0.0.0]/").host).isEqualTo("::1:0:0") - } - - @Test - fun hostIpv6WithIpv4SuffixWithOctalPrefix() { - // Chrome interprets a leading '0' as octal; Firefox rejects them. (We reject them.) - assertInvalid( - "http://[0:0:0:0:0:1:0.0.0.000000]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.0.0.000000]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:0.010.0.010]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.010.0.010]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:0.0.0.000001]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.0.0.000001]\"" - ) - } - - @Test - fun hostIpv6WithIpv4SuffixWithHexadecimalPrefix() { - // Chrome interprets a leading '0x' as hexadecimal; Firefox rejects them. (We reject them.) - assertInvalid( - "http://[0:0:0:0:0:1:0.0x10.0.0x10]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.0x10.0.0x10]\"" - ) - } - - @Test - fun hostIpv6WithMalformedIpv4Suffix() { - assertInvalid( - "http://[0:0:0:0:0:1:0.0:0.0]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.0:0.0]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:0.0-0.0]/", - "Invalid URL host: \"[0:0:0:0:0:1:0.0-0.0]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:.255.255.255]/", - "Invalid URL host: \"[0:0:0:0:0:1:.255.255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:255..255.255]/", - "Invalid URL host: \"[0:0:0:0:0:1:255..255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:255.255..255]/", - "Invalid URL host: \"[0:0:0:0:0:1:255.255..255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:0:1:255.255]/", - "Invalid URL host: \"[0:0:0:0:0:0:1:255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:256.255.255.255]/", - "Invalid URL host: \"[0:0:0:0:0:1:256.255.255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:ff.255.255.255]/", - "Invalid URL host: \"[0:0:0:0:0:1:ff.255.255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:0:1:255.255.255.255]/", - "Invalid URL host: \"[0:0:0:0:0:0:1:255.255.255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:1:255.255.255.255]/", - "Invalid URL host: \"[0:0:0:0:1:255.255.255.255]\"" - ) - assertInvalid( - "http://[0:0:0:0:1:0.0.0.0:1]/", - "Invalid URL host: \"[0:0:0:0:1:0.0.0.0:1]\"" - ) - assertInvalid( - "http://[0:0.0.0.0:1:0:0:0:0:1]/", - "Invalid URL host: \"[0:0.0.0.0:1:0:0:0:0:1]\"" - ) - assertInvalid( - "http://[0.0.0.0:0:0:0:0:0:1]/", - "Invalid URL host: \"[0.0.0.0:0:0:0:0:0:1]\"" - ) - } - - @Test - fun hostIpv6WithIncompleteIpv4Suffix() { - // To Chrome & Safari these are well-formed; Firefox disagrees. (We're consistent with Firefox). - assertInvalid( - "http://[0:0:0:0:0:1:255.255.255.]/", - "Invalid URL host: \"[0:0:0:0:0:1:255.255.255.]\"" - ) - assertInvalid( - "http://[0:0:0:0:0:1:255.255.255]/", - "Invalid URL host: \"[0:0:0:0:0:1:255.255.255]\"" - ) - } - - @Test - fun hostIpv6Malformed() { - assertInvalid("http://[::g]/", "Invalid URL host: \"[::g]\"") - } - - /** - * The builder permits square braces but does not require them. - */ - @Test - fun hostIpv6Builder() { - val base = parse("http://example.com/") - assertThat(base.newBuilder().host("[::1]").build().toString()) - .isEqualTo("http://[::1]/") - assertThat(base.newBuilder().host("[::0001]").build().toString()) - .isEqualTo("http://[::1]/") - assertThat(base.newBuilder().host("::1").build().toString()) - .isEqualTo("http://[::1]/") - assertThat(base.newBuilder().host("::0001").build().toString()) - .isEqualTo("http://[::1]/") - } - - @Test - fun relativePath() { - val base = parse("http://host/a/b/c") - assertThat(base.resolve("d/e/f")).isEqualTo(parse("http://host/a/b/d/e/f")) - assertThat(base.resolve("../../d/e/f")).isEqualTo(parse("http://host/d/e/f")) - assertThat(base.resolve("..")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("../..")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../..")).isEqualTo(parse("http://host/")) - assertThat(base.resolve(".")).isEqualTo(parse("http://host/a/b/")) - assertThat(base.resolve("././..")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("c/d/../e/../")).isEqualTo(parse("http://host/a/b/c/")) - assertThat(base.resolve("..e/")).isEqualTo(parse("http://host/a/b/..e/")) - assertThat(base.resolve("e/f../")).isEqualTo(parse("http://host/a/b/e/f../")) - assertThat(base.resolve("%2E.")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve(".%2E")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("%2E%2E")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("%2e.")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve(".%2e")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("%2e%2e")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("%2E")).isEqualTo(parse("http://host/a/b/")) - assertThat(base.resolve("%2e")).isEqualTo(parse("http://host/a/b/")) - } - - @Test - fun relativePathWithTrailingSlash() { - val base = parse("http://host/a/b/c/") - assertThat(base.resolve("..")).isEqualTo(parse("http://host/a/b/")) - assertThat(base.resolve("../")).isEqualTo(parse("http://host/a/b/")) - assertThat(base.resolve("../..")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("../../")).isEqualTo(parse("http://host/a/")) - assertThat(base.resolve("../../..")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../../")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../../..")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../../../")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../../../a")).isEqualTo(parse("http://host/a")) - assertThat(base.resolve("../../../../a/..")).isEqualTo(parse("http://host/")) - assertThat(base.resolve("../../../../a/b/..")).isEqualTo(parse("http://host/a/")) - } - - @Test - fun pathWithBackslash() { - val base = parse("http://host/a/b/c") - assertThat(base.resolve("d\\e\\f")).isEqualTo(parse("http://host/a/b/d/e/f")) - assertThat(base.resolve("../..\\d\\e\\f")) - .isEqualTo(parse("http://host/d/e/f")) - assertThat(base.resolve("..\\..")).isEqualTo(parse("http://host/")) - } - - @Test - fun relativePathWithSameScheme() { - val base = parse("http://host/a/b/c") - assertThat(base.resolve("http:d/e/f")).isEqualTo(parse("http://host/a/b/d/e/f")) - assertThat(base.resolve("http:../../d/e/f")) - .isEqualTo(parse("http://host/d/e/f")) - } - - @Test - fun decodeUsername() { - assertThat(parse("http://user@host/").username).isEqualTo("user") - assertThat(parse("http://%F0%9F%8D%A9@host/").username).isEqualTo("\uD83C\uDF69") - } - - @Test - fun decodePassword() { - assertThat(parse("http://user:password@host/").password).isEqualTo("password") - assertThat(parse("http://user:@host/").password).isEqualTo("") - assertThat(parse("http://user:%F0%9F%8D%A9@host/").password) - .isEqualTo("\uD83C\uDF69") - } - - @Test - fun decodeSlashCharacterInDecodedPathSegment() { - assertThat(parse("http://host/a%2Fb%2Fc").pathSegments).containsExactly("a/b/c") - } - - @Test - fun decodeEmptyPathSegments() { - assertThat(parse("http://host/").pathSegments).containsExactly("") - } - - @Test - fun percentDecode() { - assertThat(parse("http://host/%00").pathSegments).containsExactly("\u0000") - assertThat(parse("http://host/a/%E2%98%83/c").pathSegments) - .containsExactly("a", "\u2603", "c") - assertThat(parse("http://host/a/%F0%9F%8D%A9/c").pathSegments) - .containsExactly("a", "\uD83C\uDF69", "c") - assertThat(parse("http://host/a/%62/c").pathSegments) - .containsExactly("a", "b", "c") - assertThat(parse("http://host/a/%7A/c").pathSegments) - .containsExactly("a", "z", "c") - assertThat(parse("http://host/a/%7a/c").pathSegments) - .containsExactly("a", "z", "c") - } - - @Test - fun malformedPercentEncoding() { - assertThat(parse("http://host/a%f/b").pathSegments).containsExactly("a%f", "b") - assertThat(parse("http://host/%/b").pathSegments).containsExactly("%", "b") - assertThat(parse("http://host/%").pathSegments).containsExactly("%") - assertThat(parse("http://github.com/%%30%30").pathSegments) - .containsExactly("%00") - } - - @Test - fun malformedUtf8Encoding() { - // Replace a partial UTF-8 sequence with the Unicode replacement character. - assertThat(parse("http://host/a/%E2%98x/c").pathSegments) - .containsExactly("a", "\ufffdx", "c") - } - - @Test - fun incompleteUrlComposition() { - val noHost = assertFailsWith { - HttpUrl.Builder().scheme("http").build() - } - assertThat(noHost.message).isEqualTo("host == null") - val noScheme = assertFailsWith { - HttpUrl.Builder().host("host").build() - } - assertThat(noScheme.message).isEqualTo("scheme == null") - } - - @Test - fun builderToString() { - assertThat(parse("https://host.com/path").newBuilder().toString()) - .isEqualTo("https://host.com/path") - } - - @Test - fun incompleteBuilderToString() { - assertThat(HttpUrl.Builder().scheme("https").encodedPath("/path").toString()) - .isEqualTo("https:///path") - assertThat(HttpUrl.Builder().host("host.com").encodedPath("/path").toString()) - .isEqualTo("//host.com/path") - assertThat( - HttpUrl.Builder().host("host.com").encodedPath("/path").port(8080).toString() - ) - .isEqualTo("//host.com:8080/path") - } - - @Test - fun changingSchemeChangesDefaultPort() { - assertThat( - parse("http://example.com") - .newBuilder() - .scheme("https") - .build().port - ).isEqualTo(443) - assertThat( - parse("https://example.com") - .newBuilder() - .scheme("http") - .build().port - ).isEqualTo(80) - assertThat( - parse("https://example.com:1234") - .newBuilder() - .scheme("http") - .build().port - ).isEqualTo(1234) - } - - @Test - fun composeWithEncodedPath() { - val url = HttpUrl.Builder() - .scheme("http") - .host("host") - .encodedPath("/a%2Fb/c") - .build() - assertThat(url.toString()).isEqualTo("http://host/a%2Fb/c") - assertThat(url.encodedPath).isEqualTo("/a%2Fb/c") - assertThat(url.pathSegments).containsExactly("a/b", "c") - } - - @Test - fun composeMixingPathSegments() { - val url = HttpUrl.Builder() - .scheme("http") - .host("host") - .encodedPath("/a%2fb/c") - .addPathSegment("d%25e") - .addEncodedPathSegment("f%25g") - .build() - assertThat(url.toString()).isEqualTo("http://host/a%2fb/c/d%2525e/f%25g") - assertThat(url.encodedPath).isEqualTo("/a%2fb/c/d%2525e/f%25g") - assertThat(url.encodedPathSegments) - .containsExactly("a%2fb", "c", "d%2525e", "f%25g") - assertThat(url.pathSegments).containsExactly("a/b", "c", "d%25e", "f%g") - } - - @Test - fun composeWithAddSegment() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder() - .addPathSegment("") - .build().encodedPath) - .isEqualTo("/a/b/c/") - assertThat(base.newBuilder() - .addPathSegment("") - .addPathSegment("d") - .build().encodedPath) - .isEqualTo("/a/b/c/d") - assertThat(base.newBuilder() - .addPathSegment("..") - .build().encodedPath) - .isEqualTo("/a/b/") - assertThat(base.newBuilder() - .addPathSegment("") - .addPathSegment("..") - .build().encodedPath) - .isEqualTo("/a/b/") - assertThat(base.newBuilder() - .addPathSegment("") - .addPathSegment("") - .build().encodedPath) - .isEqualTo("/a/b/c/") - } - - @Test - fun addPathSegments() { - val base = parse("http://host/a/b/c") - - // Add a string with zero slashes: resulting URL gains one slash. - assertThat(base.newBuilder().addPathSegments("").build().encodedPath) - .isEqualTo("/a/b/c/") - assertThat(base.newBuilder().addPathSegments("d").build().encodedPath) - .isEqualTo("/a/b/c/d") - - // Add a string with one slash: resulting URL gains two slashes. - assertThat(base.newBuilder().addPathSegments("/").build().encodedPath) - .isEqualTo("/a/b/c//") - assertThat(base.newBuilder().addPathSegments("d/").build().encodedPath) - .isEqualTo("/a/b/c/d/") - assertThat(base.newBuilder().addPathSegments("/d").build().encodedPath) - .isEqualTo("/a/b/c//d") - - // Add a string with two slashes: resulting URL gains three slashes. - assertThat(base.newBuilder().addPathSegments("//").build().encodedPath) - .isEqualTo("/a/b/c///") - assertThat(base.newBuilder().addPathSegments("/d/").build().encodedPath) - .isEqualTo("/a/b/c//d/") - assertThat(base.newBuilder().addPathSegments("d//").build().encodedPath) - .isEqualTo("/a/b/c/d//") - assertThat(base.newBuilder().addPathSegments("//d").build().encodedPath) - .isEqualTo("/a/b/c///d") - assertThat(base.newBuilder().addPathSegments("d/e/f").build().encodedPath) - .isEqualTo("/a/b/c/d/e/f") - } - - @Test - fun addPathSegmentsOntoTrailingSlash() { - val base = parse("http://host/a/b/c/") - - // Add a string with zero slashes: resulting URL gains zero slashes. - assertThat(base.newBuilder().addPathSegments("").build().encodedPath) - .isEqualTo("/a/b/c/") - assertThat(base.newBuilder().addPathSegments("d").build().encodedPath) - .isEqualTo("/a/b/c/d") - - // Add a string with one slash: resulting URL gains one slash. - assertThat(base.newBuilder().addPathSegments("/").build().encodedPath) - .isEqualTo("/a/b/c//") - assertThat(base.newBuilder().addPathSegments("d/").build().encodedPath) - .isEqualTo("/a/b/c/d/") - assertThat(base.newBuilder().addPathSegments("/d").build().encodedPath) - .isEqualTo("/a/b/c//d") - - // Add a string with two slashes: resulting URL gains two slashes. - assertThat(base.newBuilder().addPathSegments("//").build().encodedPath) - .isEqualTo("/a/b/c///") - assertThat(base.newBuilder().addPathSegments("/d/").build().encodedPath) - .isEqualTo("/a/b/c//d/") - assertThat(base.newBuilder().addPathSegments("d//").build().encodedPath) - .isEqualTo("/a/b/c/d//") - assertThat(base.newBuilder().addPathSegments("//d").build().encodedPath) - .isEqualTo("/a/b/c///d") - assertThat(base.newBuilder().addPathSegments("d/e/f").build().encodedPath) - .isEqualTo("/a/b/c/d/e/f") - } - - @Test - fun addPathSegmentsWithBackslash() { - val base = parse("http://host/") - assertThat(base.newBuilder().addPathSegments("d\\e").build().encodedPath) - .isEqualTo("/d/e") - assertThat(base.newBuilder().addEncodedPathSegments("d\\e").build().encodedPath) - .isEqualTo("/d/e") - } - - @Test - fun addPathSegmentsWithEmptyPaths() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addPathSegments("/d/e///f").build().encodedPath) - .isEqualTo("/a/b/c//d/e///f") - } - - @Test - fun addEncodedPathSegments() { - val base = parse("http://host/a/b/c") - assertThat( - base.newBuilder().addEncodedPathSegments("d/e/%20/\n").build().encodedPath as Any - ).isEqualTo("/a/b/c/d/e/%20/") - } - - @Test - fun addPathSegmentDotDoesNothing() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addPathSegment(".").build().encodedPath) - .isEqualTo("/a/b/c") - } - - @Test - fun addPathSegmentEncodes() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addPathSegment("%2e").build().encodedPath) - .isEqualTo("/a/b/c/%252e") - assertThat(base.newBuilder().addPathSegment("%2e%2e").build().encodedPath) - .isEqualTo("/a/b/c/%252e%252e") - } - - @Test - fun addPathSegmentDotDotPopsDirectory() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addPathSegment("..").build().encodedPath) - .isEqualTo("/a/b/") - } - - @Test - fun addPathSegmentDotAndIgnoredCharacter() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addPathSegment(".\n").build().encodedPath) - .isEqualTo("/a/b/c/.%0A") - } - - @Test - fun addEncodedPathSegmentDotAndIgnoredCharacter() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addEncodedPathSegment(".\n").build().encodedPath) - .isEqualTo("/a/b/c") - } - - @Test - fun addEncodedPathSegmentDotDotAndIgnoredCharacter() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().addEncodedPathSegment("..\n").build().encodedPath) - .isEqualTo("/a/b/") - } - - @Test - fun setPathSegment() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().setPathSegment(0, "d").build().encodedPath) - .isEqualTo("/d/b/c") - assertThat(base.newBuilder().setPathSegment(1, "d").build().encodedPath) - .isEqualTo("/a/d/c") - assertThat(base.newBuilder().setPathSegment(2, "d").build().encodedPath) - .isEqualTo("/a/b/d") - } - - @Test - fun setPathSegmentAcceptsEmpty() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().setPathSegment(0, "").build().encodedPath) - .isEqualTo("//b/c") - assertThat(base.newBuilder().setPathSegment(2, "").build().encodedPath) - .isEqualTo("/a/b/") - } - - @Test - fun setPathSegmentRejectsDot() { - val base = parse("http://host/a/b/c") - assertFailsWith { - base.newBuilder().setPathSegment(0, ".") - } - } - - @Test - fun setPathSegmentRejectsDotDot() { - val base = parse("http://host/a/b/c") - assertFailsWith { - base.newBuilder().setPathSegment(0, "..") - } - } - - @Test - fun setPathSegmentOutOfBounds() { - assertFailsWith { - HttpUrl.Builder().setPathSegment(1, "a") - } - } - - @Test - fun setEncodedPathSegmentEncodes() { - val base = parse("http://host/a/b/c") - assertThat(base.newBuilder().setEncodedPathSegment(0, "%25").build().encodedPath) - .isEqualTo("/%25/b/c") - } - - @Test - fun setEncodedPathSegmentRejectsDot() { - val base = parse("http://host/a/b/c") - assertFailsWith { - base.newBuilder().setEncodedPathSegment(0, ".") - } - } - - @Test - fun setEncodedPathSegmentRejectsDotDot() { - val base = parse("http://host/a/b/c") - assertFailsWith { - base.newBuilder().setEncodedPathSegment(0, "..") - } - } - - @Test - fun setEncodedPathSegmentOutOfBounds() { - assertFailsWith { - HttpUrl.Builder().setEncodedPathSegment(1, "a") - } - } - - @Test - fun removePathSegment() { - val base = parse("http://host/a/b/c") - val url = base.newBuilder() - .removePathSegment(0) - .build() - assertThat(url.encodedPath).isEqualTo("/b/c") - } - - @Test - fun removePathSegmentDoesntRemovePath() { - val base = parse("http://host/a/b/c") - val url = base.newBuilder() - .removePathSegment(0) - .removePathSegment(0) - .removePathSegment(0) - .build() - assertThat(url.pathSegments).containsExactly("") - assertThat(url.encodedPath).isEqualTo("/") - } - - @Test - fun removePathSegmentOutOfBounds() { - assertFailsWith { - HttpUrl.Builder().removePathSegment(1) - } - } - - /** - * When callers use `addEncodedQueryParameter()` we only encode what's strictly required. We - * retain the encoded (or non-encoded) state of the input. - */ - @Test - fun queryCharactersNotReencodedWhenComposedWithAddEncoded() { - val url = HttpUrl.Builder() - .scheme("http") - .host("host") - .addEncodedQueryParameter("a", "!$(),/:;?@[]\\^`{|}~") - .build() - assertThat(url.toString()).isEqualTo("http://host/?a=!$(),/:;?@[]\\^`{|}~") - assertThat(url.queryParameter("a")).isEqualTo("!$(),/:;?@[]\\^`{|}~") - } - - /** - * When callers parse a URL with query components that aren't encoded, we shouldn't convert them - * into a canonical form because doing so could be semantically different. - */ - @Test - fun queryCharactersNotReencodedWhenParsed() { - val url = parse("http://host/?a=!$(),/:;?@[]\\^`{|}~") - assertThat(url.toString()).isEqualTo("http://host/?a=!$(),/:;?@[]\\^`{|}~") - assertThat(url.queryParameter("a")).isEqualTo("!$(),/:;?@[]\\^`{|}~") - } - - @Test - fun composeQueryRemoveQueryParameter() { - val url = parse("http://host/").newBuilder() - .addQueryParameter("a+=& b", "c+=& d") - .removeAllQueryParameters("a+=& b") - .build() - assertThat(url.toString()).isEqualTo("http://host/") - assertThat(url.queryParameter("a+=& b")).isNull() - } - - @Test - fun composeQueryRemoveEncodedQueryParameter() { - val url = parse("http://host/").newBuilder() - .addEncodedQueryParameter("a+=& b", "c+=& d") - .removeAllEncodedQueryParameters("a+=& b") - .build() - assertThat(url.toString()).isEqualTo("http://host/") - assertThat(url.queryParameter("a =& b")).isNull() - } - - @Test - fun absentQueryIsZeroNameValuePairs() { - val url = parse("http://host/").newBuilder() - .query(null) - .build() - assertThat(url.querySize).isEqualTo(0) - } - - @Test - fun emptyQueryIsSingleNameValuePairWithEmptyKey() { - val url = parse("http://host/").newBuilder() - .query("") - .build() - assertThat(url.querySize).isEqualTo(1) - assertThat(url.queryParameterName(0)).isEqualTo("") - assertThat(url.queryParameterValue(0)).isNull() - } - - @Test - fun ampersandQueryIsTwoNameValuePairsWithEmptyKeys() { - val url = parse("http://host/").newBuilder() - .query("&") - .build() - assertThat(url.querySize).isEqualTo(2) - assertThat(url.queryParameterName(0)).isEqualTo("") - assertThat(url.queryParameterValue(0)).isNull() - assertThat(url.queryParameterName(1)).isEqualTo("") - assertThat(url.queryParameterValue(1)).isNull() - } - - @Test - fun removeAllDoesNotRemoveQueryIfNoParametersWereRemoved() { - val url = parse("http://host/").newBuilder() - .query("") - .removeAllQueryParameters("a") - .build() - assertThat(url.toString()).isEqualTo("http://host/?") - } - - @Test - fun queryParametersWithRepeatedName() { - val url = parse("http://host/?foo[]=1&foo[]=2&foo[]=3") - assertThat(url.querySize).isEqualTo(3) - assertThat(url.queryParameterNames).isEqualTo(setOf("foo[]")) - assertThat(url.queryParameterValue(0)).isEqualTo("1") - assertThat(url.queryParameterValue(1)).isEqualTo("2") - assertThat(url.queryParameterValue(2)).isEqualTo("3") - assertThat(url.queryParameterValues("foo[]")).containsExactly("1", "2", "3") - } - - @Test - fun queryParameterLookupWithNonCanonicalEncoding() { - val url = parse("http://host/?%6d=m&+=%20") - assertThat(url.queryParameterName(0)).isEqualTo("m") - assertThat(url.queryParameterName(1)).isEqualTo(" ") - assertThat(url.queryParameter("m")).isEqualTo("m") - assertThat(url.queryParameter(" ")).isEqualTo(" ") - } - - @Test - fun parsedQueryDoesntIncludeFragment() { - val url = parse("http://host/?#fragment") - assertThat(url.fragment).isEqualTo("fragment") - assertThat(url.query).isEqualTo("") - assertThat(url.encodedQuery).isEqualTo("") - } - - /** - * Although HttpUrl prefers percent-encodings in uppercase, it should preserve the exact structure - * of the original encoding. - */ - @Test - fun rawEncodingRetained() { - val urlString = "http://%6d%6D:%6d%6D@host/%6d%6D?%6d%6D#%6d%6D" - val url = parse(urlString) - assertThat(url.encodedUsername).isEqualTo("%6d%6D") - assertThat(url.encodedPassword).isEqualTo("%6d%6D") - assertThat(url.encodedPath).isEqualTo("/%6d%6D") - assertThat(url.encodedPathSegments).containsExactly("%6d%6D") - assertThat(url.encodedQuery).isEqualTo("%6d%6D") - assertThat(url.encodedFragment).isEqualTo("%6d%6D") - assertThat(url.toString()).isEqualTo(urlString) - assertThat(url.newBuilder().build().toString()).isEqualTo(urlString) - assertThat(url.resolve("").toString()) - .isEqualTo("http://%6d%6D:%6d%6D@host/%6d%6D?%6d%6D") - } - - @Test - fun clearFragment() { - val url = parse("http://host/#fragment") - .newBuilder() - .fragment(null) - .build() - assertThat(url.toString()).isEqualTo("http://host/") - assertThat(url.fragment).isNull() - assertThat(url.encodedFragment).isNull() - } - - @Test - fun clearEncodedFragment() { - val url = parse("http://host/#fragment") - .newBuilder() - .encodedFragment(null) - .build() - assertThat(url.toString()).isEqualTo("http://host/") - assertThat(url.fragment).isNull() - assertThat(url.encodedFragment).isNull() - } - - @Test - fun unparseableTopPrivateDomain() { - assertInvalid("http://a../", "Invalid URL host: \"a..\"") - assertInvalid("http://..a/", "Invalid URL host: \"..a\"") - assertInvalid("http://a..b/", "Invalid URL host: \"a..b\"") - assertInvalid("http://.a/", "Invalid URL host: \".a\"") - assertInvalid("http://../", "Invalid URL host: \"..\"") - } - - @Test - fun trailingDotIsOkay() { - val name251 = "a.".repeat(125) + "a" - assertThat(parse("http://a./").toString()).isEqualTo("http://a./") - assertThat(parse("http://${name251}a./").toString()).isEqualTo("http://${name251}a./") - assertThat(parse("http://${name251}aa/").toString()).isEqualTo("http://${name251}aa/") - assertInvalid("http://${name251}aa./", "Invalid URL host: \"${name251}aa.\"") - } - - @Test - fun labelIsEmpty() { - assertInvalid("http:///", "Invalid URL host: \"\"") - assertInvalid("http://a..b/", "Invalid URL host: \"a..b\"") - assertInvalid("http://.a/", "Invalid URL host: \".a\"") - assertInvalid("http://./", "Invalid URL host: \".\"") - assertInvalid("http://../", "Invalid URL host: \"..\"") - assertInvalid("http://.../", "Invalid URL host: \"...\"") - assertInvalid("http://…/", "Invalid URL host: \"…\"") - } - - @Test - fun labelTooLong() { - val a63 = "a".repeat(63) - assertThat(parse("http://$a63/").toString()).isEqualTo("http://$a63/") - assertThat(parse("http://a.$a63/").toString()).isEqualTo("http://a.$a63/") - assertThat(parse("http://$a63.a/").toString()).isEqualTo("http://$a63.a/") - assertInvalid("http://a$a63/", "Invalid URL host: \"a$a63\"") - assertInvalid("http://a.a$a63/", "Invalid URL host: \"a.a$a63\"") - assertInvalid("http://a$a63.a/", "Invalid URL host: \"a$a63.a\"") - } - - @Test - fun hostnameTooLong() { - val dotA126 = "a.".repeat(126) - assertThat(parse("http://a$dotA126/").toString()) - .isEqualTo("http://a$dotA126/") - assertInvalid("http://aa$dotA126/", "Invalid URL host: \"aa$dotA126\"") - } -} diff --git a/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlTest.kt b/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlTest.kt index 4dda9dda4b31..2538b9d1c257 100644 --- a/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlTest.kt +++ b/okhttp/src/commonTest/kotlin/okhttp3/HttpUrlTest.kt @@ -18,14 +18,15 @@ package okhttp3 import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.containsExactlyInAnyOrder +import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isNull import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.fail import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.UrlComponentEncodingTester.Encoding @Suppress("HttpUrlsUsage") // Don't warn if we should be using https://. @@ -35,7 +36,19 @@ open class HttpUrlTest { } protected open fun assertInvalid(string: String, exceptionMessage: String?) { - assertThat(string.toHttpUrlOrNull()).isNull() + try { + val result = string.toHttpUrl() + if (exceptionMessage != null) { + fail("Expected failure with $exceptionMessage but got $result") + } else { + fail("Expected failure but got $result") + } + } catch(iae: IllegalArgumentException) { + iae.printStackTrace() + if (exceptionMessage != null) { + assertThat(iae).hasMessage(exceptionMessage) + } + } } @Test diff --git a/okhttp/src/jvmMain/kotlin/okhttp3/HttpUrl.kt b/okhttp/src/jvmMain/kotlin/okhttp3/HttpUrl.kt index f584c1f75fb8..34de98f442dd 100644 --- a/okhttp/src/jvmMain/kotlin/okhttp3/HttpUrl.kt +++ b/okhttp/src/jvmMain/kotlin/okhttp3/HttpUrl.kt @@ -15,13 +15,10 @@ */ package okhttp3 -import java.net.InetAddress import java.net.MalformedURLException import java.net.URI import java.net.URISyntaxException import java.net.URL -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.internal.CommonHttpUrl.FRAGMENT_ENCODE_SET_URI import okhttp3.internal.CommonHttpUrl.PATH_SEGMENT_ENCODE_SET_URI import okhttp3.internal.CommonHttpUrl.QUERY_COMPONENT_ENCODE_SET_URI @@ -73,256 +70,6 @@ import okhttp3.internal.HttpUrlCommon.canonicalize import okhttp3.internal.canParseAsIpAddress import okhttp3.internal.publicsuffix.PublicSuffixDatabase -/** - * A uniform resource locator (URL) with a scheme of either `http` or `https`. Use this class to - * compose and decompose Internet addresses. For example, this code will compose and print a URL for - * Google search: - * - * ```java - * HttpUrl url = new HttpUrl.Builder() - * .scheme("https") - * .host("www.google.com") - * .addPathSegment("search") - * .addQueryParameter("q", "polar bears") - * .build(); - * System.out.println(url); - * ``` - * - * which prints: - * - * ``` - * https://www.google.com/search?q=polar%20bears - * ``` - * - * As another example, this code prints the human-readable query parameters of a Twitter search: - * - * ```java - * HttpUrl url = HttpUrl.parse("https://twitter.com/search?q=cute%20%23puppies&f=images"); - * for (int i = 0, size = url.querySize(); i < size; i++) { - * System.out.println(url.queryParameterName(i) + ": " + url.queryParameterValue(i)); - * } - * ``` - * - * which prints: - * - * ``` - * q: cute #puppies - * f: images - * ``` - * - * In addition to composing URLs from their component parts and decomposing URLs into their - * component parts, this class implements relative URL resolution: what address you'd reach by - * clicking a relative link on a specified page. For example: - * - * ```java - * HttpUrl base = HttpUrl.parse("https://www.youtube.com/user/WatchTheDaily/videos"); - * HttpUrl link = base.resolve("../../watch?v=cbP2N1BQdYc"); - * System.out.println(link); - * ``` - * - * which prints: - * - * ``` - * https://www.youtube.com/watch?v=cbP2N1BQdYc - * ``` - * - * ## What's in a URL? - * - * A URL has several components. - * - * ### Scheme - * - * Sometimes referred to as *protocol*, A URL's scheme describes what mechanism should be used to - * retrieve the resource. Although URLs have many schemes (`mailto`, `file`, `ftp`), this class only - * supports `http` and `https`. Use [java.net.URI][URI] for URLs with arbitrary schemes. - * - * ### Username and Password - * - * Username and password are either present, or the empty string `""` if absent. This class offers - * no mechanism to differentiate empty from absent. Neither of these components are popular in - * practice. Typically HTTP applications use other mechanisms for user identification and - * authentication. - * - * ### Host - * - * The host identifies the webserver that serves the URL's resource. It is either a hostname like - * `square.com` or `localhost`, an IPv4 address like `192.168.0.1`, or an IPv6 address like `::1`. - * - * Usually a webserver is reachable with multiple identifiers: its IP addresses, registered - * domain names, and even `localhost` when connecting from the server itself. Each of a web server's - * names is a distinct URL and they are not interchangeable. For example, even if - * `http://square.github.io/dagger` and `http://google.github.io/dagger` are served by the same IP - * address, the two URLs identify different resources. - * - * ### Port - * - * The port used to connect to the web server. By default this is 80 for HTTP and 443 for HTTPS. - * This class never returns -1 for the port: if no port is explicitly specified in the URL then the - * scheme's default is used. - * - * ### Path - * - * The path identifies a specific resource on the host. Paths have a hierarchical structure like - * "/square/okhttp/issues/1486" and decompose into a list of segments like `["square", "okhttp", - * "issues", "1486"]`. - * - * This class offers methods to compose and decompose paths by segment. It composes each path - * from a list of segments by alternating between "/" and the encoded segment. For example the - * segments `["a", "b"]` build "/a/b" and the segments `["a", "b", ""]` build "/a/b/". - * - * If a path's last segment is the empty string then the path ends with "/". This class always - * builds non-empty paths: if the path is omitted it defaults to "/". The default path's segment - * list is a single empty string: `[""]`. - * - * ### Query - * - * The query is optional: it can be null, empty, or non-empty. For many HTTP URLs the query string - * is subdivided into a collection of name-value parameters. This class offers methods to set the - * query as the single string, or as individual name-value parameters. With name-value parameters - * the values are optional and names may be repeated. - * - * ### Fragment - * - * The fragment is optional: it can be null, empty, or non-empty. Unlike host, port, path, and - * query the fragment is not sent to the webserver: it's private to the client. - * - * ## Encoding - * - * Each component must be encoded before it is embedded in the complete URL. As we saw above, the - * string `cute #puppies` is encoded as `cute%20%23puppies` when used as a query parameter value. - * - * ### Percent encoding - * - * Percent encoding replaces a character (like `\ud83c\udf69`) with its UTF-8 hex bytes (like - * `%F0%9F%8D%A9`). This approach works for whitespace characters, control characters, non-ASCII - * characters, and characters that already have another meaning in a particular context. - * - * Percent encoding is used in every URL component except for the hostname. But the set of - * characters that need to be encoded is different for each component. For example, the path - * component must escape all of its `?` characters, otherwise it could be interpreted as the - * start of the URL's query. But within the query and fragment components, the `?` character - * doesn't delimit anything and doesn't need to be escaped. - * - * ```java - * HttpUrl url = HttpUrl.parse("http://who-let-the-dogs.out").newBuilder() - * .addPathSegment("_Who?_") - * .query("_Who?_") - * .fragment("_Who?_") - * .build(); - * System.out.println(url); - * ``` - * - * This prints: - * - * ``` - * http://who-let-the-dogs.out/_Who%3F_?_Who?_#_Who?_ - * ``` - * - * When parsing URLs that lack percent encoding where it is required, this class will percent encode - * the offending characters. - * - * ### IDNA Mapping and Punycode encoding - * - * Hostnames have different requirements and use a different encoding scheme. It consists of IDNA - * mapping and Punycode encoding. - * - * In order to avoid confusion and discourage phishing attacks, [IDNA Mapping][idna] transforms - * names to avoid confusing characters. This includes basic case folding: transforming shouting - * `SQUARE.COM` into cool and casual `square.com`. It also handles more exotic characters. For - * example, the Unicode trademark sign (™) could be confused for the letters "TM" in - * `http://ho™ail.com`. To mitigate this, the single character (™) maps to the string (tm). There - * is similar policy for all of the 1.1 million Unicode code points. Note that some code points such - * as "\ud83c\udf69" are not mapped and cannot be used in a hostname. - * - * [Punycode](http://ietf.org/rfc/rfc3492.txt) converts a Unicode string to an ASCII string to make - * international domain names work everywhere. For example, "σ" encodes as "xn--4xa". The encoded - * string is not human readable, but can be used with classes like [InetAddress] to establish - * connections. - * - * ## Why another URL model? - * - * Java includes both [java.net.URL][URL] and [java.net.URI][URI]. We offer a new URL - * model to address problems that the others don't. - * - * ### Different URLs should be different - * - * Although they have different content, `java.net.URL` considers the following two URLs - * equal, and the [equals()][Object.equals] method between them returns true: - * - * * https://example.net/ - * - * * https://example.com/ - * - * This is because those two hosts share the same IP address. This is an old, bad design decision - * that makes `java.net.URL` unusable for many things. It shouldn't be used as a [Map] key or in a - * [Set]. Doing so is both inefficient because equality may require a DNS lookup, and incorrect - * because unequal URLs may be equal because of how they are hosted. - * - * ### Equal URLs should be equal - * - * These two URLs are semantically identical, but `java.net.URI` disagrees: - * - * * http://host:80/ - * - * * http://host - * - * Both the unnecessary port specification (`:80`) and the absent trailing slash (`/`) cause URI to - * bucket the two URLs separately. This harms URI's usefulness in collections. Any application that - * stores information-per-URL will need to either canonicalize manually, or suffer unnecessary - * redundancy for such URLs. - * - * Because they don't attempt canonical form, these classes are surprisingly difficult to use - * securely. Suppose you're building a webservice that checks that incoming paths are prefixed - * "/static/images/" before serving the corresponding assets from the filesystem. - * - * ```java - * String attack = "http://example.com/static/images/../../../../../etc/passwd"; - * System.out.println(new URL(attack).getPath()); - * System.out.println(new URI(attack).getPath()); - * System.out.println(HttpUrl.parse(attack).encodedPath()); - * ``` - * - * By canonicalizing the input paths, they are complicit in directory traversal attacks. Code that - * checks only the path prefix may suffer! - * - * ``` - * /static/images/../../../../../etc/passwd - * /static/images/../../../../../etc/passwd - * /etc/passwd - * ``` - * - * ### If it works on the web, it should work in your application - * - * The `java.net.URI` class is strict around what URLs it accepts. It rejects URLs like - * `http://example.com/abc|def` because the `|` character is unsupported. This class is more - * forgiving: it will automatically percent-encode the `|'` yielding `http://example.com/abc%7Cdef`. - * This kind behavior is consistent with web browsers. `HttpUrl` prefers consistency with major web - * browsers over consistency with obsolete specifications. - * - * ### Paths and Queries should decompose - * - * Neither of the built-in URL models offer direct access to path segments or query parameters. - * Manually using `StringBuilder` to assemble these components is cumbersome: do '+' characters get - * silently replaced with spaces? If a query parameter contains a '&', does that get escaped? - * By offering methods to read and write individual query parameters directly, application - * developers are saved from the hassles of encoding and decoding. - * - * ### Plus a modern API - * - * The URL (JDK1.0) and URI (Java 1.4) classes predate builders and instead use telescoping - * constructors. For example, there's no API to compose a URI with a custom port without also - * providing a query and fragment. - * - * Instances of [HttpUrl] are well-formed and always have a scheme, host, and path. With - * `java.net.URL` it's possible to create an awkward URL like `http:/` with scheme and path but no - * hostname. Building APIs that consume such malformed values is difficult! - * - * This class has a modern API. It avoids punitive checked exceptions: [toHttpUrl] throws - * [IllegalArgumentException] on invalid input or [toHttpUrlOrNull] returns null if the input is an - * invalid URL. You can even be explicit about whether each component has been encoded already. - * - * [idna]: http://www.unicode.org/reports/tr46/#ToASCII - */ actual class HttpUrl internal actual constructor( @get:JvmName("scheme") actual val scheme: String, @@ -336,11 +83,6 @@ actual class HttpUrl internal actual constructor( @get:JvmName("pathSegments") actual val pathSegments: List, - /** - * Alternating, decoded query names and values, or null for no query. Names may be empty or - * non-empty, but never null. Values are null if the name has no corresponding '=' separator, or - * empty, or non-empty. - */ internal actual val queryNamesAndValues: List?, @get:JvmName("fragment") actual val fragment: String?, @@ -610,9 +352,6 @@ actual class HttpUrl internal actual constructor( internal actual var encodedQueryNamesAndValues: MutableList? = null internal actual var encodedFragment: String? = null - /** - * @param scheme either "http" or "https". - */ actual fun scheme(scheme: String) = commonScheme(scheme) actual fun username(username: String) = commonUsername(username) @@ -623,31 +362,18 @@ actual class HttpUrl internal actual constructor( actual fun encodedPassword(encodedPassword: String) = commonEncodedPassword(encodedPassword) - /** - * @param host either a regular hostname, International Domain Name, IPv4 address, or IPv6 - * address. - */ actual fun host(host: String) = commonHost(host) actual fun port(port: Int) = commonPort(port) actual fun addPathSegment(pathSegment: String) = commonAddPathSegment(pathSegment) - /** - * Adds a set of path segments separated by a slash (either `\` or `/`). If `pathSegments` - * starts with a slash, the resulting URL will have empty path segment. - */ actual fun addPathSegments(pathSegments: String): Builder = commonAddPathSegments(pathSegments) actual fun addEncodedPathSegment(encodedPathSegment: String) = commonAddEncodedPathSegment(encodedPathSegment) - /** - * Adds a set of encoded path segments separated by a slash (either `\` or `/`). If - * `encodedPathSegments` starts with a slash, the resulting URL will have empty path segment. - */ actual fun addEncodedPathSegments(encodedPathSegments: String): Builder = commonAddEncodedPathSegments(encodedPathSegments) - actual fun setPathSegment(index: Int, pathSegment: String) = commonSetPathSegment(index, pathSegment) actual fun setEncodedPathSegment(index: Int, encodedPathSegment: String) = commonSetEncodedPathSegment(index, encodedPathSegment) @@ -660,10 +386,8 @@ actual class HttpUrl internal actual constructor( actual fun encodedQuery(encodedQuery: String?) = commonEncodedQuery(encodedQuery) - /** Encodes the query parameter using UTF-8 and adds it to this URL's query string. */ actual fun addQueryParameter(name: String, value: String?) = commonAddQueryParameter(name, value) - /** Adds the pre-encoded query parameter to this URL's query string. */ actual fun addEncodedQueryParameter(encodedName: String, encodedValue: String?) = commonAddEncodedQueryParameter(encodedName, encodedValue) actual fun setQueryParameter(name: String, value: String?) = commonSetQueryParameter(name, value) @@ -687,9 +411,9 @@ actual class HttpUrl internal actual constructor( for (i in 0 until encodedPathSegments.size) { encodedPathSegments[i] = encodedPathSegments[i].canonicalize( - encodeSet = PATH_SEGMENT_ENCODE_SET_URI, - alreadyEncoded = true, - strict = true + encodeSet = PATH_SEGMENT_ENCODE_SET_URI, + alreadyEncoded = true, + strict = true ) } @@ -697,19 +421,19 @@ actual class HttpUrl internal actual constructor( if (encodedQueryNamesAndValues != null) { for (i in 0 until encodedQueryNamesAndValues.size) { encodedQueryNamesAndValues[i] = encodedQueryNamesAndValues[i]?.canonicalize( - encodeSet = QUERY_COMPONENT_ENCODE_SET_URI, - alreadyEncoded = true, - strict = true, - plusIsSpace = true + encodeSet = QUERY_COMPONENT_ENCODE_SET_URI, + alreadyEncoded = true, + strict = true, + plusIsSpace = true ) } } encodedFragment = encodedFragment?.canonicalize( - encodeSet = FRAGMENT_ENCODE_SET_URI, - alreadyEncoded = true, - strict = true, - unicodeAllowed = true + encodeSet = FRAGMENT_ENCODE_SET_URI, + alreadyEncoded = true, + strict = true, + unicodeAllowed = true ) } @@ -736,45 +460,55 @@ actual class HttpUrl internal actual constructor( * other protocol. */ @JvmStatic - @JvmName("get") fun URL.toHttpUrlOrNull(): HttpUrl? = toString().toHttpUrlOrNull() + @JvmName("get") + fun URL.toHttpUrlOrNull(): HttpUrl? = toString().toHttpUrlOrNull() @JvmStatic - @JvmName("get") fun URI.toHttpUrlOrNull(): HttpUrl? = toString().toHttpUrlOrNull() + @JvmName("get") + fun URI.toHttpUrlOrNull(): HttpUrl? = toString().toHttpUrlOrNull() @JvmName("-deprecated_get") @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "url.toHttpUrl()", - imports = ["okhttp3.HttpUrl.Companion.toHttpUrl"]), - level = DeprecationLevel.ERROR) + message = "moved to extension function", + replaceWith = ReplaceWith( + expression = "url.toHttpUrl()", + imports = ["okhttp3.HttpUrl.Companion.toHttpUrl"] + ), + level = DeprecationLevel.ERROR + ) fun get(url: String): HttpUrl = url.toHttpUrl() @JvmName("-deprecated_parse") @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "url.toHttpUrlOrNull()", - imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"]), - level = DeprecationLevel.ERROR) + message = "moved to extension function", + replaceWith = ReplaceWith( + expression = "url.toHttpUrlOrNull()", + imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"] + ), + level = DeprecationLevel.ERROR + ) fun parse(url: String): HttpUrl? = url.toHttpUrlOrNull() @JvmName("-deprecated_get") @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "url.toHttpUrlOrNull()", - imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"]), - level = DeprecationLevel.ERROR) + message = "moved to extension function", + replaceWith = ReplaceWith( + expression = "url.toHttpUrlOrNull()", + imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"] + ), + level = DeprecationLevel.ERROR + ) fun get(url: URL): HttpUrl? = url.toHttpUrlOrNull() @JvmName("-deprecated_get") @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "uri.toHttpUrlOrNull()", - imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"]), - level = DeprecationLevel.ERROR) + message = "moved to extension function", + replaceWith = ReplaceWith( + expression = "uri.toHttpUrlOrNull()", + imports = ["okhttp3.HttpUrl.Companion.toHttpUrlOrNull"] + ), + level = DeprecationLevel.ERROR + ) fun get(uri: URI): HttpUrl? = uri.toHttpUrlOrNull() } } diff --git a/okhttp/src/jvmTest/java/okhttp3/HttpUrlGetTest.java b/okhttp/src/jvmTest/java/okhttp3/HttpUrlGetTest.java deleted file mode 100644 index 1eb74e77ba65..000000000000 --- a/okhttp/src/jvmTest/java/okhttp3/HttpUrlGetTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2020 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -public class HttpUrlGetTest extends HttpUrlTest { - @Override - protected HttpUrl parse(String url) { - return HttpUrl.get(url); - } - - @Override - protected void assertInvalid(String string, String exceptionMessage) { - try { - parse(string); - fail("Expected get of \"" + string + "\" to throw with: " + exceptionMessage); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo(exceptionMessage); - } - } -}