All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmTest.okhttp3.HttpUrlTest.kt Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * 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 java.net.URI
import java.net.URL
import kotlin.test.assertFailsWith
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.UrlComponentEncodingTester.Encoding
import okhttp3.testing.PlatformRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test

@Suppress("HttpUrlsUsage") // Don't warn if we should be using https://.
open class HttpUrlTest {
  val platform = PlatformRule()

  protected open fun parse(url: String): HttpUrl {
    return url.toHttpUrl()
  }

  protected open fun assertInvalid(string: String, exceptionMessage: String?) {
    assertThat(string.toHttpUrlOrNull()).overridingErrorMessage(string).isNull()
  }

  @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 scheme() {
    assertThat(parse("http://host/")).isEqualTo(parse("http://host/"))
    assertThat(parse("Http://host/")).isEqualTo(parse("http://host/"))
    assertThat(parse("http://host/")).isEqualTo(parse("http://host/"))
    assertThat(parse("HTTP://host/")).isEqualTo(parse("http://host/"))
    assertThat(parse("https://host/")).isEqualTo(parse("https://host/"))
    assertThat(parse("HTTPS://host/")).isEqualTo(parse("https://host/"))
    assertInvalid(
      "image640://480.png",
      "Expected URL scheme 'http' or 'https' but was 'image640'"
    )
    assertInvalid("httpp://host/", "Expected URL scheme 'http' or 'https' but was 'httpp'")
    assertInvalid(
      "0ttp://host/",
      "Expected URL scheme 'http' or 'https' but no scheme was found for 0ttp:/..."
    )
    assertInvalid("ht+tp://host/", "Expected URL scheme 'http' or 'https' but was 'ht+tp'")
    assertInvalid("ht.tp://host/", "Expected URL scheme 'http' or 'https' but was 'ht.tp'")
    assertInvalid("ht-tp://host/", "Expected URL scheme 'http' or 'https' but was 'ht-tp'")
    assertInvalid("ht1tp://host/", "Expected URL scheme 'http' or 'https' but was 'ht1tp'")
    assertInvalid("httpss://host/", "Expected URL scheme 'http' or 'https' but was 'httpss'")
  }

  @Test
  fun parseNoScheme() {
    assertInvalid(
      "//host",
      "Expected URL scheme 'http' or 'https' but no scheme was found for //host"
    )
    assertInvalid(
      "://host",
      "Expected URL scheme 'http' or 'https' but no scheme was found for ://hos..."
    )
    assertInvalid(
      "/path",
      "Expected URL scheme 'http' or 'https' but no scheme was found for /path"
    )
    assertInvalid(
      "path",
      "Expected URL scheme 'http' or 'https' but no scheme was found for path"
    )
    assertInvalid(
      "?query",
      "Expected URL scheme 'http' or 'https' but no scheme was found for ?query"
    )
    assertInvalid(
      "#fragment",
      "Expected URL scheme 'http' or 'https' but no scheme was found for #fragm..."
    )
  }

