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

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

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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.io.File
import java.io.IOException
import java.io.InputStream
import java.net.ConnectException
import java.net.CookieManager
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.PasswordAuthentication
import java.net.ProtocolException
import java.net.Proxy
import java.net.ProxySelector
import java.net.ServerSocket
import java.net.Socket
import java.net.SocketAddress
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.URI
import java.net.URLConnection
import java.net.UnknownHostException
import java.security.KeyStore
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.time.Duration
import java.util.Arrays
import java.util.EnumSet
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import java.util.zip.GZIPInputStream
import javax.net.SocketFactory
import javax.net.ssl.SSLException
import javax.net.ssl.SSLHandshakeException
import javax.net.ssl.SSLProtocolException
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import mockwebserver3.MockResponse
import mockwebserver3.MockWebServer
import mockwebserver3.SocketPolicy
import okhttp3.Credentials.basic
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.TestUtil.assertSuppressed
import okhttp3.internal.RecordingAuthenticator
import okhttp3.internal.RecordingOkAuthenticator
import okhttp3.internal.addHeaderLenient
import okhttp3.internal.http.HTTP_PERM_REDIRECT
import okhttp3.internal.http.HTTP_TEMP_REDIRECT
import okhttp3.internal.platform.Platform.Companion.get
import okhttp3.internal.userAgent
import okhttp3.testing.Flaky
import okhttp3.testing.PlatformRule
import okhttp3.tls.internal.TlsUtil.localhost
import okio.Buffer
import okio.BufferedSink
import okio.GzipSink
import okio.buffer
import okio.utf8Size
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.io.TempDir
import org.opentest4j.TestAbortedException

/** Android's URLConnectionTest, ported to exercise OkHttp's Call API.  */
@Tag("Slow")
class URLConnectionTest {
  @RegisterExtension
  val platform = PlatformRule()

  @RegisterExtension
  val clientTestRule = OkHttpClientTestRule()

  @TempDir
  lateinit var tempDir: File

  private lateinit var server: MockWebServer
  private lateinit var server2: MockWebServer
  private val handshakeCertificates = localhost()
  private var client = clientTestRule.newClient()
  private var cache: Cache? = null

  @BeforeEach
  fun setUp(server: MockWebServer, server2: MockWebServer) {
    this.server = server
    this.server2 = server2
    platform.assumeNotBouncyCastle()
    server.protocolNegotiationEnabled = false
  }

  @AfterEach
  fun tearDown() {
    java.net.Authenticator.setDefault(null)
    System.clearProperty("proxyHost")
    System.clearProperty("proxyPort")
    System.clearProperty("http.proxyHost")
    System.clearProperty("http.proxyPort")
    System.clearProperty("https.proxyHost")
    System.clearProperty("https.proxyPort")
    if (cache != null) {
      cache!!.delete()
    }
  }

  @Test
  fun requestHeaders() {
    server.enqueue(MockResponse())
    val request = Request.Builder()
      .url(server.url("/"))
      .addHeader("D", "e")
      .addHeader("D", "f")
      .build()
    assertThat(request.header("D")).isEqualTo("f")
    assertThat(request.header("d")).isEqualTo("f")
    val requestHeaders = request.headers
    assertThat(LinkedHashSet(requestHeaders.values("D"))).isEqualTo(newSet("e", "f"))
    assertThat(LinkedHashSet(requestHeaders.values("d"))).isEqualTo(newSet("e", "f"))
    val response = getResponse(request)
    response.close()
    val recordedRequest = server.takeRequest()
    assertThat(recordedRequest.headers.values("D")).isEqualTo(listOf("e", "f"))
    assertThat(recordedRequest.getHeader("G")).isNull()
    assertThat(recordedRequest.getHeader("null")).isNull()
  }

  @Test
  fun getRequestPropertyReturnsLastValue() {
    val request = Request.Builder()
      .url(server.url("/"))
      .addHeader("A", "value1")
      .addHeader("A", "value2")
      .build()
    assertThat(request.header("A")).isEqualTo("value2")
  }

  @Test
  fun responseHeaders() {
    server.enqueue(
      MockResponse()
        .setStatus("HTTP/1.0 200 Fantastic")
        .addHeader("A: c")
        .addHeader("B: d")
        .addHeader("A: e")
        .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)
    )
    val request = newRequest("/")
    val response = getResponse(request)
    assertThat(response.code).isEqualTo(200)
    assertThat(response.message).isEqualTo("Fantastic")
    val responseHeaders = response.headers
    assertThat(LinkedHashSet(responseHeaders.values("A"))).isEqualTo(newSet("c", "e"))
    assertThat(LinkedHashSet(responseHeaders.values("a"))).isEqualTo(newSet("c", "e"))
    assertThat(responseHeaders.name(0)).isEqualTo("A")
    assertThat(responseHeaders.value(0)).isEqualTo("c")
    assertThat(responseHeaders.name(1)).isEqualTo("B")
    assertThat(responseHeaders.value(1)).isEqualTo("d")
    assertThat(responseHeaders.name(2)).isEqualTo("A")
    assertThat(responseHeaders.value(2)).isEqualTo("e")
    response.body.close()
  }

  @Test
  fun serverSendsInvalidStatusLine() {
    server.enqueue(MockResponse().setStatus("HTP/1.1 200 OK"))
    val request = newRequest("/")
    try {
      getResponse(request)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun serverSendsInvalidCodeTooLarge() {
    server.enqueue(MockResponse().setStatus("HTTP/1.1 2147483648 OK"))
    val request = newRequest("/")
    try {
      getResponse(request)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun serverSendsInvalidCodeNotANumber() {
    server.enqueue(MockResponse().setStatus("HTTP/1.1 00a OK"))
    val request = newRequest("/")
    try {
      getResponse(request)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun serverSendsUnnecessaryWhitespace() {
    server.enqueue(MockResponse().setStatus(" HTTP/1.1 2147483648 OK"))
    val request = newRequest("/")
    try {
      getResponse(request)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun connectRetriesUntilConnectedOrFailed() {
    val request = newRequest("/foo")
    server.shutdown()
    try {
      getResponse(request)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun requestBodySurvivesRetriesWithFixedLength() {
    testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun requestBodySurvivesRetriesWithChunkedStreaming() {
    testRequestBodySurvivesRetries(TransferKind.CHUNKED)
  }

  private fun testRequestBodySurvivesRetries(transferKind: TransferKind) {
    server.enqueue(
      MockResponse()
        .setBody("abc")
    )

    // Use a misconfigured proxy to guarantee that the request is retried.
    client = client.newBuilder()
      .proxySelector(
        FakeProxySelector()
          .addProxy(server2.toProxyAddress())
          .addProxy(Proxy.NO_PROXY)
      )
      .build()
    server2.shutdown()
    val request = Request(
      url = server.url("/def"),
      body = transferKind.newRequestBody("body"),
    )
    val response = getResponse(request)
    assertContent("abc", response)
    assertThat(server.takeRequest().body.readUtf8()).isEqualTo("body")
  }

  // Check that if we don't read to the end of a response, the next request on the
  // recycled connection doesn't get the unread tail of the first request's response.
  // http://code.google.com/p/android/issues/detail?id=2939
  @Test
  fun bug2939() {
    val response = MockResponse()
      .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)
    server.enqueue(response)
    server.enqueue(response)
    val request = newRequest("/")
    val c1 = getResponse(request)
    assertContent("ABCDE", c1, 5)
    val c2 = getResponse(request)
    assertContent("ABCDE", c2, 5)
    c1.close()
    c2.close()
  }

  @Test
  fun connectionsArePooled() {
    val response = MockResponse()
      .setBody("ABCDEFGHIJKLMNOPQR")
    server.enqueue(response)
    server.enqueue(response)
    server.enqueue(response)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/foo")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/bar?baz=quux")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(1)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/z")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(2)
  }

  @Test
  fun chunkedConnectionsArePooled() {
    val response = MockResponse()
      .setChunkedBody("ABCDEFGHIJKLMNOPQR", 5)
    server.enqueue(response)
    server.enqueue(response)
    server.enqueue(response)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/foo")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/bar?baz=quux")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(1)
    assertContent("ABCDEFGHIJKLMNOPQR", getResponse(newRequest("/z")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(2)
  }

  @Test
  fun serverClosesSocket() {
    testServerClosesOutput(SocketPolicy.DISCONNECT_AT_END)
  }

  @Test
  fun serverShutdownInput() {
    testServerClosesOutput(SocketPolicy.SHUTDOWN_INPUT_AT_END)
  }

  @Test
  fun serverShutdownOutput() {
    testServerClosesOutput(SocketPolicy.SHUTDOWN_OUTPUT_AT_END)
  }

  @Test
  fun invalidHost() {
    // Note that 1234.1.1.1 is an invalid host in a URI, but URL isn't as strict.
    client = client.newBuilder()
      .dns(FakeDns())
      .build()
    try {
      getResponse(
        Request.Builder()
          .url("http://1234.1.1.1/index.html".toHttpUrl())
          .build()
      )
      fail()
    } catch (expected: UnknownHostException) {
    }
  }

  private fun testServerClosesOutput(socketPolicy: SocketPolicy) {
    server.enqueue(
      MockResponse()
        .setBody("This connection won't pool properly")
        .setSocketPolicy(socketPolicy)
    )
    val responseAfter = MockResponse()
      .setBody("This comes after a busted connection")
    server.enqueue(responseAfter)
    server.enqueue(responseAfter) // Enqueue 2x because the broken connection may be reused.
    val response1 = getResponse(newRequest("/a"))
    response1.body.source().timeout().timeout(100, TimeUnit.MILLISECONDS)
    assertContent("This connection won't pool properly", response1)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)

    // Give the server time to enact the socket policy if it's one that could happen after the
    // client has received the response.
    Thread.sleep(500)
    val response2 = getResponse(newRequest("/b"))
    response1.body.source().timeout().timeout(100, TimeUnit.MILLISECONDS)
    assertContent("This comes after a busted connection", response2)

    // Check that a fresh connection was created, either immediately or after attempting reuse.
    // We know that a fresh connection was created if the server recorded a request with sequence
    // number 0. Since the client may have attempted to reuse the broken connection just before
    // creating a fresh connection, the server may have recorded 2 requests at this point. The order
    // of recording is non-deterministic.
    val requestAfter = server.takeRequest()
    assertThat(
      requestAfter.sequenceNumber == 0
        || server.requestCount == 3 && server.takeRequest().sequenceNumber == 0
    ).isTrue
  }

  internal enum class WriteKind {
    BYTE_BY_BYTE,
    SMALL_BUFFERS,
    LARGE_BUFFERS
  }

  @Test
  fun chunkedUpload_byteByByte() {
    doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE)
  }

  @Test
  fun chunkedUpload_smallBuffers() {
    doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS)
  }

  @Test
  fun chunkedUpload_largeBuffers() {
    doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS)
  }

  @Test
  fun fixedLengthUpload_byteByByte() {
    doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE)
  }

  @Test
  fun fixedLengthUpload_smallBuffers() {
    doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS)
  }

  @Test
  fun fixedLengthUpload_largeBuffers() {
    doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS)
  }

  private fun doUpload(uploadKind: TransferKind, writeKind: WriteKind) {
    val n = 512 * 1024
    server.bodyLimit = 0
    server.enqueue(MockResponse())
    val requestBody: RequestBody = object : RequestBody() {
      override fun contentType(): MediaType? {
        return null
      }

      override fun contentLength(): Long {
        return if (uploadKind === TransferKind.CHUNKED) -1L else n.toLong()
      }

      override fun writeTo(sink: BufferedSink) {
        if (writeKind == WriteKind.BYTE_BY_BYTE) {
          for (i in 0 until n) {
            sink.writeByte('x'.code)
          }
        } else {
          val buf = ByteArray(if (writeKind == WriteKind.SMALL_BUFFERS) 256 else 64 * 1024)
          Arrays.fill(buf, 'x'.code.toByte())
          var i = 0
          while (i < n) {
            sink.write(buf, 0, Math.min(buf.size, n - i))
            i += buf.size
          }
        }
      }
    }
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = requestBody,
      )
    )
    assertThat(response.code).isEqualTo(200)
    val request = server.takeRequest()
    assertThat(request.bodySize).isEqualTo(n.toLong())
    if (uploadKind === TransferKind.CHUNKED) {
      assertThat(request.chunkSizes).isNotEmpty
    } else {
      assertThat(request.chunkSizes).isEmpty()
    }
  }

  @Test
  fun connectViaHttps() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("this response comes via HTTPS")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(newRequest("/foo"))
    assertContent("this response comes via HTTPS", response)
    val request = server.takeRequest()
    assertThat(request.requestLine).isEqualTo("GET /foo HTTP/1.1")
  }

  @Test
  fun connectViaHttpsReusingConnections() {
    connectViaHttpsReusingConnections(false)
  }

  @Test
  fun connectViaHttpsReusingConnectionsAfterRebuildingClient() {
    connectViaHttpsReusingConnections(true)
  }

  private fun connectViaHttpsReusingConnections(rebuildClient: Boolean) {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("this response comes via HTTPS")
    )
    server.enqueue(
      MockResponse()
        .setBody("another response via HTTPS")
    )

    // The pool will only reuse sockets if the SSL socket factories are the same.
    val clientSocketFactory = handshakeCertificates.sslSocketFactory()
    val hostnameVerifier = RecordingHostnameVerifier()
    val cookieJar: CookieJar = JavaNetCookieJar(CookieManager())
    val connectionPool = ConnectionPool()
    client = OkHttpClient.Builder()
      .cache(cache)
      .connectionPool(connectionPool)
      .cookieJar(cookieJar)
      .sslSocketFactory(clientSocketFactory, handshakeCertificates.trustManager)
      .hostnameVerifier(hostnameVerifier)
      .build()
    val response1 = getResponse(newRequest("/"))
    assertContent("this response comes via HTTPS", response1)
    if (rebuildClient) {
      client = OkHttpClient.Builder()
        .cache(cache)
        .connectionPool(connectionPool)
        .cookieJar(cookieJar)
        .sslSocketFactory(clientSocketFactory, handshakeCertificates.trustManager)
        .hostnameVerifier(hostnameVerifier)
        .build()
    }
    val response2 = getResponse(newRequest("/"))
    assertContent("another response via HTTPS", response2)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(1)
  }

  @Test
  fun connectViaHttpsReusingConnectionsDifferentFactories() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("this response comes via HTTPS")
    )
    server.enqueue(
      MockResponse()
        .setBody("another response via HTTPS")
    )

    // install a custom SSL socket factory so the server can be authorized
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(),
        handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response1 = getResponse(newRequest("/"))
    assertContent("this response comes via HTTPS", response1)
    val sslContext2 = get().newSSLContext()
    sslContext2.init(null, null, null)
    val sslSocketFactory2 = sslContext2.socketFactory
    val trustManagerFactory = TrustManagerFactory.getInstance(
      TrustManagerFactory.getDefaultAlgorithm()
    )
    trustManagerFactory.init(null as KeyStore?)
    val trustManager = trustManagerFactory.trustManagers[0] as X509TrustManager
    client = client.newBuilder()
      .sslSocketFactory(sslSocketFactory2, trustManager)
      .build()
    try {
      getResponse(newRequest("/"))
      fail(
        "without an SSL socket factory, the connection should fail"
      )
    } catch (expected: SSLException) {
    }
  }

  // TODO(jwilson): tests below this marker need to be migrated to OkHttp's request/response API.
  @Test
  fun connectViaHttpsWithSSLFallback() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)
    )
    server.enqueue(
      MockResponse()
        .setBody("this response comes via SSL")
    )
    client = client.newBuilder()
      .hostnameVerifier(
        RecordingHostnameVerifier()
      ) // Attempt RESTRICTED_TLS then fall back to MODERN_TLS.
      .connectionSpecs(Arrays.asList(ConnectionSpec.RESTRICTED_TLS, ConnectionSpec.MODERN_TLS))
      .sslSocketFactory(
        suppressTlsFallbackClientSocketFactory(), handshakeCertificates.trustManager
      )
      .build()
    val response = getResponse(newRequest("/foo"))
    assertContent("this response comes via SSL", response)
    val failHandshakeRequest = server.takeRequest()
    assertThat(failHandshakeRequest.requestLine).isEmpty()
    val fallbackRequest = server.takeRequest()
    assertThat(fallbackRequest.requestLine).isEqualTo("GET /foo HTTP/1.1")
    assertThat(fallbackRequest.tlsVersion).isIn(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3)
  }

  @Test
  fun connectViaHttpsWithSSLFallbackFailuresRecorded() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)
    )
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)
    )
    client = client.newBuilder()
      .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
      .hostnameVerifier(RecordingHostnameVerifier())
      .sslSocketFactory(
        suppressTlsFallbackClientSocketFactory(), handshakeCertificates.trustManager
      )
      .build()
    try {
      getResponse(newRequest("/foo"))
      fail()
    } catch (expected: IOException) {
      expected.assertSuppressed { throwables: List? ->
        assertThat(throwables).hasSize(1)
        Unit
      }
    }
  }

  /**
   * When a pooled connection fails, don't blame the route. Otherwise pooled connection failures can
   * cause unnecessary SSL fallbacks.
   *
   * https://github.com/square/okhttp/issues/515
   */
  @Test
  fun sslFallbackNotUsedWhenRecycledConnectionFails() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("abc")
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    server.enqueue(
      MockResponse()
        .setBody("def")
    )
    client = client.newBuilder()
      .hostnameVerifier(RecordingHostnameVerifier())
      .sslSocketFactory(
        suppressTlsFallbackClientSocketFactory(), handshakeCertificates.trustManager
      )
      .build()
    assertContent("abc", getResponse(newRequest("/")))

    // Give the server time to disconnect.
    Thread.sleep(500)
    assertContent("def", getResponse(newRequest("/")))
    val tlsVersions: Set = EnumSet.of(
      TlsVersion.TLS_1_0, TlsVersion.TLS_1_2,
      TlsVersion.TLS_1_3
    ) // v1.2 on OpenJDK 8.
    val request1 = server.takeRequest()
    assertThat(tlsVersions).contains(request1.tlsVersion)
    val request2 = server.takeRequest()
    assertThat(tlsVersions).contains(request2.tlsVersion)
  }

  /**
   * Verify that we don't retry connections on certificate verification errors.
   *
   * http://code.google.com/p/android/issues/detail?id=13178
   */
  @Flaky
  @Test
  fun connectViaHttpsToUntrustedServer() {
    // Flaky https://github.com/square/okhttp/issues/5222
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(MockResponse()) // unused
    try {
      getResponse(newRequest("/foo"))
      fail()
    } catch (expected: SSLHandshakeException) {
      // Allow conscrypt to fail in different ways
      if (!platform.isConscrypt()) {
        assertThat(expected.cause).isInstanceOf(
          CertificateException::class.java
        )
      }
    }
    assertThat(server.requestCount).isEqualTo(0)
  }

  @Test
  fun connectViaProxyUsingProxyArg() {
    testConnectViaProxy(ProxyConfig.CREATE_ARG)
  }

  @Test
  fun connectViaProxyUsingProxySystemProperty() {
    testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY)
  }

  @Test
  fun connectViaProxyUsingHttpProxySystemProperty() {
    testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY)
  }

  private fun testConnectViaProxy(proxyConfig: ProxyConfig) {
    val mockResponse = MockResponse()
      .setBody("this response comes via a proxy")
    server.enqueue(mockResponse)
    val url = "http://android.com/foo".toHttpUrl()
    val response = proxyConfig.connect(server, client, url).execute()
    assertContent("this response comes via a proxy", response)
    val request = server.takeRequest()
    assertThat(request.requestLine).isEqualTo(
      "GET http://android.com/foo HTTP/1.1"
    )
    assertThat(request.getHeader("Host")).isEqualTo("android.com")
  }

  @Test
  fun contentDisagreesWithContentLengthHeaderBodyTooLong() {
    server.enqueue(
      MockResponse()
        .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
        .clearHeaders()
        .addHeader("Content-Length: 3")
    )
    assertContent("abc", getResponse(newRequest("/")))
  }

  @Test
  fun contentDisagreesWithContentLengthHeaderBodyTooShort() {
    server.enqueue(
      MockResponse()
        .setBody("abc")
        .setHeader("Content-Length", "5")
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    try {
      val response = getResponse(newRequest("/"))
      response.body.source().readUtf8(5)
      fail()
    } catch (expected: ProtocolException) {
    }
  }

  private fun testConnectViaSocketFactory(useHttps: Boolean) {
    val uselessSocketFactory: SocketFactory = object : SocketFactory() {
      override fun createSocket(): Socket {
        throw IllegalArgumentException("useless")
      }

      override fun createSocket(host: InetAddress, port: Int): Socket? = null

      override fun createSocket(
        address: InetAddress,
        port: Int,
        localAddress: InetAddress,
        localPort: Int
      ): Socket? = null

      override fun createSocket(host: String, port: Int): Socket? = null

      override fun createSocket(
        host: String,
        port: Int,
        localHost: InetAddress,
        localPort: Int
      ): Socket? = null
    }
    if (useHttps) {
      server.useHttps(handshakeCertificates.sslSocketFactory(), false)
      client = client.newBuilder()
        .sslSocketFactory(
          handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
        )
        .hostnameVerifier(RecordingHostnameVerifier())
        .build()
    }
    server.enqueue(
      MockResponse()
        .setStatus("HTTP/1.1 200 OK")
    )
    client = client.newBuilder()
      .socketFactory(uselessSocketFactory)
      .build()
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: IllegalArgumentException) {
    }
    client = client.newBuilder()
      .socketFactory(SocketFactory.getDefault())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(200)
  }

  @Test
  fun connectHttpViaSocketFactory() {
    testConnectViaSocketFactory(false)
  }

  @Test
  fun connectHttpsViaSocketFactory() {
    testConnectViaSocketFactory(true)
  }

  @Test
  fun contentDisagreesWithChunkedHeaderBodyTooLong() {
    val mockResponse = MockResponse()
      .setChunkedBody("abc", 3)
    val buffer = mockResponse.getBody()
    buffer!!.writeUtf8("\r\nYOU SHOULD NOT SEE THIS")
    mockResponse.setBody(buffer)
    mockResponse.clearHeaders()
    mockResponse.addHeader("Transfer-encoding: chunked")
    server.enqueue(mockResponse)
    assertContent("abc", getResponse(newRequest("/")))
  }

  @Test
  fun contentDisagreesWithChunkedHeaderBodyTooShort() {
    val mockResponse = MockResponse()
      .setChunkedBody("abcdefg", 5)
    val truncatedBody = Buffer()
    val fullBody = mockResponse.getBody()
    truncatedBody.write(fullBody!!, 4)
    mockResponse.setBody(truncatedBody)
    mockResponse.clearHeaders()
    mockResponse.addHeader("Transfer-encoding: chunked")
    mockResponse.setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    server.enqueue(mockResponse)
    try {
      val response = getResponse(newRequest("/"))
      response.body.source().readUtf8(7)
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() {
    testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY)
  }

  @Test
  fun connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() {
    // https should not use http proxy
    testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY)
  }

  private fun testConnectViaDirectProxyToHttps(proxyConfig: ProxyConfig) {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("this response comes via HTTPS")
    )
    val url = server.url("/foo")
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val call = proxyConfig.connect(server, client, url)
    assertContent("this response comes via HTTPS", call.execute())
    val request = server.takeRequest()
    assertThat(request.requestLine).isEqualTo("GET /foo HTTP/1.1")
  }

  @Test
  fun connectViaHttpProxyToHttpsUsingProxyArg() {
    testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG)
  }

  /**
   * We weren't honoring all of the appropriate proxy system properties when connecting via HTTPS.
   * http://b/3097518
   */
  @Test
  fun connectViaHttpProxyToHttpsUsingProxySystemProperty() {
    testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY)
  }

  @Test
  fun connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() {
    testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY)
  }

  /**
   * We were verifying the wrong hostname when connecting to an HTTPS site through a proxy.
   * http://b/3097277
   */
  private fun testConnectViaHttpProxyToHttps(proxyConfig: ProxyConfig) {
    val hostnameVerifier = RecordingHostnameVerifier()
    server.useHttps(handshakeCertificates.sslSocketFactory(), true)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
        .clearHeaders()
    )
    server.enqueue(
      MockResponse()
        .setBody("this response comes via a secure proxy")
    )
    val url = "https://android.com/foo".toHttpUrl()
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(hostnameVerifier)
      .build()
    val call = proxyConfig.connect(server, client, url)
    assertContent("this response comes via a secure proxy", call.execute())
    val connect = server.takeRequest()
    assertThat(connect.requestLine).overridingErrorMessage(
      "Connect line failure on proxy"
    ).isEqualTo("CONNECT android.com:443 HTTP/1.1")
    assertThat(connect.getHeader("Host")).isEqualTo("android.com:443")
    val get = server.takeRequest()
    assertThat(get.requestLine).isEqualTo("GET /foo HTTP/1.1")
    assertThat(get.getHeader("Host")).isEqualTo("android.com")
    assertThat(hostnameVerifier.calls).isEqualTo(
      Arrays.asList("verify android.com")
    )
  }

  /** Tolerate bad https proxy response when using HttpResponseCache. Android bug 6754912.  */
  @Test
  fun connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() {
    initResponseCache()
    server.useHttps(handshakeCertificates.sslSocketFactory(), true)
    // The inclusion of a body in the response to a CONNECT is key to reproducing b/6754912.
    val badProxyResponse = MockResponse()
      .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
      .setBody("bogus proxy connect response content")
    server.enqueue(badProxyResponse)
    server.enqueue(
      MockResponse()
        .setBody("response")
    )

    // Configure a single IP address for the host and a single configuration, so we only need one
    // failure to fail permanently.
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS))
      .hostnameVerifier(RecordingHostnameVerifier())
      .proxy(server.toProxyAddress())
      .build()
    val response = getResponse(
      Request.Builder()
        .url("https://android.com/foo".toHttpUrl())
        .build()
    )
    assertContent("response", response)
    val connect = server.takeRequest()
    assertThat(connect.requestLine).isEqualTo(
      "CONNECT android.com:443 HTTP/1.1"
    )
    assertThat(connect.getHeader("Host")).isEqualTo("android.com:443")
  }

  private fun initResponseCache() {
    cache = Cache(tempDir, Int.MAX_VALUE.toLong())
    client = client.newBuilder()
      .cache(cache)
      .build()
  }

  /** Test which headers are sent unencrypted to the HTTP proxy.  */
  @Test
  fun proxyConnectIncludesProxyHeadersOnly() {
    val hostnameVerifier = RecordingHostnameVerifier()
    server.useHttps(handshakeCertificates.sslSocketFactory(), true)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
        .clearHeaders()
    )
    server.enqueue(
      MockResponse()
        .setBody("encrypted response from the origin server")
    )
    client = client.newBuilder()
      .proxy(server.toProxyAddress())
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(hostnameVerifier)
      .build()
    val response = getResponse(
      Request.Builder()
        .url("https://android.com/foo".toHttpUrl())
        .header("Private", "Secret")
        .header("Proxy-Authorization", "bar")
        .header("User-Agent", "baz")
        .build()
    )
    assertContent("encrypted response from the origin server", response)
    val connect = server.takeRequest()
    assertThat(connect.getHeader("Private")).isNull()
    assertThat(connect.getHeader("Proxy-Authorization")).isNull()
    assertThat(connect.getHeader("User-Agent")).isEqualTo(userAgent)
    assertThat(connect.getHeader("Host")).isEqualTo("android.com:443")
    assertThat(connect.getHeader("Proxy-Connection")).isEqualTo("Keep-Alive")
    val get = server.takeRequest()
    assertThat(get.getHeader("Private")).isEqualTo("Secret")
    assertThat(hostnameVerifier.calls).isEqualTo(listOf("verify android.com"))
  }

  @Test
  fun proxyAuthenticateOnConnect() {
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    server.useHttps(handshakeCertificates.sslSocketFactory(), true)
    server.enqueue(
      MockResponse()
        .setResponseCode(407)
        .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")
    )
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
        .clearHeaders()
    )
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    client = client.newBuilder()
      .proxyAuthenticator(Authenticator.JAVA_NET_AUTHENTICATOR)
      .proxy(server.toProxyAddress())
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(
      Request.Builder()
        .url("https://android.com/foo".toHttpUrlOrNull()!!)
        .build()
    )
    assertContent("A", response)
    val connect1 = server.takeRequest()
    assertThat(connect1.requestLine).isEqualTo("CONNECT android.com:443 HTTP/1.1")
    assertThat(connect1.getHeader("Proxy-Authorization")).isNull()
    val connect2 = server.takeRequest()
    assertThat(connect2.requestLine).isEqualTo("CONNECT android.com:443 HTTP/1.1")
    assertThat(connect2.getHeader("Proxy-Authorization"))
      .isEqualTo("Basic ${RecordingAuthenticator.BASE_64_CREDENTIALS}")
    val get = server.takeRequest()
    assertThat(get.requestLine).isEqualTo("GET /foo HTTP/1.1")
    assertThat(get.getHeader("Proxy-Authorization")).isNull()
  }

  // Don't disconnect after building a tunnel with CONNECT
  // http://code.google.com/p/android/issues/detail?id=37221
  @Test
  fun proxyWithConnectionClose() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), true)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
        .clearHeaders()
    )
    server.enqueue(
      MockResponse()
        .setBody("this response comes via a proxy")
    )
    client = client.newBuilder()
      .proxy(server.toProxyAddress())
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(
      Request.Builder()
        .url("https://android.com/foo")
        .header("Connection", "close")
        .build()
    )
    assertContent("this response comes via a proxy", response)
  }

  @Test
  fun proxyWithConnectionReuse() {
    val socketFactory = handshakeCertificates.sslSocketFactory()
    val hostnameVerifier = RecordingHostnameVerifier()
    server.useHttps(socketFactory, true)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
        .clearHeaders()
    )
    server.enqueue(
      MockResponse()
        .setBody("response 1")
    )
    server.enqueue(
      MockResponse()
        .setBody("response 2")
    )
    client = client.newBuilder()
      .proxy(server.toProxyAddress())
      .sslSocketFactory(socketFactory, handshakeCertificates.trustManager)
      .hostnameVerifier(hostnameVerifier)
      .build()
    assertContent("response 1", getResponse(Request("https://android.com/foo".toHttpUrl())))
    assertContent("response 2", getResponse(Request("https://android.com/foo".toHttpUrl())))
  }

  @Test
  fun proxySelectorHttpWithConnectionReuse() {
    server.enqueue(
      MockResponse()
        .setBody("response 1")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(407)
    )
    client = client.newBuilder()
      .proxySelector(object : ProxySelector() {
        override fun select(uri: URI): List = listOf(server.toProxyAddress())
        override fun connectFailed(uri: URI, socketAddress: SocketAddress, e: IOException) {
        }
      }).build()
    val url = "http://android.com/foo".toHttpUrl()
    assertContent("response 1", getResponse(Request(url)))
    assertThat(getResponse(Request(url)).code).isEqualTo(407)
  }

  @Test
  fun disconnectedConnection() {
    server.enqueue(
      MockResponse()
        .throttleBody(2, 100, TimeUnit.MILLISECONDS)
        .setBody("ABCD")
    )
    val call = client.newCall(newRequest("/"))
    val response = call.execute()
    val inputStream = response.body.byteStream()
    assertThat(inputStream.read().toChar()).isEqualTo('A')
    call.cancel()
    try {
      // Reading 'B' may succeed if it's buffered.
      inputStream.read()

      // But 'C' shouldn't be buffered (the response is throttled) and this should fail.
      inputStream.read()
      fail("Expected a connection closed exception")
    } catch (expected: IOException) {
    }
    inputStream.close()
  }

  @Test
  fun disconnectDuringConnect_cookieJar() {
    val callReference = AtomicReference()

    class DisconnectingCookieJar : CookieJar {
      override fun saveFromResponse(url: HttpUrl, cookies: List) {}
      override fun loadForRequest(url: HttpUrl): List {
        callReference.get().cancel()
        return emptyList()
      }
    }
    client = client.newBuilder()
      .cookieJar(DisconnectingCookieJar())
      .build()
    val call = client.newCall(newRequest("/"))
    callReference.set(call)
    try {
      call.execute()
      fail("Connection should not be established")
    } catch (expected: IOException) {
      assertThat(expected.message).isEqualTo("Canceled")
    }
  }

  @Test
  fun disconnectBeforeConnect() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val call = client.newCall(newRequest("/"))
    call.cancel()
    try {
      call.execute()
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun defaultRequestProperty() {
    URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A")
    assertThat(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")).isNull()
  }

  /**
   * Reads `count` characters from the stream. If the stream is exhausted before `count`
   * characters can be read, the remaining characters are returned and the stream is closed.
   */
  private fun readAscii(inputStream: InputStream, count: Int): String {
    val result = StringBuilder()
    for (i in 0 until count) {
      val value = inputStream.read()
      if (value == -1) {
        inputStream.close()
        break
      }
      result.append(value.toChar())
    }
    return result.toString()
  }

  @Test
  fun markAndResetWithContentLengthHeader() {
    testMarkAndReset(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun markAndResetWithChunkedEncoding() {
    testMarkAndReset(TransferKind.CHUNKED)
  }

  @Test
  fun markAndResetWithNoLengthHeaders() {
    testMarkAndReset(TransferKind.END_OF_STREAM)
  }

  private fun testMarkAndReset(transferKind: TransferKind) {
    val response = MockResponse()
    transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024)
    server.enqueue(response)
    server.enqueue(response)
    val inputStream = getResponse(newRequest("/")).body.byteStream()
    assertThat(inputStream.markSupported())
      .overridingErrorMessage("This implementation claims to support mark().")
      .isFalse
    inputStream.mark(5)
    assertThat(readAscii(inputStream, 5)).isEqualTo("ABCDE")
    try {
      inputStream.reset()
      fail()
    } catch (expected: IOException) {
    }
    assertThat(readAscii(inputStream, Int.MAX_VALUE)).isEqualTo(
      "FGHIJKLMNOPQRSTUVWXYZ"
    )
    inputStream.close()
    assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", getResponse(newRequest("/")))
  }

  /**
   * We've had a bug where we forget the HTTP response when we see response code 401. This causes a
   * new HTTP request to be issued for every call into the URLConnection.
   */
  @Test
  fun unauthorizedResponseHandling() {
    val mockResponse = MockResponse()
      .addHeader("WWW-Authenticate: challenge")
      .setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED)
      .setBody("Unauthorized")
    server.enqueue(mockResponse)
    server.enqueue(mockResponse)
    server.enqueue(mockResponse)
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(401)
    assertThat(response.code).isEqualTo(401)
    assertThat(response.code).isEqualTo(401)
    assertThat(server.requestCount).isEqualTo(1)
    response.body.close()
  }

  @Test
  fun nonHexChunkSize() {
    server.enqueue(
      MockResponse()
        .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
        .clearHeaders()
        .addHeader("Transfer-encoding: chunked")
    )
    try {
      getResponse(newRequest("/")).use { response ->
        response.body.string()
        fail()
      }
    } catch (expected: IOException) {
    }
  }

  @Test
  fun malformedChunkSize() {
    server.enqueue(
      MockResponse()
        .setBody("5:x\r\nABCDE\r\n0\r\n\r\n")
        .clearHeaders()
        .addHeader("Transfer-encoding: chunked")
    )
    try {
      getResponse(newRequest("/")).use { response ->
        readAscii(response.body.byteStream(), Int.MAX_VALUE)
        fail()
      }
    } catch (expected: IOException) {
    }
  }

  @Test
  fun extensionAfterChunkSize() {
    server.enqueue(
      MockResponse()
        .setBody("5;x\r\nABCDE\r\n0\r\n\r\n")
        .clearHeaders()
        .addHeader("Transfer-encoding: chunked")
    )
    getResponse(newRequest("/")).use { response -> assertContent("ABCDE", response) }
  }

  @Test
  fun missingChunkBody() {
    server.enqueue(
      MockResponse()
        .setBody("5")
        .clearHeaders()
        .addHeader("Transfer-encoding: chunked")
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    try {
      getResponse(newRequest("/")).use { response ->
        readAscii(response.body.byteStream(), Int.MAX_VALUE)
        fail()
      }
    } catch (expected: IOException) {
    }
  }

  /**
   * This test checks whether connections are gzipped by default. This behavior in not required by
   * the API, so a failure of this test does not imply a bug in the implementation.
   */
  @Test
  fun gzipEncodingEnabledByDefault() {
    server.enqueue(
      MockResponse()
        .setBody(gzip("ABCABCABC"))
        .addHeader("Content-Encoding: gzip")
    )
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "ABCABCABC"
    )
    assertThat(response.header("Content-Encoding")).isNull()
    assertThat(response.body.contentLength()).isEqualTo(-1L)
    val request = server.takeRequest()
    assertThat(request.getHeader("Accept-Encoding")).isEqualTo("gzip")
  }

  @Test
  fun clientConfiguredGzipContentEncoding() {
    val bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    server.enqueue(
      MockResponse()
        .setBody(bodyBytes)
        .addHeader("Content-Encoding: gzip")
    )
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .header("Accept-Encoding", "gzip")
        .build()
    )
    val gunzippedIn: InputStream = GZIPInputStream(response.body.byteStream())
    assertThat(readAscii(gunzippedIn, Int.MAX_VALUE)).isEqualTo("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    assertThat(response.body.contentLength()).isEqualTo(bodyBytes.size)
    val request = server.takeRequest()
    assertThat(request.getHeader("Accept-Encoding")).isEqualTo("gzip")
  }

  @Test
  fun gzipAndConnectionReuseWithFixedLength() {
    testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false)
  }

  @Test
  fun gzipAndConnectionReuseWithChunkedEncoding() {
    testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false)
  }

  @Test
  fun gzipAndConnectionReuseWithFixedLengthAndTls() {
    testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true)
  }

  @Test
  fun gzipAndConnectionReuseWithChunkedEncodingAndTls() {
    testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true)
  }

  @Test
  fun clientConfiguredCustomContentEncoding() {
    server.enqueue(
      MockResponse()
        .setBody("ABCDE")
        .addHeader("Content-Encoding: custom")
    )
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .header("Accept-Encoding", "custom")
        .build()
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo("ABCDE")
    val request = server.takeRequest()
    assertThat(request.getHeader("Accept-Encoding")).isEqualTo("custom")
  }

  /**
   * Test a bug where gzip input streams weren't exhausting the input stream, which corrupted the
   * request that followed or prevented connection reuse. http://code.google.com/p/android/issues/detail?id=7059
   * http://code.google.com/p/android/issues/detail?id=38817
   */
  private fun testClientConfiguredGzipContentEncodingAndConnectionReuse(
    transferKind: TransferKind,
    tls: Boolean
  ) {
    if (tls) {
      val socketFactory = handshakeCertificates.sslSocketFactory()
      val hostnameVerifier = RecordingHostnameVerifier()
      server.useHttps(socketFactory, false)
      client = client.newBuilder()
        .sslSocketFactory(socketFactory, handshakeCertificates.trustManager)
        .hostnameVerifier(hostnameVerifier)
        .build()
    }
    val responseOne = MockResponse()
      .addHeader("Content-Encoding: gzip")
    transferKind.setBody(responseOne, gzip("one (gzipped)"), 5)
    server.enqueue(responseOne)
    val responseTwo = MockResponse()
    transferKind.setBody(responseTwo, "two (identity)", 5)
    server.enqueue(responseTwo)
    val response1 = getResponse(
      Request.Builder()
        .header("Accept-Encoding", "gzip")
        .url(server.url("/"))
        .build()
    )
    val gunzippedIn: InputStream = GZIPInputStream(response1.body.byteStream())
    assertThat(readAscii(gunzippedIn, Int.MAX_VALUE)).isEqualTo("one (gzipped)")
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    val response2 = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .build()
    )
    assertThat(readAscii(response2.body.byteStream(), Int.MAX_VALUE)).isEqualTo("two (identity)")
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(1)
  }

  @Test
  fun transparentGzipWorksAfterExceptionRecovery() {
    server.enqueue(
      MockResponse()
        .setBody("a")
        .setSocketPolicy(SocketPolicy.SHUTDOWN_INPUT_AT_END)
    )
    server.enqueue(
      MockResponse()
        .addHeader("Content-Encoding: gzip")
        .setBody(gzip("b"))
    )

    // Seed the pool with a bad connection.
    assertContent("a", getResponse(newRequest("/")))

    // Give the server time to disconnect.
    Thread.sleep(500)

    // This connection will need to be recovered. When it is, transparent gzip should still work!
    assertContent("b", getResponse(newRequest("/")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    // Connection is not pooled.
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
  }

  @Test
  fun endOfStreamResponseIsNotPooled() {
    client.connectionPool.evictAll()
    server.enqueue(
      MockResponse()
        .setBody("{}")
        .clearHeaders()
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    val response = getResponse(newRequest("/"))
    assertContent("{}", response)
    assertThat(client.connectionPool.idleConnectionCount()).isEqualTo(0)
  }

  @Test
  fun earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() {
    testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED)
  }

  @Test
  fun earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() {
    testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH)
  }

  private fun testEarlyDisconnectDoesntHarmPooling(transferKind: TransferKind) {
    val mockResponse1 = MockResponse()
    transferKind.setBody(mockResponse1, "ABCDEFGHIJK", 1024)
    server.enqueue(mockResponse1)
    val mockResponse2 = MockResponse()
    transferKind.setBody(mockResponse2, "LMNOPQRSTUV", 1024)
    server.enqueue(mockResponse2)
    val call1 = client.newCall(newRequest("/"))
    val response1 = call1.execute()
    val in1 = response1.body.byteStream()
    assertThat(readAscii(in1, 5)).isEqualTo("ABCDE")
    in1.close()
    call1.cancel()
    val call2 = client.newCall(newRequest("/"))
    val response2 = call2.execute()
    val in2 = response2.body.byteStream()
    assertThat(readAscii(in2, 5)).isEqualTo("LMNOP")
    in2.close()
    call2.cancel()
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    // Connection is pooled!
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(1)
  }

  @Test
  fun streamDiscardingIsTimely() {
    // This response takes at least a full second to serve: 10,000 bytes served 100 bytes at a time.
    server.enqueue(
      MockResponse()
        .setBody(Buffer().write(ByteArray(10000)))
        .throttleBody(100, 10, TimeUnit.MILLISECONDS)
    )
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val startNanos = System.nanoTime()
    val connection1 = getResponse(newRequest("/"))
    val inputStream = connection1.body.byteStream()
    inputStream.close()
    val elapsedNanos = System.nanoTime() - startNanos
    val elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos)

    // If we're working correctly, this should be greater than 100ms, but less than double that.
    // Previously we had a bug where we would download the entire response body as long as no
    // individual read took longer than 100ms.
    assertThat(elapsedMillis).isLessThan(500L)

    // Do another request to confirm that the discarded connection was not pooled.
    assertContent("A", getResponse(newRequest("/")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    // Connection is not pooled.
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
  }

  @Test
  fun setChunkedStreamingMode() {
    server.enqueue(MockResponse())
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = TransferKind.CHUNKED.newRequestBody("ABCDEFGHIJKLMNOPQ"),
      )
    )
    assertThat(response.code).isEqualTo(200)
    val request = server.takeRequest()
    assertThat(request.body.readUtf8()).isEqualTo("ABCDEFGHIJKLMNOPQ")
    assertThat(request.chunkSizes).isEqualTo(
      Arrays.asList("ABCDEFGHIJKLMNOPQ".length)
    )
  }

  @Test
  fun authenticateWithFixedLengthStreaming() {
    testAuthenticateWithStreamingPost(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun authenticateWithChunkedStreaming() {
    testAuthenticateWithStreamingPost(TransferKind.CHUNKED)
  }

  private fun testAuthenticateWithStreamingPost(streamingMode: TransferKind) {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
        .setBody("Please authenticate.")
    )
    server.enqueue(
      MockResponse()
        .setBody("Authenticated!")
    )
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val request = Request(
      url = server.url("/"),
      body = streamingMode.newRequestBody("ABCD"),
    )
    val response = getResponse(request)
    assertThat(response.code).isEqualTo(200)
    assertContent("Authenticated!", response)

    // No authorization header for the request...
    val recordedRequest = server.takeRequest()
    assertThat(recordedRequest.getHeader("Authorization")).isNull()
    assertThat(recordedRequest.body.readUtf8()).isEqualTo("ABCD")
  }

  @Test
  fun postBodyRetransmittedAfterAuthorizationFail() {
    postBodyRetransmittedAfterAuthorizationFail("abc")
  }

  @Test
  fun postBodyRetransmittedAfterAuthorizationFail_HTTP_2() {
    platform.assumeHttp2Support()
    enableProtocol(Protocol.HTTP_2)
    postBodyRetransmittedAfterAuthorizationFail("abc")
  }

  /** Don't explode when resending an empty post. https://github.com/square/okhttp/issues/1131  */
  @Test
  fun postEmptyBodyRetransmittedAfterAuthorizationFail() {
    postBodyRetransmittedAfterAuthorizationFail("")
  }

  @Test
  fun postEmptyBodyRetransmittedAfterAuthorizationFail_HTTP_2() {
    platform.assumeHttp2Support()
    enableProtocol(Protocol.HTTP_2)
    postBodyRetransmittedAfterAuthorizationFail("")
  }

  private fun postBodyRetransmittedAfterAuthorizationFail(body: String) {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
    )
    server.enqueue(MockResponse())
    val credential = basic("jesse", "secret")
    client = client.newBuilder()
      .authenticator(RecordingOkAuthenticator(credential, null))
      .build()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = body.toRequestBody(),
      )
    )
    assertThat(response.code).isEqualTo(200)
    response.body.byteStream().close()
    val recordedRequest1 = server.takeRequest()
    assertThat(recordedRequest1.method).isEqualTo("POST")
    assertThat(recordedRequest1.body.readUtf8()).isEqualTo(body)
    assertThat(recordedRequest1.getHeader("Authorization")).isNull()
    val recordedRequest2 = server.takeRequest()
    assertThat(recordedRequest2.method).isEqualTo("POST")
    assertThat(recordedRequest2.body.readUtf8()).isEqualTo(body)
    assertThat(recordedRequest2.getHeader("Authorization")).isEqualTo(credential)
  }

  @Test
  fun nonStandardAuthenticationScheme() {
    val calls = authCallsForHeader("WWW-Authenticate: Foo")
    assertThat(calls).isEqualTo(emptyList())
  }

  @Test
  fun nonStandardAuthenticationSchemeWithRealm() {
    val calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\"")
    assertThat(calls.size).isEqualTo(0)
  }

  // Digest auth is currently unsupported. Test that digest requests should fail reasonably.
  // http://code.google.com/p/android/issues/detail?id=11140
  @Test
  fun digestAuthentication() {
    val calls = authCallsForHeader(
      "WWW-Authenticate: Digest "
        + "realm=\"[email protected]\", qop=\"auth,auth-int\", "
        + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
        + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""
    )
    assertThat(calls.size).isEqualTo(0)
  }

  @Test
  fun allAttributesSetInServerAuthenticationCallbacks() {
    val calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\"")
    assertThat(calls.size).isEqualTo(1)
    val url = server.url("/").toUrl()
    val call = calls[0]
    assertThat(call).contains("host=" + url.host)
    assertThat(call).contains("port=" + url.port)
    assertThat(call).contains("site=" + url.host)
    assertThat(call).contains("url=$url")
    assertThat(call).contains("type=" + java.net.Authenticator.RequestorType.SERVER)
    assertThat(call).contains("prompt=Bar")
    assertThat(call).contains("protocol=http")
    assertThat(call.lowercase(Locale.US))
      .contains("scheme=basic") // lowercase for the RI.
  }

  @Test
  fun allAttributesSetInProxyAuthenticationCallbacks() {
    val calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\"")
    assertThat(calls.size).isEqualTo(1)
    val url = server.url("/").toUrl()
    val call = calls[0]
    assertThat(call).contains("host=" + url.host)
    assertThat(call).contains("port=" + url.port)
    assertThat(call).contains("site=" + url.host)
    assertThat(call).contains("url=http://android.com")
    assertThat(call).contains("type=" + java.net.Authenticator.RequestorType.PROXY)
    assertThat(call).contains("prompt=Bar")
    assertThat(call).contains("protocol=http")
    assertThat(call.lowercase(Locale.US)).contains("scheme=basic")
  }

  private fun authCallsForHeader(authHeader: String): List {
    val proxy = authHeader.startsWith("Proxy-")
    val responseCode = if (proxy) 407 else 401
    val authenticator = RecordingAuthenticator(null)
    java.net.Authenticator.setDefault(authenticator)
    server.enqueue(
      MockResponse()
        .setResponseCode(responseCode)
        .addHeader(authHeader)
        .setBody("Please authenticate.")
    )
    val response: Response
    if (proxy) {
      client = client.newBuilder()
        .proxy(server.toProxyAddress())
        .proxyAuthenticator(JavaNetAuthenticator())
        .build()
      response = getResponse(Request("http://android.com/".toHttpUrl()))
    } else {
      client = client.newBuilder()
        .authenticator(JavaNetAuthenticator())
        .build()
      response = getResponse(newRequest("/"))
    }
    assertThat(response.code).isEqualTo(responseCode)
    response.body.byteStream().close()
    return authenticator.calls
  }

  @Test
  fun setValidRequestMethod() {
    assertMethodForbidsRequestBody("GET")
    assertMethodPermitsRequestBody("DELETE")
    assertMethodForbidsRequestBody("HEAD")
    assertMethodPermitsRequestBody("OPTIONS")
    assertMethodPermitsRequestBody("POST")
    assertMethodPermitsRequestBody("PUT")
    assertMethodPermitsRequestBody("TRACE")
    assertMethodPermitsRequestBody("PATCH")
    assertMethodPermitsNoRequestBody("GET")
    assertMethodPermitsNoRequestBody("DELETE")
    assertMethodPermitsNoRequestBody("HEAD")
    assertMethodPermitsNoRequestBody("OPTIONS")
    assertMethodForbidsNoRequestBody("POST")
    assertMethodForbidsNoRequestBody("PUT")
    assertMethodPermitsNoRequestBody("TRACE")
    assertMethodForbidsNoRequestBody("PATCH")
  }

  private fun assertMethodPermitsRequestBody(requestMethod: String) {
    val request = Request.Builder()
      .url(server.url("/"))
      .method(requestMethod, "abc".toRequestBody(null))
      .build()
    assertThat(request.method).isEqualTo(requestMethod)
  }

  private fun assertMethodForbidsRequestBody(requestMethod: String) {
    try {
      Request.Builder()
        .url(server.url("/"))
        .method(requestMethod, "abc".toRequestBody(null))
        .build()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  private fun assertMethodPermitsNoRequestBody(requestMethod: String) {
    val request = Request.Builder()
      .url(server.url("/"))
      .method(requestMethod, null)
      .build()
    assertThat(request.method).isEqualTo(requestMethod)
  }

  private fun assertMethodForbidsNoRequestBody(requestMethod: String) {
    try {
      Request.Builder()
        .url(server.url("/"))
        .method(requestMethod, null)
        .build()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun setInvalidRequestMethodLowercase() {
    assertValidRequestMethod("get")
  }

  @Test
  fun setInvalidRequestMethodConnect() {
    assertValidRequestMethod("CONNECT")
  }

  private fun assertValidRequestMethod(requestMethod: String) {
    server.enqueue(MockResponse())
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .method(requestMethod, null)
        .build()
    )
    assertThat(response.code).isEqualTo(200)
    val recordedRequest = server.takeRequest()
    assertThat(recordedRequest.method).isEqualTo(requestMethod)
  }

  @Test
  fun shoutcast() {
    server.enqueue(
      MockResponse()
        .setStatus("ICY 200 OK")
        .addHeader("Accept-Ranges: none")
        .addHeader("Content-Type: audio/mpeg")
        .addHeader("icy-br:128")
        .addHeader("ice-audio-info: bitrate=128;samplerate=44100;channels=2")
        .addHeader("icy-br:128")
        .addHeader("icy-description:Rock")
        .addHeader("icy-genre:riders")
        .addHeader("icy-name:A2RRock")
        .addHeader("icy-pub:1")
        .addHeader("icy-url:http://www.A2Rradio.com")
        .addHeader("Server: Icecast 2.3.3-kh8")
        .addHeader("Cache-Control: no-cache")
        .addHeader("Pragma: no-cache")
        .addHeader("Expires: Mon, 26 Jul 1997 05:00:00 GMT")
        .addHeader("icy-metaint:16000")
        .setBody("mp3 data")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(200)
    assertThat(response.message).isEqualTo("OK")
    assertContent("mp3 data", response)
  }

  @Test
  fun secureFixedLengthStreaming() {
    testSecureStreamingPost(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun secureChunkedStreaming() {
    testSecureStreamingPost(TransferKind.CHUNKED)
  }

  /**
   * Users have reported problems using HTTPS with streaming request bodies.
   * http://code.google.com/p/android/issues/detail?id=12860
   */
  private fun testSecureStreamingPost(streamingMode: TransferKind) {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("Success!")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = streamingMode.newRequestBody("ABCD"),
      )
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "Success!"
    )
    val request = server.takeRequest()
    assertThat(request.requestLine).isEqualTo("POST / HTTP/1.1")
    if (streamingMode === TransferKind.FIXED_LENGTH) {
      assertThat(request.chunkSizes).isEqualTo(emptyList())
    } else if (streamingMode === TransferKind.CHUNKED) {
      assertThat(request.chunkSizes).containsExactly(4)
    }
    assertThat(request.body.readUtf8()).isEqualTo("ABCD")
  }

  @Test
  fun authenticateWithPost() {
    val pleaseAuthenticate = MockResponse()
      .setResponseCode(401)
      .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
      .setBody("Please authenticate.")
    // Fail auth three times...
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    // ...then succeed the fourth time.
    server.enqueue(
      MockResponse()
        .setBody("Successful auth!")
    )
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = "ABCD".toRequestBody(null),
      )
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "Successful auth!"
    )

    // No authorization header for the first request...
    var request = server.takeRequest()
    assertThat(request.getHeader("Authorization")).isNull()

    // ...but the three requests that follow include an authorization header.
    for (i in 0..2) {
      request = server.takeRequest()
      assertThat(request.requestLine).isEqualTo("POST / HTTP/1.1")
      assertThat(request.getHeader("Authorization")).isEqualTo(
        "Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS
      )
      assertThat(request.body.readUtf8()).isEqualTo("ABCD")
    }
  }

  @Test
  fun authenticateWithGet() {
    val pleaseAuthenticate = MockResponse()
      .setResponseCode(401)
      .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
      .setBody("Please authenticate.")
    // Fail auth three times...
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    // ...then succeed the fourth time.
    server.enqueue(
      MockResponse()
        .setBody("Successful auth!")
    )
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Successful auth!")

    // No authorization header for the first request...
    var request = server.takeRequest()
    assertThat(request.getHeader("Authorization")).isNull()

    // ...but the three requests that follow requests include an authorization header.
    for (i in 0..2) {
      request = server.takeRequest()
      assertThat(request.requestLine).isEqualTo("GET / HTTP/1.1")
      assertThat(request.getHeader("Authorization"))
        .isEqualTo("Basic ${RecordingAuthenticator.BASE_64_CREDENTIALS}")
    }
  }

  @Test
  fun authenticateWithCharset() {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Basic realm=\"protected area\", charset=\"UTF-8\"")
        .setBody("Please authenticate with UTF-8.")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
        .setBody("Please authenticate with ISO-8859-1.")
    )
    server.enqueue(
      MockResponse()
        .setBody("Successful auth!")
    )
    java.net.Authenticator.setDefault(
      RecordingAuthenticator(
        PasswordAuthentication("username", "mötorhead".toCharArray())
      )
    )
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Successful auth!")

    // No authorization header for the first request...
    val request1 = server.takeRequest()
    assertThat(request1.getHeader("Authorization")).isNull()

    // UTF-8 encoding for the first credential.
    val request2 = server.takeRequest()
    assertThat(request2.getHeader("Authorization")).isEqualTo(
      "Basic dXNlcm5hbWU6bcO2dG9yaGVhZA=="
    )

    // ISO-8859-1 encoding for the second credential.
    val request3 = server.takeRequest()
    assertThat(request3.getHeader("Authorization"))
      .isEqualTo("Basic dXNlcm5hbWU6bfZ0b3JoZWFk")
  }

  /** https://code.google.com/p/android/issues/detail?id=74026  */
  @Test
  fun authenticateWithGetAndTransparentGzip() {
    val pleaseAuthenticate = MockResponse()
      .setResponseCode(401)
      .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
      .setBody("Please authenticate.")
    // Fail auth three times...
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    server.enqueue(pleaseAuthenticate)
    // ...then succeed the fourth time.
    val successfulResponse = MockResponse()
      .addHeader("Content-Encoding", "gzip")
      .setBody(gzip("Successful auth!"))
    server.enqueue(successfulResponse)
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Successful auth!")

    // no authorization header for the first request...
    var request = server.takeRequest()
    assertThat(request.getHeader("Authorization")).isNull()

    // ...but the three requests that follow requests include an authorization header
    for (i in 0..2) {
      request = server.takeRequest()
      assertThat(request.requestLine).isEqualTo("GET / HTTP/1.1")
      assertThat(request.getHeader("Authorization")).isEqualTo(
        "Basic ${RecordingAuthenticator.BASE_64_CREDENTIALS}"
      )
    }
  }

  /** https://github.com/square/okhttp/issues/342  */
  @Test
  fun authenticateRealmUppercase() {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("wWw-aUtHeNtIcAtE: bAsIc rEaLm=\"pRoTeCtEd aReA\"")
        .setBody("Please authenticate.")
    )
    server.enqueue(
      MockResponse()
        .setBody("Successful auth!")
    )
    java.net.Authenticator.setDefault(RecordingAuthenticator())
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Successful auth!")
  }

  @Test
  fun redirectedWithChunkedEncoding() {
    testRedirected(TransferKind.CHUNKED, true)
  }

  @Test
  fun redirectedWithContentLengthHeader() {
    testRedirected(TransferKind.FIXED_LENGTH, true)
  }

  @Test
  fun redirectedWithNoLengthHeaders() {
    testRedirected(TransferKind.END_OF_STREAM, false)
  }

  private fun testRedirected(transferKind: TransferKind, reuse: Boolean) {
    val mockResponse = MockResponse()
      .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
      .addHeader("Location: /foo")
    transferKind.setBody(mockResponse, "This page has moved!", 10)
    server.enqueue(mockResponse)
    server.enqueue(
      MockResponse()
        .setBody("This is the new location!")
    )
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "This is the new location!"
    )
    val first = server.takeRequest()
    assertThat(first.requestLine).isEqualTo("GET / HTTP/1.1")
    val retry = server.takeRequest()
    assertThat(retry.requestLine).isEqualTo("GET /foo HTTP/1.1")
    if (reuse) {
      assertThat(retry.sequenceNumber)
        .overridingErrorMessage("Expected connection reuse")
        .isEqualTo(1)
    }
  }

  @Test
  fun redirectedOnHttps() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: /foo")
        .setBody("This page has moved!")
    )
    server.enqueue(
      MockResponse()
        .setBody("This is the new location!")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "This is the new location!"
    )
    val first = server.takeRequest()
    assertThat(first.requestLine).isEqualTo("GET / HTTP/1.1")
    val retry = server.takeRequest()
    assertThat(retry.requestLine).isEqualTo("GET /foo HTTP/1.1")
    assertThat(retry.sequenceNumber)
      .overridingErrorMessage("Expected connection reuse")
      .isEqualTo(1)
  }

  @Test
  fun notRedirectedFromHttpsToHttp() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: http://anyhost/foo")
        .setBody("This page has moved!")
    )
    client = client.newBuilder()
      .followSslRedirects(false)
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("This page has moved!")
  }

  @Test
  fun notRedirectedFromHttpToHttps() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: https://anyhost/foo")
        .setBody("This page has moved!")
    )
    client = client.newBuilder()
      .followSslRedirects(false)
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("This page has moved!")
  }

  @Test
  fun redirectedFromHttpsToHttpFollowingProtocolRedirects() {
    server2.enqueue(
      MockResponse()
        .setBody("This is insecure HTTP!")
    )
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: " + server2.url("/").toUrl())
        .setBody("This page has moved!")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .followSslRedirects(true)
      .build()
    val response = getResponse(newRequest("/"))
    assertContent("This is insecure HTTP!", response)
    assertThat(response.handshake).isNull()
  }

  @Test
  fun redirectedFromHttpToHttpsFollowingProtocolRedirects() {
    server2.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server2.enqueue(
      MockResponse()
        .setBody("This is secure HTTPS!")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: " + server2.url("/").toUrl())
        .setBody("This page has moved!")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .followSslRedirects(true)
      .build()
    val response = getResponse(newRequest("/"))
    assertContent("This is secure HTTPS!", response)
  }

  @Test
  fun redirectToAnotherOriginServer() {
    redirectToAnotherOriginServer(false)
  }

  @Test
  fun redirectToAnotherOriginServerWithHttps() {
    redirectToAnotherOriginServer(true)
  }

  private fun redirectToAnotherOriginServer(https: Boolean) {
    if (https) {
      server.useHttps(handshakeCertificates.sslSocketFactory(), false)
      server2.useHttps(handshakeCertificates.sslSocketFactory(), false)
      server2.protocolNegotiationEnabled = false
      client = client.newBuilder()
        .sslSocketFactory(
          handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
        )
        .hostnameVerifier(RecordingHostnameVerifier())
        .build()
    }
    server2.enqueue(
      MockResponse()
        .setBody("This is the 2nd server!")
    )
    server2.enqueue(
      MockResponse()
        .setBody("This is the 2nd server, again!")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: " + server2.url("/").toUrl())
        .setBody("This page has moved!")
    )
    server.enqueue(
      MockResponse()
        .setBody("This is the first server again!")
    )
    val response = getResponse(newRequest("/"))
    assertContent("This is the 2nd server!", response)
    assertThat(response.request.url).isEqualTo(
      server2.url("/")
    )

    // make sure the first server was careful to recycle the connection
    assertContent("This is the first server again!", getResponse(Request(server.url("/"))))
    assertContent("This is the 2nd server, again!", getResponse(Request(server2.url("/"))))
    val server1Host = server.hostName + ":" + server.port
    val server2Host = server2.hostName + ":" + server2.port
    assertThat(server.takeRequest().getHeader("Host")).isEqualTo(server1Host)
    assertThat(server2.takeRequest().getHeader("Host")).isEqualTo(server2Host)
    assertThat(server.takeRequest().sequenceNumber)
      .overridingErrorMessage("Expected connection reuse")
      .isEqualTo(1)
    assertThat(server2.takeRequest().sequenceNumber)
      .overridingErrorMessage("Expected connection reuse")
      .isEqualTo(1)
  }

  @Test
  fun redirectWithProxySelector() {
    val proxySelectionRequests: MutableList = ArrayList()
    client = client.newBuilder()
      .proxySelector(object : ProxySelector() {
        override fun select(uri: URI): List {
          proxySelectionRequests.add(uri)
          val proxyServer = if (uri.port == server.port) server else server2
          return listOf(proxyServer.toProxyAddress())
        }

        override fun connectFailed(uri: URI, address: SocketAddress, failure: IOException) {
          throw AssertionError()
        }
      })
      .build()
    server2.enqueue(
      MockResponse()
        .setBody("This is the 2nd server!")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: " + server2.url("/b"))
        .setBody("This page has moved!")
    )
    assertContent("This is the 2nd server!", getResponse(newRequest("/a")))
    assertThat(proxySelectionRequests).isEqualTo(
      listOf(
        server.url("/").toUrl().toURI(),
        server2.url("/").toUrl().toURI()
      )
    )
  }

  @Test
  fun redirectWithAuthentication() {
    server2.enqueue(
      MockResponse()
        .setBody("Page 2")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(302)
        .addHeader("Location: " + server2.url("/b"))
    )
    client = client.newBuilder()
      .authenticator(RecordingOkAuthenticator(basic("jesse", "secret"), null))
      .build()
    assertContent("Page 2", getResponse(newRequest("/a")))
    val redirectRequest = server2.takeRequest()
    assertThat(redirectRequest.getHeader("Authorization")).isNull()
    assertThat(redirectRequest.path).isEqualTo("/b")
  }

  @Test
  fun response300MultipleChoiceWithPost() {
    // Chrome doesn't follow the redirect, but Firefox and the RI both do
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE, TransferKind.END_OF_STREAM)
  }

  @Test
  fun response301MovedPermanentlyWithPost() {
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM, TransferKind.END_OF_STREAM)
  }

  @Test
  fun response302MovedTemporarilyWithPost() {
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.END_OF_STREAM)
  }

  @Test
  fun response303SeeOtherWithPost() {
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER, TransferKind.END_OF_STREAM)
  }

  @Test
  fun postRedirectToGetWithChunkedRequest() {
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.CHUNKED)
  }

  @Test
  fun postRedirectToGetWithStreamedRequest() {
    testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.FIXED_LENGTH)
  }

  private fun testResponseRedirectedWithPost(redirectCode: Int, transferKind: TransferKind) {
    server.enqueue(
      MockResponse()
        .setResponseCode(redirectCode)
        .addHeader("Location: /page2")
        .setBody("This page has moved!")
    )
    server.enqueue(
      MockResponse()
        .setBody("Page 2")
    )
    val response = getResponse(
      Request(
        url = server.url("/page1"),
        body = transferKind.newRequestBody("ABCD"),
      )
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Page 2")
    val page1 = server.takeRequest()
    assertThat(page1.requestLine).isEqualTo("POST /page1 HTTP/1.1")
    assertThat(page1.body.readUtf8()).isEqualTo("ABCD")
    val page2 = server.takeRequest()
    assertThat(page2.requestLine).isEqualTo("GET /page2 HTTP/1.1")
  }

  @Test
  fun redirectedPostStripsRequestBodyHeaders() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: /page2")
    )
    server.enqueue(
      MockResponse()
        .setBody("Page 2")
    )
    val response = getResponse(
      Request.Builder()
        .url(server.url("/page1"))
        .post("ABCD".toRequestBody("text/plain; charset=utf-8".toMediaType()))
        .header("Transfer-Encoding", "identity")
        .build()
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Page 2")
    assertThat(server.takeRequest().requestLine)
      .isEqualTo("POST /page1 HTTP/1.1")
    val page2 = server.takeRequest()
    assertThat(page2.requestLine).isEqualTo("GET /page2 HTTP/1.1")
    assertThat(page2.getHeader("Content-Length")).isNull()
    assertThat(page2.getHeader("Content-Type")).isNull()
    assertThat(page2.getHeader("Transfer-Encoding")).isNull()
  }

  @Test
  fun response305UseProxy() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
        .addHeader("Location: " + server.url("/").toUrl())
        .setBody("This page has moved!")
    )
    server.enqueue(
      MockResponse()
        .setBody("Proxy Response")
    )
    val response = getResponse(newRequest("/foo"))
    // Fails on the RI, which gets "Proxy Response".
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("This page has moved!")
    val page1 = server.takeRequest()
    assertThat(page1.requestLine).isEqualTo("GET /foo HTTP/1.1")
    assertThat(server.requestCount).isEqualTo(1)
  }

  @Test
  fun response307WithGet() {
    testRedirect(true, "GET")
  }

  @Test
  fun response307WithHead() {
    testRedirect(true, "HEAD")
  }

  @Test
  fun response307WithOptions() {
    testRedirect(true, "OPTIONS")
  }

  @Test
  fun response307WithPost() {
    testRedirect(true, "POST")
  }

  @Test
  fun response308WithGet() {
    testRedirect(false, "GET")
  }

  @Test
  fun response308WithHead() {
    testRedirect(false, "HEAD")
  }

  @Test
  fun response308WithOptions() {
    testRedirect(false, "OPTIONS")
  }

  @Test
  fun response308WithPost() {
    testRedirect(false, "POST")
  }

  /**
   * In OkHttp 4.5 and earlier, HTTP 307 and 308 redirects were only honored if the request method
   * was GET or HEAD.
   *
   * In OkHttp 4.6 and later, HTTP 307 and 308 redirects are honored for all request methods.
   *
   * If you're upgrading to OkHttp 4.6 and would like to retain the previous behavior, install this
   * as a **network interceptor**. It will strip the `Location` header of impacted responses to
   * prevent the redirect.
   *
   * ```
   * OkHttpClient client = client.newBuilder()
   *   .addNetworkInterceptor(new LegacyRedirectInterceptor())
   *   .build();
   * ```
   */
  internal class LegacyRedirectInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
      val response = chain.proceed(chain.request())
      val code = response.code
      if (code != HTTP_TEMP_REDIRECT && code != HTTP_PERM_REDIRECT) return response
      val method = response.request.method
      if (method == "GET" || method == "HEAD") return response
      val location = response.header("Location") ?: return response
      return response.newBuilder()
        .removeHeader("Location")
        .header("LegacyRedirectInterceptor-Location", location)
        .build()
    }
  }

  @Test
  fun response307WithPostReverted() {
    client = client.newBuilder()
      .addNetworkInterceptor(LegacyRedirectInterceptor())
      .build()
    val response1 = MockResponse()
      .setResponseCode(HTTP_TEMP_REDIRECT)
      .setBody("This page has moved!")
      .addHeader("Location: /page2")
    server.enqueue(response1)
    val request = Request(
      url = server.url("/page1"),
      body = "ABCD".toRequestBody(null),
    )
    val response = getResponse(request)
    val responseString = readAscii(response.body.byteStream(), Int.MAX_VALUE)
    val page1 = server.takeRequest()
    assertThat(page1.requestLine).isEqualTo("POST /page1 HTTP/1.1")
    assertThat(page1.body.readUtf8()).isEqualTo("ABCD")
    assertThat(server.requestCount).isEqualTo(1)
    assertThat(responseString).isEqualTo("This page has moved!")
  }

  @Test
  fun response308WithPostReverted() {
    client = client.newBuilder()
      .addNetworkInterceptor(LegacyRedirectInterceptor())
      .build()
    val response1 = MockResponse()
      .setResponseCode(HTTP_PERM_REDIRECT)
      .setBody("This page has moved!")
      .addHeader("Location: /page2")
    server.enqueue(response1)
    val request = Request(
      url = server.url("/page1"),
      body = "ABCD".toRequestBody(null),
    )
    val response = getResponse(request)
    val responseString = readAscii(response.body.byteStream(), Int.MAX_VALUE)
    val page1 = server.takeRequest()
    assertThat(page1.requestLine).isEqualTo("POST /page1 HTTP/1.1")
    assertThat(page1.body.readUtf8()).isEqualTo("ABCD")
    assertThat(server.requestCount).isEqualTo(1)
    assertThat(responseString).isEqualTo("This page has moved!")
  }

  private fun testRedirect(temporary: Boolean, method: String) {
    val response1 = MockResponse()
      .setResponseCode(
        if (temporary) HTTP_TEMP_REDIRECT else HTTP_PERM_REDIRECT
      )
      .addHeader("Location: /page2")
    if (method != "HEAD") {
      response1.setBody("This page has moved!")
    }
    server.enqueue(response1)
    server.enqueue(
      MockResponse()
        .setBody("Page 2")
    )
    val requestBuilder = Request.Builder()
      .url(server.url("/page1"))
    if (method == "POST") {
      requestBuilder.post("ABCD".toRequestBody(null))
    } else {
      requestBuilder.method(method, null)
    }
    val response = getResponse(requestBuilder.build())
    val responseString = readAscii(response.body.byteStream(), Int.MAX_VALUE)
    val page1 = server.takeRequest()
    assertThat(page1.requestLine).isEqualTo(
      "$method /page1 HTTP/1.1"
    )
    if (method == "GET") {
      assertThat(responseString).isEqualTo("Page 2")
    } else if (method == "HEAD") {
      assertThat(responseString).isEqualTo("")
    }
    assertThat(server.requestCount).isEqualTo(2)
    val page2 = server.takeRequest()
    assertThat(page2.requestLine)
      .isEqualTo("$method /page2 HTTP/1.1")
  }

  @Test
  fun follow20Redirects() {
    for (i in 0..19) {
      server.enqueue(
        MockResponse()
          .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
          .addHeader("Location: /" + (i + 1))
          .setBody("Redirecting to /" + (i + 1))
      )
    }
    server.enqueue(
      MockResponse()
        .setBody("Success!")
    )
    val response = getResponse(newRequest("/0"))
    assertContent("Success!", response)
    assertThat(response.request.url)
      .isEqualTo(server.url("/20"))
  }

  @Test
  fun doesNotFollow21Redirects() {
    for (i in 0..20) {
      server.enqueue(
        MockResponse()
          .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
          .addHeader("Location: /" + (i + 1))
          .setBody("Redirecting to /" + (i + 1))
      )
    }
    try {
      getResponse(newRequest("/0"))
      fail()
    } catch (expected: ProtocolException) {
      assertThat(expected.message).isEqualTo(
        "Too many follow-up requests: 21"
      )
    }
  }

  @Test
  fun httpsWithCustomTrustManager() {
    val hostnameVerifier = RecordingHostnameVerifier()
    val trustManager = RecordingTrustManager(handshakeCertificates.trustManager)
    val sslContext = get().newSSLContext()
    sslContext.init(null, arrayOf(trustManager), null)
    client = client.newBuilder()
      .hostnameVerifier(hostnameVerifier)
      .sslSocketFactory(sslContext.socketFactory, trustManager)
      .build()
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.enqueue(
      MockResponse()
        .setBody("ABC")
    )
    server.enqueue(
      MockResponse()
        .setBody("DEF")
    )
    server.enqueue(
      MockResponse()
        .setBody("GHI")
    )
    assertContent("ABC", getResponse(newRequest("/")))
    assertContent("DEF", getResponse(newRequest("/")))
    assertContent("GHI", getResponse(newRequest("/")))
    assertThat(hostnameVerifier.calls)
      .isEqualTo(listOf("verify " + server.hostName))
    assertThat(trustManager.calls)
      .isEqualTo(listOf("checkServerTrusted [CN=localhost 1]"))
  }

  @Test
  fun getClientRequestTimeout() {
    enqueueClientRequestTimeoutResponses()
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(200)
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Body")
  }

  private fun enqueueClientRequestTimeoutResponses() {
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
        .setResponseCode(HttpURLConnection.HTTP_CLIENT_TIMEOUT)
        .setHeader("Connection", "Close")
        .setBody("You took too long!")
    )
    server.enqueue(
      MockResponse()
        .setBody("Body")
    )
  }

  @Test
  fun bufferedBodyWithClientRequestTimeout() {
    enqueueClientRequestTimeoutResponses()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = "Hello".toRequestBody(null),
      )
    )
    assertThat(response.code).isEqualTo(200)
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("Body")
    val request1 = server.takeRequest()
    assertThat(request1.body.readUtf8()).isEqualTo("Hello")
    val request2 = server.takeRequest()
    assertThat(request2.body.readUtf8()).isEqualTo("Hello")
  }

  @Test
  fun streamedBodyWithClientRequestTimeout() {
    enqueueClientRequestTimeoutResponses()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = TransferKind.CHUNKED.newRequestBody("Hello"),
      )
    )
    assertThat(response.code).isEqualTo(200)
    assertContent("Body", response)
    response.close()
    assertThat(server.requestCount).isEqualTo(2)
  }

  @Test
  fun readTimeouts() {
    // This relies on the fact that MockWebServer doesn't close the
    // connection after a response has been sent. This causes the client to
    // try to read more bytes than are sent, which results in a timeout.
    server.enqueue(
      MockResponse()
        .setBody("ABC")
        .clearHeaders()
        .addHeader("Content-Length: 4")
    )
    server.enqueue(
      MockResponse()
        .setBody("unused")
    ) // to keep the server alive
    val response = getResponse(newRequest("/"))
    val source = response.body.source()
    source.timeout().timeout(1000, TimeUnit.MILLISECONDS)
    assertThat(source.readByte()).isEqualTo('A'.code.toByte())
    assertThat(source.readByte()).isEqualTo('B'.code.toByte())
    assertThat(source.readByte()).isEqualTo('C'.code.toByte())
    try {
      source.readByte() // If Content-Length was accurate, this would return -1 immediately.
      fail()
    } catch (expected: SocketTimeoutException) {
    }
    source.close()
  }

  /** Confirm that an unacknowledged write times out.  */
  @Test
  fun writeTimeouts() {
    val server = MockWebServer()
    // Sockets on some platforms can have large buffers that mean writes do not block when
    // required. These socket factories explicitly set the buffer sizes on sockets created.
    val SOCKET_BUFFER_SIZE = 4 * 1024
    server.serverSocketFactory = object : DelegatingServerSocketFactory(getDefault()) {
      override fun configureServerSocket(serverSocket: ServerSocket): ServerSocket {
        serverSocket.receiveBufferSize = SOCKET_BUFFER_SIZE
        return serverSocket
      }
    }
    client = client.newBuilder()
      .socketFactory(object : DelegatingSocketFactory(getDefault()) {
        override fun configureSocket(socket: Socket): Socket {
          socket.receiveBufferSize = SOCKET_BUFFER_SIZE
          socket.sendBufferSize = SOCKET_BUFFER_SIZE
          return socket
        }
      })
      .writeTimeout(Duration.ofMillis(500))
      .build()
    server.start()
    server.enqueue(
      MockResponse()
        .throttleBody(1, 1, TimeUnit.SECONDS)
    ) // Prevent the server from reading!
    val request = Request(
      url = server.url("/"),
      body = object : RequestBody() {
        override fun contentType(): MediaType? {
          return null
        }

        override fun writeTo(sink: BufferedSink) {
          val data = ByteArray(2 * 1024 * 1024) // 2 MiB.
          sink.write(data)
        }
      },
    )
    try {
      getResponse(request)
      fail()
    } catch (expected: SocketTimeoutException) {
    }
  }

  @Test
  fun setChunkedEncodingAsRequestProperty() {
    server.enqueue(MockResponse())
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .header("Transfer-encoding", "chunked")
        .post(TransferKind.CHUNKED.newRequestBody("ABC"))
        .build()
    )
    assertThat(response.code).isEqualTo(200)
    val request = server.takeRequest()
    assertThat(request.body.readUtf8()).isEqualTo("ABC")
  }

  @Test
  fun connectionCloseInRequest() {
    server.enqueue(MockResponse()) // Server doesn't honor the connection: close header!
    server.enqueue(MockResponse())
    val a = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .header("Connection", "close")
        .build()
    )
    assertThat(a.code).isEqualTo(200)
    val b = getResponse(newRequest("/"))
    assertThat(b.code).isEqualTo(200)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertThat(server.takeRequest().sequenceNumber)
      .overridingErrorMessage(
        "When connection: close is used, each request should get its own connection"
      )
      .isEqualTo(0L)
  }

  @Test
  fun connectionCloseInResponse() {
    server.enqueue(
      MockResponse()
        .addHeader("Connection: close")
    )
    server.enqueue(MockResponse())
    val a = getResponse(newRequest("/"))
    assertThat(a.code).isEqualTo(200)
    val b = getResponse(newRequest("/"))
    assertThat(b.code).isEqualTo(200)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertThat(server.takeRequest().sequenceNumber)
      .overridingErrorMessage(
        "When connection: close is used, each request should get its own connection"
      )
      .isEqualTo(0L)
  }

  @Test
  fun connectionCloseWithRedirect() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: /foo")
        .addHeader("Connection: close")
    )
    server.enqueue(
      MockResponse()
        .setBody("This is the new location!")
    )
    val response = getResponse(newRequest("/"))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo(
      "This is the new location!"
    )
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertThat(server.takeRequest().sequenceNumber)
      .overridingErrorMessage(
        "When connection: close is used, each request should get its own connection"
      )
      .isEqualTo(0L)
  }

  /**
   * Retry redirects if the socket is closed.
   * https://code.google.com/p/android/issues/detail?id=41576
   */
  @Test
  fun sameConnectionRedirectAndReuse() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .setSocketPolicy(SocketPolicy.SHUTDOWN_INPUT_AT_END)
        .addHeader("Location: /foo")
    )
    server.enqueue(
      MockResponse()
        .setBody("This is the new page!")
    )
    assertContent("This is the new page!", getResponse(newRequest("/")))
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
    assertThat(server.takeRequest().sequenceNumber).isEqualTo(0)
  }

  @Test
  fun responseCodeDisagreesWithHeaders() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
        .setBody("This body is not allowed!")
    )
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: IOException) {
      assertThat(expected.message).isEqualTo("HTTP 204 had non-zero Content-Length: 25")
    }
  }

  @Test
  fun singleByteReadIsSigned() {
    server.enqueue(
      MockResponse()
        .setBody(
          Buffer()
            .writeByte(-2)
            .writeByte(-1)
        )
    )
    val response = getResponse(newRequest("/"))
    val inputStream = response.body.byteStream()
    assertThat(inputStream.read()).isEqualTo(254)
    assertThat(inputStream.read()).isEqualTo(255)
    assertThat(inputStream.read()).isEqualTo(-1)
  }

  @Test
  fun flushAfterStreamTransmittedWithChunkedEncoding() {
    testFlushAfterStreamTransmitted(TransferKind.CHUNKED)
  }

  @Test
  fun flushAfterStreamTransmittedWithFixedLength() {
    testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun flushAfterStreamTransmittedWithNoLengthHeaders() {
    testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM)
  }

  /**
   * We explicitly permit apps to close the upload stream even after it has been transmitted.  We
   * also permit flush so that buffered streams can do a no-op flush when they are closed.
   * http://b/3038470
   */
  private fun testFlushAfterStreamTransmitted(transferKind: TransferKind) {
    server.enqueue(
      MockResponse()
        .setBody("abc")
    )
    val sinkReference = AtomicReference()
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = object : ForwardingRequestBody(transferKind.newRequestBody("def")) {
          override fun writeTo(sink: BufferedSink) {
            sinkReference.set(sink)
            super.writeTo(sink)
          }
        },
      )
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE))
      .isEqualTo("abc")
    try {
      sinkReference.get().flush()
      fail()
    } catch (expected: IllegalStateException) {
    }
    try {
      sinkReference.get().write("ghi".toByteArray())
      sinkReference.get().emit()
      fail()
    } catch (expected: IllegalStateException) {
    }
  }

  @Test
  fun getHeadersThrows() {
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
    )
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun dnsFailureThrowsIOException() {
    client = client.newBuilder()
      .dns(FakeDns())
      .build()
    try {
      getResponse(Request("http://host.unlikelytld".toHttpUrl()))
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun malformedUrlThrowsUnknownHostException() {
    try {
      getResponse(Request("http://./foo.html".toHttpUrl()))
      fail()
    } catch (expected: UnknownHostException) {
    }
  }

  // The request should work once and then fail.
  @Test
  fun getKeepAlive() {
    server.enqueue(
      MockResponse()
        .setBody("ABC")
    )

    // The request should work once and then fail.
    val connection1 = getResponse(newRequest("/"))
    val source1 = connection1.body.source()
    source1.timeout().timeout(100, TimeUnit.MILLISECONDS)
    assertThat(readAscii(source1.inputStream(), Int.MAX_VALUE)).isEqualTo("ABC")
    server.shutdown()
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: ConnectException) {
    }
  }

  /** http://code.google.com/p/android/issues/detail?id=14562  */
  @Test
  fun readAfterLastByte() {
    server.enqueue(
      MockResponse()
        .setBody("ABC")
        .clearHeaders()
        .addHeader("Connection: close")
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    val response = getResponse(newRequest("/"))
    val `in` = response.body.byteStream()
    assertThat(readAscii(`in`, 3)).isEqualTo("ABC")
    assertThat(`in`.read()).isEqualTo(-1)
    // throws IOException in Gingerbread.
    assertThat(`in`.read()).isEqualTo(-1)
  }

  @Test
  fun getOutputStreamOnGetFails() {
    try {
      Request.Builder()
        .url(server.url("/"))
        .method("GET", "abc".toRequestBody(null))
        .build()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun clientSendsContentLength() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = "ABC".toRequestBody(null),
      )
    )
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo("A")
    val request = server.takeRequest()
    assertThat(request.getHeader("Content-Length")).isEqualTo("3")
    response.body.close()
  }

  @Test
  fun getContentLengthConnects() {
    server.enqueue(
      MockResponse()
        .setBody("ABC")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.body.contentLength()).isEqualTo(3L)
    response.body.close()
  }

  @Test
  fun getContentTypeConnects() {
    server.enqueue(
      MockResponse()
        .addHeader("Content-Type: text/plain")
        .setBody("ABC")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.body.contentType()).isEqualTo(
      "text/plain".toMediaType()
    )
    response.body.close()
  }

  @Test
  fun getContentEncodingConnects() {
    server.enqueue(
      MockResponse()
        .addHeader("Content-Encoding: identity")
        .setBody("ABC")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.header("Content-Encoding")).isEqualTo("identity")
    response.body.close()
  }

  @Test
  fun urlContainsQueryButNoPath() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val url = server.url("?query")
    val response = getResponse(Request(url))
    assertThat(readAscii(response.body.byteStream(), Int.MAX_VALUE)).isEqualTo("A")
    val request = server.takeRequest()
    assertThat(request.requestLine).isEqualTo("GET /?query HTTP/1.1")
  }

  @Test
  fun doOutputForMethodThatDoesntSupportOutput() {
    try {
      Request.Builder()
        .url(server.url("/"))
        .method("HEAD", "".toRequestBody(null))
        .build()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  // http://code.google.com/p/android/issues/detail?id=20442
  @Test
  fun inputStreamAvailableWithChunkedEncoding() {
    testInputStreamAvailable(TransferKind.CHUNKED)
  }

  @Test
  fun inputStreamAvailableWithContentLengthHeader() {
    testInputStreamAvailable(TransferKind.FIXED_LENGTH)
  }

  @Test
  fun inputStreamAvailableWithNoLengthHeaders() {
    testInputStreamAvailable(TransferKind.END_OF_STREAM)
  }

  private fun testInputStreamAvailable(transferKind: TransferKind) {
    val body = "ABCDEFGH"
    val mockResponse = MockResponse()
    transferKind.setBody(mockResponse, body, 4)
    server.enqueue(mockResponse)
    val response = getResponse(newRequest("/"))
    val inputStream = response.body.byteStream()
    for (i in 0 until body.length) {
      assertThat(inputStream.available()).isGreaterThanOrEqualTo(0)
      assertThat(inputStream.read()).isEqualTo(body[i].code)
    }
    assertThat(inputStream.available()).isEqualTo(0)
    assertThat(inputStream.read()).isEqualTo(-1)
  }

  @Test
  fun postFailsWithBufferedRequestForSmallRequest() {
    reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 1024)
  }

  @Test
  fun postFailsWithBufferedRequestForLargeRequest() {
    reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 16384)
  }

  @Test
  fun postFailsWithChunkedRequestForSmallRequest() {
    reusedConnectionFailsWithPost(TransferKind.CHUNKED, 1024)
  }

  @Test
  fun postFailsWithChunkedRequestForLargeRequest() {
    reusedConnectionFailsWithPost(TransferKind.CHUNKED, 16384)
  }

  @Test
  fun postFailsWithFixedLengthRequestForSmallRequest() {
    reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 1024)
  }

  @Test
  fun postFailsWithFixedLengthRequestForLargeRequest() {
    reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 16384)
  }

  private fun reusedConnectionFailsWithPost(transferKind: TransferKind, requestSize: Int) {
    server.enqueue(
      MockResponse()
        .setBody("A")
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    server.enqueue(
      MockResponse()
        .setBody("B")
    )
    server.enqueue(
      MockResponse()
        .setBody("C")
    )
    assertContent("A", getResponse(newRequest("/a")))

    // Give the server time to disconnect.
    Thread.sleep(500)

    // If the request body is larger than OkHttp's replay buffer, the failure may still occur.
    val requestBodyChars = CharArray(requestSize)
    Arrays.fill(requestBodyChars, 'x')
    val requestBody = String(requestBodyChars)
    for (j in 0..1) {
      try {
        val response = getResponse(
          Request(
            url = server.url("/b"),
            body = transferKind.newRequestBody(requestBody),
          )
        )
        assertContent("B", response)
        break
      } catch (socketException: IOException) {
        // If there's a socket exception, this must have a streamed request body.
        assertThat(j).isEqualTo(0)
        assertThat(transferKind).isIn(TransferKind.CHUNKED, TransferKind.FIXED_LENGTH)
      }
    }
    val requestA = server.takeRequest()
    assertThat(requestA.path).isEqualTo("/a")
    val requestB = server.takeRequest()
    assertThat(requestB.path).isEqualTo("/b")
    assertThat(requestB.body.readUtf8()).isEqualTo(requestBody)
  }

  @Test
  fun postBodyRetransmittedOnFailureRecovery() {
    server.enqueue(
      MockResponse()
        .setBody("abc")
    )
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST)
    )
    server.enqueue(
      MockResponse()
        .setBody("def")
    )

    // Seed the connection pool so we have something that can fail.
    assertContent("abc", getResponse(newRequest("/")))
    val post = getResponse(
      Request(
        url = server.url("/"),
        body = "body!".toRequestBody(null),
      )
    )
    assertContent("def", post)
    val get = server.takeRequest()
    assertThat(get.sequenceNumber).isEqualTo(0)
    val post1 = server.takeRequest()
    assertThat(post1.body.readUtf8()).isEqualTo("body!")
    assertThat(post1.sequenceNumber).isEqualTo(1)
    val post2 = server.takeRequest()
    assertThat(post2.body.readUtf8()).isEqualTo("body!")
    assertThat(post2.sequenceNumber).isEqualTo(0)
  }

  @Test
  fun fullyBufferedPostIsTooShort() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val requestBody: RequestBody = object : RequestBody() {
      override fun contentType(): MediaType? = null

      override fun contentLength(): Long = 4L

      override fun writeTo(sink: BufferedSink) {
        sink.writeUtf8("abc")
      }
    }
    try {
      getResponse(
        Request(
          url = server.url("/b"),
          body = requestBody,
        )
      )
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  fun fullyBufferedPostIsTooLong() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val requestBody: RequestBody = object : RequestBody() {
      override fun contentType(): MediaType? = null

      override fun contentLength(): Long = 3L

      override fun writeTo(sink: BufferedSink) {
        sink.writeUtf8("abcd")
      }
    }
    try {
      getResponse(
        Request(
          url = server.url("/b"),
          body = requestBody,
        )
      )
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test
  @Disabled
  fun testPooledConnectionsDetectHttp10() {
    // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1)
    fail("TODO")
  }

  @Test
  @Disabled
  fun postBodiesRetransmittedOnAuthProblems() {
    fail("TODO")
  }

  @Test
  @Disabled
  fun cookiesAndTrailers() {
    // Do cookie headers get processed too many times?
    fail("TODO")
  }

  @Test
  fun emptyRequestHeaderValueIsAllowed() {
    server.enqueue(
      MockResponse()
        .setBody("body")
    )
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .header("B", "")
        .build()
    )
    assertContent("body", response)
    assertThat(response.request.header("B")).isEqualTo("")
  }

  @Test
  fun emptyResponseHeaderValueIsAllowed() {
    server.enqueue(
      MockResponse()
        .addHeader("A:")
        .setBody("body")
    )
    val response = getResponse(newRequest("/"))
    assertContent("body", response)
    assertThat(response.header("A")).isEqualTo("")
  }

  @Test
  fun emptyRequestHeaderNameIsStrict() {
    try {
      Request.Builder()
        .url(server.url("/"))
        .header("", "A")
        .build()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun emptyResponseHeaderNameIsLenient() {
    val headers = Headers.Builder()
    addHeaderLenient(headers, ":A")
    server.enqueue(
      MockResponse()
        .setHeaders(headers.build())
        .setBody("body")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(200)
    assertThat(response.header("")).isEqualTo("A")
    response.body.close()
  }

  @Test
  fun requestHeaderValidationIsStrict() {
    try {
      Request.Builder()
        .addHeader("a\tb", "Value")
      fail()
    } catch (expected: IllegalArgumentException) {
    }
    try {
      Request.Builder()
        .addHeader("Name", "c\u007fd")
      fail()
    } catch (expected: IllegalArgumentException) {
    }
    try {
      Request.Builder()
        .addHeader("", "Value")
      fail()
    } catch (expected: IllegalArgumentException) {
    }
    try {
      Request.Builder()
        .addHeader("\ud83c\udf69", "Value")
      fail()
    } catch (expected: IllegalArgumentException) {
    }
    try {
      Request.Builder()
        .addHeader("Name", "\u2615\ufe0f")
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun responseHeaderParsingIsLenient() {
    val headersBuilder = Headers.Builder()
    headersBuilder.add("Content-Length", "0")
    addHeaderLenient(headersBuilder, "a\tb: c\u007fd")
    addHeaderLenient(headersBuilder, ": ef")
    addHeaderLenient(headersBuilder, "\ud83c\udf69: \u2615\ufe0f")
    val headers = headersBuilder.build()
    server.enqueue(
      MockResponse()
        .setHeaders(headers)
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(200)
    assertThat(response.header("a\tb")).isEqualTo("c\u007fd")
    assertThat(response.header("\ud83c\udf69")).isEqualTo("\u2615\ufe0f")
    assertThat(response.header("")).isEqualTo("ef")
  }

  @Test
  @Disabled
  fun deflateCompression() {
    fail("TODO")
  }

  @Test
  @Disabled
  fun postBodiesRetransmittedOnIpAddressProblems() {
    fail("TODO")
  }

  @Test
  @Disabled
  fun pooledConnectionProblemsNotReportedToProxySelector() {
    fail("TODO")
  }

  @Test
  fun customBasicAuthenticator() {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
        .setBody("Please authenticate.")
    )
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val credential = basic("jesse", "peanutbutter")
    val authenticator = RecordingOkAuthenticator(credential, null)
    client = client.newBuilder()
      .authenticator(authenticator)
      .build()
    assertContent("A", getResponse(newRequest("/private")))
    assertThat(server.takeRequest().getHeader("Authorization")).isNull()
    assertThat(server.takeRequest().getHeader("Authorization")).isEqualTo(credential)
    assertThat(authenticator.onlyRoute().proxy).isEqualTo(Proxy.NO_PROXY)
    val response = authenticator.onlyResponse()
    assertThat(response.request.url.toUrl().path).isEqualTo("/private")
    assertThat(response.challenges()).isEqualTo(listOf(Challenge("Basic", "protected area")))
  }

  @Test
  fun customTokenAuthenticator() {
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Bearer realm=\"oauthed\"")
        .setBody("Please authenticate.")
    )
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    val authenticator = RecordingOkAuthenticator("oauthed abc123", "Bearer")
    client = client.newBuilder()
      .authenticator(authenticator)
      .build()
    assertContent("A", getResponse(newRequest("/private")))
    assertThat(server.takeRequest().getHeader("Authorization")).isNull()
    assertThat(server.takeRequest().getHeader("Authorization")).isEqualTo(
      "oauthed abc123"
    )
    val response = authenticator.onlyResponse()
    assertThat(response.request.url.toUrl().path).isEqualTo("/private")
    assertThat(response.challenges()).isEqualTo(listOf(Challenge("Bearer", "oauthed")))
  }

  @Test
  fun authenticateCallsTrackedAsRedirects() {
    server.enqueue(
      MockResponse()
        .setResponseCode(302)
        .addHeader("Location: /b")
    )
    server.enqueue(
      MockResponse()
        .setResponseCode(401)
        .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
    )
    server.enqueue(
      MockResponse()
        .setBody("c")
    )
    val authenticator = RecordingOkAuthenticator(
      basic("jesse", "peanutbutter"), "Basic"
    )
    client = client.newBuilder()
      .authenticator(authenticator)
      .build()
    assertContent("c", getResponse(newRequest("/a")))
    val challengeResponse = authenticator.responses[0]
    assertThat(challengeResponse.request.url.toUrl().path).isEqualTo("/b")
    val redirectedBy = challengeResponse.priorResponse
    assertThat(redirectedBy!!.request.url.toUrl().path).isEqualTo("/a")
  }

  @Test
  fun attemptAuthorization20Times() {
    for (i in 0..19) {
      server.enqueue(
        MockResponse()
          .setResponseCode(401)
      )
    }
    server.enqueue(
      MockResponse()
        .setBody("Success!")
    )
    val credential = basic("jesse", "peanutbutter")
    client = client.newBuilder()
      .authenticator(RecordingOkAuthenticator(credential, null))
      .build()
    val response = getResponse(newRequest("/0"))
    assertContent("Success!", response)
  }

  @Test
  fun doesNotAttemptAuthorization21Times() {
    for (i in 0..20) {
      server.enqueue(
        MockResponse()
          .setResponseCode(401)
      )
    }
    val credential = basic("jesse", "peanutbutter")
    client = client.newBuilder()
      .authenticator(RecordingOkAuthenticator(credential, null))
      .build()
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: ProtocolException) {
      assertThat(expected.message).isEqualTo("Too many follow-up requests: 21")
    }
  }

  @Test
  fun setsNegotiatedProtocolHeader_HTTP_2() {
    platform.assumeHttp2Support()
    setsNegotiatedProtocolHeader(Protocol.HTTP_2)
  }

  private fun setsNegotiatedProtocolHeader(protocol: Protocol) {
    enableProtocol(protocol)
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    client = client.newBuilder()
      .protocols(Arrays.asList(protocol, Protocol.HTTP_1_1))
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(response.protocol).isEqualTo(protocol)
    assertContent("A", response)
  }

  @Test
  fun http10SelectedProtocol() {
    server.enqueue(
      MockResponse()
        .setStatus("HTTP/1.0 200 OK")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.protocol).isEqualTo(Protocol.HTTP_1_0)
  }

  @Test
  fun http11SelectedProtocol() {
    server.enqueue(
      MockResponse()
        .setStatus("HTTP/1.1 200 OK")
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.protocol).isEqualTo(Protocol.HTTP_1_1)
  }

  /** For example, empty Protobuf RPC messages end up as a zero-length POST.  */
  @Test
  fun zeroLengthPost() {
    zeroLengthPayload("POST")
  }

  @Test
  fun zeroLengthPost_HTTP_2() {
    enableProtocol(Protocol.HTTP_2)
    zeroLengthPost()
  }

  /** For example, creating an Amazon S3 bucket ends up as a zero-length POST.  */
  @Test
  fun zeroLengthPut() {
    zeroLengthPayload("PUT")
  }

  @Test
  fun zeroLengthPut_HTTP_2() {
    enableProtocol(Protocol.HTTP_2)
    zeroLengthPut()
  }

  private fun zeroLengthPayload(method: String) {
    server.enqueue(MockResponse())
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .method(method, "".toRequestBody(null))
        .build()
    )
    assertContent("", response)
    val zeroLengthPayload = server.takeRequest()
    assertThat(zeroLengthPayload.method).isEqualTo(method)
    assertThat(zeroLengthPayload.getHeader("content-length")).isEqualTo("0")
    assertThat(zeroLengthPayload.bodySize).isEqualTo(0L)
  }

  @Test
  fun setProtocols() {
    server.enqueue(
      MockResponse()
        .setBody("A")
    )
    client = client.newBuilder()
      .protocols(Arrays.asList(Protocol.HTTP_1_1))
      .build()
    assertContent("A", getResponse(newRequest("/")))
  }

  @Test
  fun setProtocolsWithoutHttp11() {
    try {
      OkHttpClient.Builder()
        .protocols(Arrays.asList(Protocol.HTTP_2))
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun setProtocolsWithNull() {
    try {
      OkHttpClient.Builder()
        .protocols(Arrays.asList(Protocol.HTTP_1_1, null))
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun veryLargeFixedLengthRequest() {
    server.bodyLimit = 0
    server.enqueue(MockResponse())
    val contentLength = Int.MAX_VALUE + 1L
    val response = getResponse(
      Request(
        url = server.url("/"),
        body = object : RequestBody() {
          override fun contentType(): MediaType? = null

          override fun contentLength(): Long = contentLength

          override fun writeTo(sink: BufferedSink) {
            val buffer = ByteArray(1024 * 1024)
            var bytesWritten: Long = 0
            while (bytesWritten < contentLength) {
              val byteCount = Math.min(buffer.size.toLong(), contentLength - bytesWritten).toInt()
              bytesWritten += byteCount.toLong()
              sink.write(buffer, 0, byteCount)
            }
          }
        },
      )
    )
    assertContent("", response)
    val request = server.takeRequest()
    assertThat(request.getHeader("Content-Length")).isEqualTo(
      java.lang.Long.toString(contentLength)
    )
  }

  @Test
  fun testNoSslFallback() {
    server.useHttps(handshakeCertificates.sslSocketFactory(), false /* tunnelProxy */)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)
    )
    server.enqueue(
      MockResponse()
        .setBody("Response that would have needed fallbacks")
    )
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager
      )
      .build()
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: SSLProtocolException) {
      // RI response to the FAIL_HANDSHAKE
    } catch (expected: SSLHandshakeException) {
      // Android's response to the FAIL_HANDSHAKE
    } catch (expected: SSLException) {
      // JDK 1.9 response to the FAIL_HANDSHAKE
      // javax.net.ssl.SSLException: Unexpected handshake message: client_hello
    } catch (expected: SocketException) {
      // Conscrypt's response to the FAIL_HANDSHAKE
    }
  }

  /**
   * We had a bug where we attempted to gunzip responses that didn't have a body. This only came up
   * with 304s since that response code can include headers (like "Content-Encoding") without any
   * content to go along with it. https://github.com/square/okhttp/issues/358
   */
  @Test
  fun noTransparentGzipFor304NotModified() {
    server.enqueue(
      MockResponse()
        .clearHeaders()
        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)
        .addHeader("Content-Encoding: gzip")
    )
    server.enqueue(
      MockResponse()
        .setBody("b")
    )
    val response1 = getResponse(newRequest("/"))
    assertThat(response1.code).isEqualTo(HttpURLConnection.HTTP_NOT_MODIFIED.toLong())
    assertContent("", response1)
    val response2 = getResponse(newRequest("/"))
    assertThat(response2.code).isEqualTo(HttpURLConnection.HTTP_OK)
    assertContent("b", response2)
    val requestA = server.takeRequest()
    assertThat(requestA.sequenceNumber).isEqualTo(0)
    val requestB = server.takeRequest()
    assertThat(requestB.sequenceNumber).isEqualTo(1)
  }

  /**
   * We had a bug where we weren't closing Gzip streams on redirects.
   * https://github.com/square/okhttp/issues/441
   */
  @Test
  fun gzipWithRedirectAndConnectionReuse() {
    server.enqueue(
      MockResponse()
        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
        .addHeader("Location: /foo")
        .addHeader("Content-Encoding: gzip")
        .setBody(gzip("Moved! Moved! Moved!"))
    )
    server.enqueue(
      MockResponse()
        .setBody("This is the new page!")
    )
    val response = getResponse(newRequest("/"))
    assertContent("This is the new page!", response)
    val requestA = server.takeRequest()
    assertThat(requestA.sequenceNumber).isEqualTo(0)
    val requestB = server.takeRequest()
    assertThat(requestB.sequenceNumber).isEqualTo(1)
  }

  /**
   * The RFC is unclear in this regard as it only specifies that this should invalidate the cache
   * entry (if any).
   */
  @Test
  fun bodyPermittedOnDelete() {
    server.enqueue(MockResponse())
    val response = getResponse(
      Request.Builder()
        .url(server.url("/"))
        .delete("BODY".toRequestBody(null))
        .build()
    )
    assertThat(response.code).isEqualTo(200)
    val request = server.takeRequest()
    assertThat(request.method).isEqualTo("DELETE")
    assertThat(request.body.readUtf8()).isEqualTo("BODY")
  }

  @Test
  fun userAgentDefaultsToOkHttpVersion() {
    server.enqueue(
      MockResponse()
        .setBody("abc")
    )
    assertContent("abc", getResponse(newRequest("/")))
    val request = server.takeRequest()
    assertThat(request.getHeader("User-Agent")).isEqualTo(userAgent)
  }

  @Test
  fun urlWithSpaceInHost() {
    try {
      "http://and roid.com/".toHttpUrl()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun urlWithSpaceInHostViaHttpProxy() {
    try {
      "http://and roid.com/".toHttpUrl()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun urlHostWithNul() {
    try {
      "http://host\u0000/".toHttpUrl()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Test
  fun urlRedirectToHostWithNul() {
    val redirectUrl = "http://host\u0000/"
    server.enqueue(
      MockResponse()
        .setResponseCode(302)
        .addHeaderLenient("Location", redirectUrl)
    )
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(302)
    assertThat(response.header("Location")).isEqualTo(redirectUrl)
  }

  @Test
  fun urlWithBadAsciiHost() {
    try {
      "http://host\u0001/".toHttpUrl()
      fail()
    } catch (expected: IllegalArgumentException) {
    }
  }

  @Suppress("DEPRECATION_ERROR")
  @Test
  fun setSslSocketFactoryFailsOnJdk9() {
    platform.assumeJdk9()
    try {
      client.newBuilder()
        .sslSocketFactory(handshakeCertificates.sslSocketFactory())
      fail()
    } catch (expected: UnsupportedOperationException) {
    }
  }

  /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller.  */
  @Test
  fun unexpectedExceptionSync() {
    client = client.newBuilder()
      .dns { hostname: String? -> throw RuntimeException("boom!") }
      .build()
    server.enqueue(MockResponse())
    try {
      getResponse(newRequest("/"))
      fail()
    } catch (expected: RuntimeException) {
      assertThat(expected.message).isEqualTo("boom!")
    }
  }

  @Test
  fun streamedBodyIsRetriedOnHttp2Shutdown() {
    platform.assumeHttp2Support()
    enableProtocol(Protocol.HTTP_2)
    server.enqueue(
      MockResponse()
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
        .setBody("abc")
    )
    server.enqueue(
      MockResponse()
        .setBody("def")
    )

    // Send a separate request which will trigger a GOAWAY frame on the healthy connection.
    val response = getResponse(newRequest("/"))
    assertContent("abc", response)

    // Ensure the GOAWAY frame has time to be read and processed.
    Thread.sleep(500)
    assertContent(
      "def",
      getResponse(
        Request(
          url = server.url("/"),
          body = "123".toRequestBody(null),
        )
      )
    )
    val request1 = server.takeRequest()
    assertThat(request1.sequenceNumber).isEqualTo(0)
    val request2 = server.takeRequest()
    assertThat(request2.body.readUtf8()).isEqualTo("123")
    assertThat(request2.sequenceNumber).isEqualTo(0)
  }

  @Test
  fun authenticateNoConnection() {
    server.enqueue(
      MockResponse()
        .addHeader("Connection: close")
        .setResponseCode(401)
        .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
    )
    java.net.Authenticator.setDefault(RecordingAuthenticator(null))
    client = client.newBuilder()
      .authenticator(JavaNetAuthenticator())
      .build()
    val response = getResponse(newRequest("/"))
    assertThat(response.code).isEqualTo(401)
  }

  private fun newRequest(s: String): Request = Request(server.url(s))

  private fun getResponse(request: Request): Response {
    return client.newCall(request).execute()
  }

  /** Returns a gzipped copy of `bytes`.  */
  fun gzip(data: String?): Buffer {
    val result = Buffer()
    val gzipSink = GzipSink(result).buffer()
    gzipSink.writeUtf8(data!!)
    gzipSink.close()
    return result
  }

  private fun assertContent(
    expected: String,
    response: Response,
    limit: Int = Int.MAX_VALUE
  ) {
    assertThat(readAscii(response.body.byteStream(), limit)).isEqualTo(expected)
  }

  private fun newSet(vararg elements: String): Set {
    return setOf(*elements)
  }

  internal enum class TransferKind {
    CHUNKED {
      override fun setBody(response: MockResponse, content: Buffer?, chunkSize: Int) {
        response.setChunkedBody(content!!, chunkSize)
      }

      override fun newRequestBody(body: String): RequestBody {
        return object : RequestBody() {
          override fun contentLength(): Long = -1L

          override fun contentType(): MediaType? = null

          override fun writeTo(sink: BufferedSink) {
            sink.writeUtf8(body)
          }
        }
      }
    },

    FIXED_LENGTH {
      override fun setBody(response: MockResponse, content: Buffer?, chunkSize: Int) {
        response.setBody(content!!)
      }

      override fun newRequestBody(body: String): RequestBody {
        return object : RequestBody() {
          override fun contentLength(): Long = body.utf8Size()

          override fun contentType(): MediaType? = null

          override fun writeTo(sink: BufferedSink) {
            sink.writeUtf8(body)
          }
        }
      }
    },

    END_OF_STREAM {
      override fun setBody(response: MockResponse, content: Buffer?, chunkSize: Int) {
        response.setBody(content!!)
        response.setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
        response.removeHeader("Content-Length")
      }

      override fun newRequestBody(body: String): RequestBody {
        throw TestAbortedException("END_OF_STREAM not implemented for requests")
      }
    };

    abstract fun setBody(
      response: MockResponse,
      content: Buffer?,
      chunkSize: Int
    )

    abstract fun newRequestBody(body: String): RequestBody
    fun setBody(
      response: MockResponse,
      content: String?,
      chunkSize: Int
    ) {
      setBody(response, Buffer().writeUtf8(content!!), chunkSize)
    }
  }

  internal enum class ProxyConfig {
    NO_PROXY {
      override fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory {
        return client.newBuilder()
          .proxy(Proxy.NO_PROXY)
          .build()
      }
    },
    CREATE_ARG {
      override fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory {
        return client.newBuilder()
          .proxy(server.toProxyAddress())
          .build()
      }
    },
    PROXY_SYSTEM_PROPERTY {
      override fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory {
        System.setProperty("proxyHost", server.hostName)
        System.setProperty("proxyPort", server.port.toString())
        return client
      }
    },
    HTTP_PROXY_SYSTEM_PROPERTY {
      override fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory {
        System.setProperty("http.proxyHost", server.hostName)
        System.setProperty("http.proxyPort", server.port.toString())
        return client
      }
    },
    HTTPS_PROXY_SYSTEM_PROPERTY {
      override fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory {
        System.setProperty("https.proxyHost", server.hostName)
        System.setProperty("https.proxyPort", server.port.toString())
        return client
      }
    };

    abstract fun connect(server: MockWebServer, client: OkHttpClient): Call.Factory

    fun connect(server: MockWebServer, client: OkHttpClient, url: HttpUrl): Call {
      return connect(server, client)
        .newCall(Request(url))
    }
  }

  private class RecordingTrustManager(private val delegate: X509TrustManager) : X509TrustManager {
    val calls: MutableList = ArrayList()

    override fun getAcceptedIssuers(): Array = delegate.acceptedIssuers

    override fun checkClientTrusted(chain: Array, authType: String) {
      calls.add("checkClientTrusted " + certificatesToString(chain))
    }

    override fun checkServerTrusted(chain: Array, authType: String) {
      calls.add("checkServerTrusted " + certificatesToString(chain))
    }

    private fun certificatesToString(certificates: Array): String {
      val result: MutableList = ArrayList()
      for (certificate in certificates) {
        result.add(certificate.subjectDN.toString() + " " + certificate.serialNumber)
      }
      return result.toString()
    }
  }

  /**
   * Tests that use this will fail unless boot classpath is set. Ex. `-Xbootclasspath/p:/tmp/alpn-boot-8.0.0.v20140317`
   */
  private fun enableProtocol(protocol: Protocol) {
    client = client.newBuilder()
      .sslSocketFactory(
        handshakeCertificates.sslSocketFactory(),
        handshakeCertificates.trustManager
      )
      .hostnameVerifier(RecordingHostnameVerifier())
      .protocols(Arrays.asList(protocol, Protocol.HTTP_1_1))
      .build()
    server.useHttps(handshakeCertificates.sslSocketFactory(), false)
    server.protocolNegotiationEnabled = true
    server.protocols = client.protocols
  }

  /**
   * Used during tests that involve TLS connection fallback attempts. OkHttp includes the
   * TLS_FALLBACK_SCSV cipher on fallback connections. See [FallbackTestClientSocketFactory]
   * for details.
   */
  private fun suppressTlsFallbackClientSocketFactory() =
    FallbackTestClientSocketFactory(handshakeCertificates.sslSocketFactory())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy