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.StatusLine
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.Builder()
      .url(server.url("/def"))
      .post(transferKind.newRequestBody("body"))
      .build()
    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.Builder()
        .url(server.url("/"))
        .post(requestBody)
        .build()
    )
    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(newRequest("https://android.com/foo".toHttpUrl())))
    assertContent("response 2", getResponse(newRequest("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(newRequest(url)))
    assertThat(getResponse(newRequest(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.Builder()
        .url(server.url("/"))
        .post(TransferKind.CHUNKED.newRequestBody("ABCDEFGHIJKLMNOPQ"))
        .build()
    )
    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.Builder()
      .url(server.url("/"))
      .post(streamingMode.newRequestBody("ABCD"))
      .build()
    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.Builder()
        .url(server.url("/"))
        .post(body.toRequestBody())
        .build()
    )
    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(newRequest("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.Builder()
        .url(server.url("/"))
        .post(streamingMode.newRequestBody("ABCD"))
        .build()
    )
    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.Builder()
        .url(server.url("/"))
        .post("ABCD".toRequestBody(null))
        .build()
    )
    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(newRequest(server.url("/"))))
    assertContent("This is the 2nd server, again!", getResponse(newRequest(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.Builder()
        .url(server.url("/page1"))
        .post(transferKind.newRequestBody("ABCD"))
        .build()
    )
    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 != StatusLine.HTTP_TEMP_REDIRECT && code != StatusLine.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(StatusLine.HTTP_TEMP_REDIRECT)
      .setBody("This page has moved!")
      .addHeader("Location: /page2")
    server.enqueue(response1)
    val requestBuilder = Request.Builder()
      .url(server.url("/page1"))
    requestBuilder.post("ABCD".toRequestBody(null))
    val response = getResponse(requestBuilder.build())
    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(StatusLine.HTTP_PERM_REDIRECT)
      .setBody("This page has moved!")
      .addHeader("Location: /page2")
    server.enqueue(response1)
    val requestBuilder = Request.Builder()
      .url(server.url("/page1"))
    requestBuilder.post("ABCD".toRequestBody(null))
    val response = getResponse(requestBuilder.build())
    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) StatusLine.HTTP_TEMP_REDIRECT else StatusLine.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.Builder()
        .url(server.url("/"))
        .post("Hello".toRequestBody(null))
        .build()
    )
    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.Builder()
        .url(server.url("/"))
        .post(TransferKind.CHUNKED.newRequestBody("Hello"))
        .build()
    )
    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.Builder()
      .url(server.url("/"))
      .post(object : RequestBody() {
        override fun contentType(): MediaType? {
          return null
        }

        override fun writeTo(sink: BufferedSink) {
          val data = ByteArray(2 * 1024 * 1024) // 2 MiB.
          sink.write(data)
        }
      })
      .build()
    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.Builder()
        .url(server.url("/"))
        .post(object : ForwardingRequestBody(transferKind.newRequestBody("def")) {
          override fun writeTo(sink: BufferedSink) {
            sinkReference.set(sink)
            super.writeTo(sink)
          }
        })
        .build()
    )
    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(newRequest("http://host.unlikelytld".toHttpUrl()))
      fail()
    } catch (expected: IOException) {
    }
  }

  @Test fun malformedUrlThrowsUnknownHostException() {
    try {
      getResponse(newRequest("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.Builder()
        .url(server.url("/"))
        .post("ABC".toRequestBody(null))
        .build()
    )
    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(newRequest(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.Builder()
            .url(server.url("/b"))
            .post(transferKind.newRequestBody(requestBody))
            .build()
        )
        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.Builder()
        .url(server.url("/"))
        .post("body!".toRequestBody(null))
        .build()
    )
    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.Builder()
          .url(server.url("/b"))
          .post(requestBody)
          .build()
      )
      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.Builder()
          .url(server.url("/b"))
          .post(requestBody)
          .build()
      )
      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.Builder()
        .url(server.url("/"))
        .post(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)
            }
          }
        })
        .build()
    )
    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.Builder()
          .url(server.url("/"))
          .post("123".toRequestBody(null))
          .build()
      )
    )
    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 = newRequest(server.url(s))

  private fun newRequest(url: HttpUrl): Request {
    return Request.Builder()
      .url(url)
      .build()
  }

  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 {
      val request = Request.Builder()
        .url(url)
        .build()
      return connect(server, client).newCall(request)
    }
  }

  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