  @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 usernameCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(
        Encoding.PERCENT,
        '['.toInt(),
        ']'.toInt(),
        '{'.toInt(),
        '}'.toInt(),
        '|'.toInt(),
        '^'.toInt(),
        '\''.toInt(),
        ';'.toInt(),
        '='.toInt(),
        '@'.toInt()
      )
      .override(
        Encoding.SKIP,
        ':'.toInt(),
        '/'.toInt(),
        '\\'.toInt(),
        '?'.toInt(),
        '#'.toInt()
      )
      .escapeForUri('%'.toInt())
      .test(UrlComponentEncodingTester.Component.USER)
  }

  @Test
  fun passwordCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(
        Encoding.PERCENT,
        '['.toInt(),
        ']'.toInt(),
        '{'.toInt(),
        '}'.toInt(),
        '|'.toInt(),
        '^'.toInt(),
        '\''.toInt(),
        ':'.toInt(),
        ';'.toInt(),
        '='.toInt(),
        '@'.toInt()
      )
      .override(
        Encoding.SKIP,
        '/'.toInt(),
        '\\'.toInt(),
        '?'.toInt(),
        '#'.toInt()
      )
      .escapeForUri('%'.toInt())
      .test(UrlComponentEncodingTester.Component.PASSWORD)
  }

  @Test
  fun hostContainsIllegalCharacter() {
    assertInvalid("http://\n/", "Invalid URL host: \"\n\"")
    assertInvalid("http:// /", "Invalid URL host: \" \"")
    assertInvalid("http://%20/", "Invalid URL host: \"%20\"")
  }

  @Test
  fun hostnameLowercaseCharactersMappedDirectly() {
    assertThat(parse("http://abcd").host).isEqualTo("abcd")
    assertThat(parse("http://σ").host).isEqualTo("xn--4xa")
  }

  @Test
  fun hostnameUppercaseCharactersConvertedToLowercase() {
    assertThat(parse("http://ABCD").host).isEqualTo("abcd")
    assertThat(parse("http://Σ").host).isEqualTo("xn--4xa")
  }

  @Test
  fun hostnameIgnoredCharacters() {
    // The soft hyphen (­) should be ignored.
    assertThat(parse("http://AB\u00adCD").host).isEqualTo("abcd")
  }

  @Test
  fun hostnameMultipleCharacterMapping() {
    // Map the single character telephone symbol (℡) to the string "tel".
    assertThat(parse("http://\u2121").host).isEqualTo("tel")
  }

  @Test
  fun hostnameMappingLastMappedCodePoint() {
    assertThat(parse("http://\uD87E\uDE1D").host).isEqualTo("xn--pu5l")
  }

  @Disabled("The java.net.IDN implementation doesn't ignore characters that it should.")
  @Test
  @Throws(
    Exception::class
  )
  fun hostnameMappingLastIgnoredCodePoint() {
    assertThat(parse("http://ab\uDB40\uDDEFcd").host).isEqualTo("abcd")
  }

  @Test
  fun hostnameMappingLastDisallowedCodePoint() {
    assertInvalid("http://\uDBFF\uDFFF", "Invalid URL host: \"\uDBFF\uDFFF\"")
  }

  @Test
  fun hostnameUri() {
    // Host names are special:
    //
    //  * Several characters are forbidden and must throw exceptions if used.
    //  * They don't use percent escaping at all.
    //  * They use punycode for internationalization.
    //  * URI is much more strict that HttpUrl or URL on what's accepted.
    //
    // HttpUrl is quite lenient with what characters it accepts. In particular, characters like '{'
    // and '"' are permitted but unlikely to occur in real-world URLs. Unfortunately we can't just
    // lock it down due to URL templating: "http://{env}.{dc}.example.com".
    UrlComponentEncodingTester.newInstance()
      .nonPrintableAscii(Encoding.FORBIDDEN)
      .nonAscii(Encoding.FORBIDDEN)
      .override(
        Encoding.FORBIDDEN,
        '\t'.toInt(),
        '\n'.toInt(),
        '\u000c'.toInt(),
        '\r'.toInt(),
        ' '.toInt()
      )
      .override(
        Encoding.FORBIDDEN,
        '#'.toInt(),
        '%'.toInt(),
        '/'.toInt(),
        ':'.toInt(),
        '?'.toInt(),
        '@'.toInt(),
        '['.toInt(),
        '\\'.toInt(),
        ']'.toInt()
      )
      .override(
        Encoding.IDENTITY,
        '\"'.toInt(),
        '<'.toInt(),
        '>'.toInt(),
        '^'.toInt(),
        '`'.toInt(),
        '{'.toInt(),
        '|'.toInt(),
        '}'.toInt()
      )
      .stripForUri(
        '\"'.toInt(),
        '<'.toInt(),
        '>'.toInt(),
        '^'.toInt(),
        '`'.toInt(),
        '{'.toInt(),
        '|'.toInt(),
        '}'.toInt()
      )
      .test(UrlComponentEncodingTester.Component.HOST)
  }

  /**
   * This one's ugly: the HttpUrl's host is non-empty, but the URI's host is null.
   */
  @Test
  fun hostContainsOnlyStrippedCharacters() {
    val url = parse("http://>/")
    assertThat(url.host).isEqualTo(">")
    assertThat(url.toUri().host).isNull()
  }

  @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]\"")
  }

  @Test
  fun hostIpv6CanonicalForm() {
    assertThat(parse("http://[abcd:ef01:2345:6789:abcd:ef01:2345:6789]/").host)
      .isEqualTo("abcd:ef01:2345:6789:abcd:ef01:2345:6789")
    assertThat(parse("http://[a:0:0:0:b:0:0:0]/").host).isEqualTo("a::b:0:0:0")
    assertThat(parse("http://[a:b:0:0:c:0:0:0]/").host).isEqualTo("a:b:0:0:c::")
    assertThat(parse("http://[a:b:0:0:0:c:0:0]/").host).isEqualTo("a:b::c:0:0")
    assertThat(parse("http://[a:0:0:0:b:0:0:0]/").host).isEqualTo("a::b:0:0:0")
    assertThat(parse("http://[0:0:0:a:b:0:0:0]/").host).isEqualTo("::a:b:0:0:0")
    assertThat(parse("http://[0:0:0:a:0:0:0:b]/").host).isEqualTo("::a:0:0:0:b")
    assertThat(parse("http://[0:a:b:c:d:e:f:1]/").host).isEqualTo("0:a:b:c:d:e:f:1")
    assertThat(parse("http://[a:b:c:d:e:f:1:0]/").host).isEqualTo("a:b:c:d:e:f:1:0")
    assertThat(parse("http://[FF01:0:0:0:0:0:0:101]/").host).isEqualTo("ff01::101")
    assertThat(parse("http://[2001:db8::1]/").host).isEqualTo("2001:db8::1")
    assertThat(parse("http://[2001:db8:0:0:0:0:2:1]/").host).isEqualTo("2001:db8::2:1")
    assertThat(parse("http://[2001:db8:0:1:1:1:1:1]/").host).isEqualTo("2001:db8:0:1:1:1:1:1")
    assertThat(parse("http://[2001:db8:0:0:1:0:0:1]/").host).isEqualTo("2001:db8::1:0:0:1")
    assertThat(parse("http://[2001:0:0:1:0:0:0:1]/").host).isEqualTo("2001:0:0:1::1")
    assertThat(parse("http://[1:0:0:0:0:0:0:0]/").host).isEqualTo("1::")
    assertThat(parse("http://[0:0:0:0:0:0:0:1]/").host).isEqualTo("::1")
    assertThat(parse("http://[0:0:0:0:0:0:0:0]/").host).isEqualTo("::")
    assertThat(parse("http://[::ffff:c0a8:1fe]/").host).isEqualTo("192.168.1.254")
  }

  /**
   * 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 hostIpv4CanonicalForm() {
    assertThat(parse("http://255.255.255.255/").host).isEqualTo("255.255.255.255")
    assertThat(parse("http://1.2.3.4/").host).isEqualTo("1.2.3.4")
    assertThat(parse("http://0.0.0.0/").host).isEqualTo("0.0.0.0")
  }

  @Test
  fun hostWithTrailingDot() {
    assertThat(parse("http://host./").host).isEqualTo("host.")
  }

  /**
   * Strip unexpected characters when converting to URI (which is more strict).
   * https://github.com/square/okhttp/issues/5667
   */
  @Test
  fun hostToUriStripsCharacters() {
    val httpUrl = "http://example\".com/".toHttpUrl()
    assertThat(httpUrl.toUri().toString()).isEqualTo("http://example.com/")
  }

  /**
   * Confirm that URI retains other characters. https://github.com/square/okhttp/issues/5236
   */
  @Test
  fun hostToUriStripsCharacters2() {
    val httpUrl = "http://\${tracker}/".toHttpUrl()
    assertThat(httpUrl.toUri().toString()).isEqualTo("http://\$tracker/")
  }

  @Test
  fun port() {
    assertThat(parse("http://host:80/")).isEqualTo(parse("http://host/"))
    assertThat(parse("http://host:99/")).isEqualTo(parse("http://host:99/"))
    assertThat(parse("http://host:/")).isEqualTo(parse("http://host/"))
    assertThat(parse("http://host:65535/").port).isEqualTo(65535)
    assertInvalid("http://host:0/", "Invalid URL port: \"0\"")
    assertInvalid("http://host:65536/", "Invalid URL port: \"65536\"")
    assertInvalid("http://host:-1/", "Invalid URL port: \"-1\"")
    assertInvalid("http://host:a/", "Invalid URL port: \"a\"")
    assertInvalid("http://host:%39%39/", "Invalid URL port: \"%39%39\"")
  }

  @Test
  fun pathCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(
        Encoding.PERCENT,
        '^'.toInt(),
        '{'.toInt(),
        '}'.toInt(),
        '|'.toInt()
      )
      .override(
        Encoding.SKIP,
        '\\'.toInt(),
        '?'.toInt(),
        '#'.toInt()
      )
      .escapeForUri('%'.toInt(), '['.toInt(), ']'.toInt())
      .test(UrlComponentEncodingTester.Component.PATH)
  }

  @Test
  fun queryCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(Encoding.IDENTITY, '?'.toInt(), '`'.toInt())
      .override(Encoding.PERCENT, '\''.toInt())
      .override(Encoding.SKIP, '#'.toInt(), '+'.toInt())
      .escapeForUri(
        '%'.toInt(),
        '\\'.toInt(),
        '^'.toInt(),
        '`'.toInt(),
        '{'.toInt(),
        '|'.toInt(),
        '}'.toInt()
      )
      .test(UrlComponentEncodingTester.Component.QUERY)
  }

  @Test
  fun queryValueCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(Encoding.IDENTITY, '?'.toInt(), '`'.toInt())
      .override(Encoding.PERCENT, '\''.toInt())
      .override(Encoding.SKIP, '#'.toInt(), '+'.toInt())
      .escapeForUri(
        '%'.toInt(),
        '\\'.toInt(),
        '^'.toInt(),
        '`'.toInt(),
        '{'.toInt(),
        '|'.toInt(),
        '}'.toInt()
      )
      .test(UrlComponentEncodingTester.Component.QUERY_VALUE)
  }

  @Test
  fun fragmentCharacters() {
    UrlComponentEncodingTester.newInstance()
      .override(
        Encoding.IDENTITY,
        ' '.toInt(),
        '"'.toInt(),
        '#'.toInt(),
        '<'.toInt(),
        '>'.toInt(),
        '?'.toInt(),
        '`'.toInt()
      )
      .escapeForUri(
        '%'.toInt(),
        ' '.toInt(),
        '"'.toInt(),
        '#'.toInt(),
        '<'.toInt(),
        '>'.toInt(),
        '\\'.toInt(),
        '^'.toInt(),
        '`'.toInt(),
        '{'.toInt(),
        '|'.toInt(),
        '}'.toInt()
      )
      .nonAscii(Encoding.IDENTITY)
      .test(UrlComponentEncodingTester.Component.FRAGMENT)
  }

  @Test
  fun fragmentNonAscii() {
    val url = parse("http://host/#Σ")
    assertThat(url.toString()).isEqualTo("http://host/#Σ")
    assertThat(url.fragment).isEqualTo("Σ")
    assertThat(url.encodedFragment).isEqualTo("Σ")
    assertThat(url.toUri().toString()).isEqualTo("http://host/#Σ")
  }

  @Test
  fun fragmentNonAsciiThatOffendsJavaNetUri() {
    val url = parse("http://host/#\u0080")
    assertThat(url.toString()).isEqualTo("http://host/#\u0080")
    assertThat(url.fragment).isEqualTo("\u0080")
    assertThat(url.encodedFragment).isEqualTo("\u0080")
    // Control characters may be stripped!
    assertThat(url.toUri()).isEqualTo(URI("http://host/#"))
  }

  @Test
  fun fragmentPercentEncodedNonAscii() {
    val url = parse("http://host/#%C2%80")
    assertThat(url.toString()).isEqualTo("http://host/#%C2%80")
    assertThat(url.fragment).isEqualTo("\u0080")
    assertThat(url.encodedFragment).isEqualTo("%C2%80")
    assertThat(url.toUri().toString()).isEqualTo("http://host/#%C2%80")
  }

  @Test
  fun fragmentPercentEncodedPartialCodePoint() {
    val url = parse("http://host/#%80")
    assertThat(url.toString()).isEqualTo("http://host/#%80")
    // Unicode replacement character.
    assertThat(url.fragment).isEqualTo("\ufffd")
    assertThat(url.encodedFragment).isEqualTo("%80")
    assertThat(url.toUri().toString()).isEqualTo("http://host/#%80")
  }

  @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 minimalUrlComposition() {
    val url = HttpUrl.Builder().scheme("http").host("host").build()
    assertThat(url.toString()).isEqualTo("http://host/")
    assertThat(url.scheme).isEqualTo("http")
    assertThat(url.username).isEqualTo("")
    assertThat(url.password).isEqualTo("")
    assertThat(url.host).isEqualTo("host")
    assertThat(url.port).isEqualTo(80)
    assertThat(url.encodedPath).isEqualTo("/")
    assertThat(url.query).isNull()
    assertThat(url.fragment).isNull()
  }

  @Test
  fun fullUrlComposition() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .username("username")
      .password("password")
      .host("host")
      .port(8080)
      .addPathSegment("path")
      .query("query")
      .fragment("fragment")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://username:password@host:8080/path?query#fragment")
    assertThat(url.scheme).isEqualTo("http")
    assertThat(url.username).isEqualTo("username")
    assertThat(url.password).isEqualTo("password")
    assertThat(url.host).isEqualTo("host")
    assertThat(url.port).isEqualTo(8080)
    assertThat(url.encodedPath).isEqualTo("/path")
    assertThat(url.query).isEqualTo("query")
    assertThat(url.fragment).isEqualTo("fragment")
  }

  @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 composeEncodesWhitespace() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .username("a\r\n\u000c\t b")
      .password("c\r\n\u000c\t d")
      .host("host")
      .addPathSegment("e\r\n\u000c\t f")
      .query("g\r\n\u000c\t h")
      .fragment("i\r\n\u000c\t j")
      .build()
    assertThat(url.toString()).isEqualTo(
      "http://a%0D%0A%0C%09%20b:c%0D%0A%0C%09%20d@host"
        + "/e%0D%0A%0C%09%20f?g%0D%0A%0C%09%20h#i%0D%0A%0C%09 j"
    )
    assertThat(url.username).isEqualTo("a\r\n\u000c\t b")
    assertThat(url.password).isEqualTo("c\r\n\u000c\t d")
    assertThat(url.pathSegments[0]).isEqualTo("e\r\n\u000c\t f")
    assertThat(url.query).isEqualTo("g\r\n\u000c\t h")
    assertThat(url.fragment).isEqualTo("i\r\n\u000c\t j")
  }

  @Test
  fun composeFromUnencodedComponents() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .username("a:\u0001@/\\?#%b")
      .password("c:\u0001@/\\?#%d")
      .host("ef")
      .port(8080)
      .addPathSegment("g:\u0001@/\\?#%h")
      .query("i:\u0001@/\\?#%j")
      .fragment("k:\u0001@/\\?#%l")
      .build()
    assertThat(url.toString())
      .isEqualTo(
        "http://a%3A%01%40%2F%5C%3F%23%25b:c%3A%01%40%2F%5C%3F%23%25d@ef:8080/"
          + "g:%01@%2F%5C%3F%23%25h?i:%01@/\\?%23%25j#k:%01@/\\?#%25l"
      )
    assertThat(url.scheme).isEqualTo("http")
    assertThat(url.username).isEqualTo("a:\u0001@/\\?#%b")
    assertThat(url.password).isEqualTo("c:\u0001@/\\?#%d")
    assertThat(url.pathSegments).containsExactly("g:\u0001@/\\?#%h")
    assertThat(url.query).isEqualTo("i:\u0001@/\\?#%j")
    assertThat(url.fragment).isEqualTo("k:\u0001@/\\?#%l")
    assertThat(url.encodedUsername).isEqualTo("a%3A%01%40%2F%5C%3F%23%25b")
    assertThat(url.encodedPassword).isEqualTo("c%3A%01%40%2F%5C%3F%23%25d")
    assertThat(url.encodedPath).isEqualTo("/g:%01@%2F%5C%3F%23%25h")
    assertThat(url.encodedQuery).isEqualTo("i:%01@/\\?%23%25j")
    assertThat(url.encodedFragment).isEqualTo("k:%01@/\\?#%25l")
  }

  @Test
  fun composeFromEncodedComponents() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .encodedUsername("a:\u0001@/\\?#%25b")
      .encodedPassword("c:\u0001@/\\?#%25d")
      .host("ef")
      .port(8080)
      .addEncodedPathSegment("g:\u0001@/\\?#%25h")
      .encodedQuery("i:\u0001@/\\?#%25j")
      .encodedFragment("k:\u0001@/\\?#%25l")
      .build()
    assertThat(url.toString())
      .isEqualTo(
        "http://a%3A%01%40%2F%5C%3F%23%25b:c%3A%01%40%2F%5C%3F%23%25d@ef:8080/"
          + "g:%01@%2F%5C%3F%23%25h?i:%01@/\\?%23%25j#k:%01@/\\?#%25l"
      )
    assertThat(url.scheme).isEqualTo("http")
    assertThat(url.username).isEqualTo("a:\u0001@/\\?#%b")
    assertThat(url.password).isEqualTo("c:\u0001@/\\?#%d")
    assertThat(url.pathSegments).containsExactly("g:\u0001@/\\?#%h")
    assertThat(url.query).isEqualTo("i:\u0001@/\\?#%j")
    assertThat(url.fragment).isEqualTo("k:\u0001@/\\?#%l")
    assertThat(url.encodedUsername).isEqualTo("a%3A%01%40%2F%5C%3F%23%25b")
    assertThat(url.encodedPassword).isEqualTo("c%3A%01%40%2F%5C%3F%23%25d")
    assertThat(url.encodedPath).isEqualTo("/g:%01@%2F%5C%3F%23%25h")
    assertThat(url.encodedQuery).isEqualTo("i:%01@/\\?%23%25j")
    assertThat(url.encodedFragment).isEqualTo("k:%01@/\\?#%25l")
  }

  @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 pathSize() {
    assertThat(parse("http://host/").pathSize).isEqualTo(1)
    assertThat(parse("http://host/a/b/c").pathSize).isEqualTo(3)
  }

  @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 setPathSegmentEncodes() {
    val base = parse("http://host/a/b/c")
    assertThat(base.newBuilder().setPathSegment(0, "%25").build().encodedPath)
      .isEqualTo("/%2525/b/c")
    assertThat(base.newBuilder().setPathSegment(0, ".\n").build().encodedPath)
      .isEqualTo("/.%0A/b/c")
    assertThat(base.newBuilder().setPathSegment(0, "%2e").build().encodedPath)
      .isEqualTo("/%252e/b/c")
  }

  @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 setPathSegmentWithSlash() {
    val base = parse("http://host/a/b/c")
    val url = base.newBuilder().setPathSegment(1, "/").build()
    assertThat(url.encodedPath).isEqualTo("/a/%2F/c")
  }

  @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 setEncodedPathSegmentRejectsDotAndIgnoredCharacter() {
    val base = parse("http://host/a/b/c")
    assertFailsWith {
      base.newBuilder().setEncodedPathSegment(0, ".\n")
    }
  }

  @Test
  fun setEncodedPathSegmentRejectsDotDot() {
    val base = parse("http://host/a/b/c")
    assertFailsWith {
      base.newBuilder().setEncodedPathSegment(0, "..")
    }
  }

  @Test
  fun setEncodedPathSegmentRejectsDotDotAndIgnoredCharacter() {
    val base = parse("http://host/a/b/c")
    assertFailsWith {
      base.newBuilder().setEncodedPathSegment(0, "..\n")
    }
  }

  @Test
  fun setEncodedPathSegmentWithSlash() {
    val base = parse("http://host/a/b/c")
    val url = base.newBuilder().setEncodedPathSegment(1, "/").build()
    assertThat(url.encodedPath).isEqualTo("/a/%2F/c")
  }

  @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)
    }
  }

  @Test
  fun toJavaNetUrl() {
    val httpUrl = parse("http://username:password@host/path?query#fragment")
    val javaNetUrl = httpUrl.toUrl()
    assertThat(javaNetUrl.toString())
      .isEqualTo("http://username:password@host/path?query#fragment")
  }

  @Test
  fun toUri() {
    val httpUrl = parse("http://username:password@host/path?query#fragment")
    val uri = httpUrl.toUri()
    assertThat(uri.toString())
      .isEqualTo("http://username:password@host/path?query#fragment")
  }

  @Test
  fun toUriSpecialQueryCharacters() {
    val httpUrl = parse("http://host/?d=abc!@[]^`{}|\\")
    val uri = httpUrl.toUri()
    assertThat(uri.toString()).isEqualTo("http://host/?d=abc!@[]%5E%60%7B%7D%7C%5C")
  }

  @Test
  fun toUriWithUsernameNoPassword() {
    val httpUrl = HttpUrl.Builder()
      .scheme("http")
      .username("user")
      .host("host")
      .build()
    assertThat(httpUrl.toString()).isEqualTo("http://user@host/")
    assertThat(httpUrl.toUri().toString()).isEqualTo("http://user@host/")
  }

  @Test
  fun toUriUsernameSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .username("=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/")
    assertThat(url.toUri().toString())
      .isEqualTo("http://%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/")
  }

  @Test
  fun toUriPasswordSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .username("user")
      .password("=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/")
    assertThat(url.toUri().toString())
      .isEqualTo("http://user:%3D%5B%5D%3A%3B%22~%7C%3F%23%40%5E%2F$%25*@host/")
  }

  @Test
  fun toUriPathSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .addPathSegment("=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://host/=[]:;%22~%7C%3F%23@%5E%2F$%25*")
    assertThat(url.toUri().toString())
      .isEqualTo("http://host/=%5B%5D:;%22~%7C%3F%23@%5E%2F$%25*")
  }

  @Test
  fun toUriQueryParameterNameSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .addQueryParameter("=[]:;\"~|?#@^/$%*", "a")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://host/?%3D%5B%5D%3A%3B%22%7E%7C%3F%23%40%5E%2F%24%25*=a")
    assertThat(url.toUri().toString())
      .isEqualTo("http://host/?%3D%5B%5D%3A%3B%22%7E%7C%3F%23%40%5E%2F%24%25*=a")
    assertThat(url.queryParameter("=[]:;\"~|?#@^/$%*")).isEqualTo("a")
  }

  @Test
  fun toUriQueryParameterValueSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .addQueryParameter("a", "=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://host/?a=%3D%5B%5D%3A%3B%22%7E%7C%3F%23%40%5E%2F%24%25*")
    assertThat(url.toUri().toString())
      .isEqualTo("http://host/?a=%3D%5B%5D%3A%3B%22%7E%7C%3F%23%40%5E%2F%24%25*")
    assertThat(url.queryParameter("a")).isEqualTo("=[]:;\"~|?#@^/$%*")
  }

  @Test
  fun toUriQueryValueSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .query("=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString()).isEqualTo("http://host/?=[]:;%22~|?%23@^/$%25*")
    assertThat(url.toUri().toString())
      .isEqualTo("http://host/?=[]:;%22~%7C?%23@%5E/$%25*")
  }

  @Test
  fun queryCharactersEncodedWhenComposed() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .addQueryParameter("a", "!$(),/:;?@[]\\^`{|}~")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://host/?a=%21%24%28%29%2C%2F%3A%3B%3F%40%5B%5D%5C%5E%60%7B%7C%7D%7E")
    assertThat(url.queryParameter("a")).isEqualTo("!$(),/:;?@[]\\^`{|}~")
  }

  /**
   * 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 toUriFragmentSpecialCharacters() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .host("host")
      .fragment("=[]:;\"~|?#@^/$%*")
      .build()
    assertThat(url.toString()).isEqualTo("http://host/#=[]:;\"~|?#@^/$%25*")
    assertThat(url.toUri().toString())
      .isEqualTo("http://host/#=[]:;%22~%7C?%23@%5E/$%25*")
  }

  @Test
  fun toUriWithControlCharacters() {
    // Percent-encoded in the path.
    assertThat(parse("http://host/a\u0000b").toUri())
      .isEqualTo(URI("http://host/a%00b"))
    assertThat(parse("http://host/a\u0080b").toUri())
      .isEqualTo(URI("http://host/a%C2%80b"))
    assertThat(parse("http://host/a\u009fb").toUri())
      .isEqualTo(URI("http://host/a%C2%9Fb"))
    // Percent-encoded in the query.
    assertThat(parse("http://host/?a\u0000b").toUri())
      .isEqualTo(URI("http://host/?a%00b"))
    assertThat(parse("http://host/?a\u0080b").toUri())
      .isEqualTo(URI("http://host/?a%C2%80b"))
    assertThat(parse("http://host/?a\u009fb").toUri())
      .isEqualTo(URI("http://host/?a%C2%9Fb"))
    // Stripped from the fragment.
    assertThat(parse("http://host/#a\u0000b").toUri())
      .isEqualTo(URI("http://host/#a%00b"))
    assertThat(parse("http://host/#a\u0080b").toUri())
      .isEqualTo(URI("http://host/#ab"))
    assertThat(parse("http://host/#a\u009fb").toUri())
      .isEqualTo(URI("http://host/#ab"))
  }

  @Test
  fun toUriWithSpaceCharacters() {
    // Percent-encoded in the path.
    assertThat(parse("http://host/a\u000bb").toUri())
      .isEqualTo(URI("http://host/a%0Bb"))
    assertThat(parse("http://host/a b").toUri()).isEqualTo(URI("http://host/a%20b"))
    assertThat(parse("http://host/a\u2009b").toUri())
      .isEqualTo(URI("http://host/a%E2%80%89b"))
    assertThat(parse("http://host/a\u3000b").toUri())
      .isEqualTo(URI("http://host/a%E3%80%80b"))
    // Percent-encoded in the query.
    assertThat(parse("http://host/?a\u000bb").toUri())
      .isEqualTo(URI("http://host/?a%0Bb"))
    assertThat(parse("http://host/?a b").toUri())
      .isEqualTo(URI("http://host/?a%20b"))
    assertThat(parse("http://host/?a\u2009b").toUri())
      .isEqualTo(URI("http://host/?a%E2%80%89b"))
    assertThat(parse("http://host/?a\u3000b").toUri())
      .isEqualTo(URI("http://host/?a%E3%80%80b"))
    // Stripped from the fragment.
    assertThat(parse("http://host/#a\u000bb").toUri())
      .isEqualTo(URI("http://host/#a%0Bb"))
    assertThat(parse("http://host/#a b").toUri())
      .isEqualTo(URI("http://host/#a%20b"))
    assertThat(parse("http://host/#a\u2009b").toUri())
      .isEqualTo(URI("http://host/#ab"))
    assertThat(parse("http://host/#a\u3000b").toUri())
      .isEqualTo(URI("http://host/#ab"))
  }

  @Test
  fun toUriWithNonHexPercentEscape() {
    assertThat(parse("http://host/%xx").toUri()).isEqualTo(URI("http://host/%25xx"))
  }

  @Test
  fun toUriWithTruncatedPercentEscape() {
    assertThat(parse("http://host/%a").toUri()).isEqualTo(URI("http://host/%25a"))
    assertThat(parse("http://host/%").toUri()).isEqualTo(URI("http://host/%25"))
  }

  @Test
  fun fromJavaNetUrl() {
    val javaNetUrl = URL("http://username:password@host/path?query#fragment")
    val httpUrl = javaNetUrl.toHttpUrlOrNull()
    assertThat(httpUrl.toString())
      .isEqualTo("http://username:password@host/path?query#fragment")
  }

  @Test

  fun fromJavaNetUrlUnsupportedScheme() {
    // java.net.MalformedURLException: unknown protocol: mailto
    platform.assumeNotAndroid()

    // Accessing an URL protocol that was not enabled. The URL protocol mailto is not tested and
    // might not work as expected. It can be enabled by adding the --enable-url-protocols=mailto
    // option to the native-image command.
    platform.assumeNotGraalVMImage()
    val javaNetUrl = URL("mailto:[email protected]")
    assertThat(javaNetUrl.toHttpUrlOrNull()).isNull()
  }

  @Test
  fun fromUri() {
    val uri = URI("http://username:password@host/path?query#fragment")
    val httpUrl = uri.toHttpUrlOrNull()
    assertThat(httpUrl.toString())
      .isEqualTo("http://username:password@host/path?query#fragment")
  }

  @Test
  fun fromUriUnsupportedScheme() {
    val uri = URI("mailto:[email protected]")
    assertThat(uri.toHttpUrlOrNull()).isNull()
  }

  @Test
  fun fromUriPartial() {
    val uri = URI("/path")
    assertThat(uri.toHttpUrlOrNull()).isNull()
  }

  @Test
  fun composeQueryWithComponents() {
    val base = parse("http://host/")
    val url = base.newBuilder().addQueryParameter("a+=& b", "c+=& d").build()
    assertThat(url.toString())
      .isEqualTo("http://host/?a%2B%3D%26%20b=c%2B%3D%26%20d")
    assertThat(url.queryParameterValue(0)).isEqualTo("c+=& d")
    assertThat(url.queryParameterName(0)).isEqualTo("a+=& b")
    assertThat(url.queryParameter("a+=& b")).isEqualTo("c+=& d")
    assertThat(url.queryParameterNames).isEqualTo(setOf("a+=& b"))
    assertThat(url.queryParameterValues("a+=& b")).isEqualTo(listOf("c+=& d"))
    assertThat(url.querySize).isEqualTo(1)
    // Ambiguous! (Though working as designed.)
    assertThat(url.query).isEqualTo("a+=& b=c+=& d")
    assertThat(url.encodedQuery).isEqualTo("a%2B%3D%26%20b=c%2B%3D%26%20d")
  }

  @Test
  fun composeQueryWithEncodedComponents() {
    val base = parse("http://host/")
    val url = base.newBuilder()
      .addEncodedQueryParameter("a+=& b", "c+=& d")
      .build()
    assertThat(url.toString()).isEqualTo("http://host/?a+%3D%26%20b=c+%3D%26%20d")
    assertThat(url.queryParameter("a =& b")).isEqualTo("c =& d")
  }

  @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 composeQuerySetQueryParameter() {
    val url = parse("http://host/").newBuilder()
      .addQueryParameter("a+=& b", "c+=& d")
      .setQueryParameter("a+=& b", "ef")
      .build()
    assertThat(url.toString()).isEqualTo("http://host/?a%2B%3D%26%20b=ef")
    assertThat(url.queryParameter("a+=& b")).isEqualTo("ef")
  }

  @Test
  fun composeQuerySetEncodedQueryParameter() {
    val url = parse("http://host/").newBuilder()
      .addEncodedQueryParameter("a+=& b", "c+=& d")
      .setEncodedQueryParameter("a+=& b", "ef")
      .build()
    assertThat(url.toString()).isEqualTo("http://host/?a+%3D%26%20b=ef")
    assertThat(url.queryParameter("a =& b")).isEqualTo("ef")
  }

  @Test
  fun composeQueryMultipleEncodedValuesForParameter() {
    val url = parse("http://host/").newBuilder()
      .addQueryParameter("a+=& b", "c+=& d")
      .addQueryParameter("a+=& b", "e+=& f")
      .build()
    assertThat(url.toString())
      .isEqualTo("http://host/?a%2B%3D%26%20b=c%2B%3D%26%20d&a%2B%3D%26%20b=e%2B%3D%26%20f")
    assertThat(url.querySize).isEqualTo(2)
    assertThat(url.queryParameterNames).isEqualTo(setOf("a+=& b"))
    assertThat(url.queryParameterValues("a+=& b"))
      .containsExactly("c+=& d", "e+=& f")
  }

  @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 queryParametersWithoutValues() {
    val url = parse("http://host/?foo&bar&baz")
    assertThat(url.querySize).isEqualTo(3)
    assertThat(url.queryParameterNames).containsExactly("foo", "bar", "baz")
    assertThat(url.queryParameterValue(0)).isNull()
    assertThat(url.queryParameterValue(1)).isNull()
    assertThat(url.queryParameterValue(2)).isNull()
    assertThat(url.queryParameterValues("foo")).isEqualTo(listOf(null as String?))
    assertThat(url.queryParameterValues("bar")).isEqualTo(listOf(null as String?))
    assertThat(url.queryParameterValues("baz")).isEqualTo(listOf(null as String?))
  }

  @Test
  fun queryParametersWithEmptyValues() {
    val url = parse("http://host/?foo=&bar=&baz=")
    assertThat(url.querySize).isEqualTo(3)
    assertThat(url.queryParameterNames).containsExactly("foo", "bar", "baz")
    assertThat(url.queryParameterValue(0)).isEqualTo("")
    assertThat(url.queryParameterValue(1)).isEqualTo("")
    assertThat(url.queryParameterValue(2)).isEqualTo("")
    assertThat(url.queryParameterValues("foo")).isEqualTo(listOf(""))
    assertThat(url.queryParameterValues("bar")).isEqualTo(listOf(""))
    assertThat(url.queryParameterValues("baz")).isEqualTo(listOf(""))
  }

  @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("")
  }

  @Test
  fun roundTripBuilder() {
    val url = HttpUrl.Builder()
      .scheme("http")
      .username("%")
      .password("%")
      .host("host")
      .addPathSegment("%")
      .query("%")
      .fragment("%")
      .build()
    assertThat(url.toString()).isEqualTo("http://%25:%25@host/%25?%25#%25")
    assertThat(url.newBuilder().build().toString())
      .isEqualTo("http://%25:%25@host/%25?%25#%25")
    assertThat(url.resolve("").toString()).isEqualTo("http://%25:%25@host/%25?%25")
  }

  /**
   * 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 topPrivateDomain() {
    assertThat(parse("https://google.com").topPrivateDomain())
      .isEqualTo("google.com")
    assertThat(parse("https://adwords.google.co.uk").topPrivateDomain())
      .isEqualTo("google.co.uk")
    assertThat(parse("https://栃.栃木.jp").topPrivateDomain())
      .isEqualTo("xn--ewv.xn--4pvxs.jp")
    assertThat(parse("https://xn--ewv.xn--4pvxs.jp").topPrivateDomain())
      .isEqualTo("xn--ewv.xn--4pvxs.jp")
    assertThat(parse("https://co.uk").topPrivateDomain()).isNull()
    assertThat(parse("https://square").topPrivateDomain()).isNull()
    assertThat(parse("https://栃木.jp").topPrivateDomain()).isNull()
    assertThat(parse("https://xn--4pvxs.jp").topPrivateDomain()).isNull()
    assertThat(parse("https://localhost").topPrivateDomain()).isNull()
    assertThat(parse("https://127.0.0.1").topPrivateDomain()).isNull()

    // https://github.com/square/okhttp/issues/6109
    assertThat(parse("http://a./").topPrivateDomain()).isNull()
    assertThat(parse("http://squareup.com./").topPrivateDomain())
      .isEqualTo("squareup.com")
  }

  @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 hostnameTelephone() {
    // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/

    // Map the single character telephone symbol (℡) to the string "tel".
    assertThat(parse("http://\u2121").host).isEqualTo("tel")

    // Map the Kelvin symbol (K) to the string "k".
    assertThat(parse("http://\u212A").host).isEqualTo("k")
  }

  @Test
  fun quirks() {
    assertThat(parse("http://facebook.com").host).isEqualTo("facebook.com")
    assertThat(parse("http://facebooK.com").host).isEqualTo("facebook.com")
    assertThat(parse("http://Facebook.com").host).isEqualTo("facebook.com")
    assertThat(parse("http://FacebooK.com").host).isEqualTo("facebook.com")
  }

  @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 labelTooLongDueToAsciiExpansion() {
    val a60 = "a".repeat(60)
    assertThat(parse("http://\u2121$a60/").toString()).isEqualTo("http://tel$a60/")
    assertInvalid("http://a\u2121$a60/", "Invalid URL host: \"a\u2121$a60\"")
  }

  @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\"")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy