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

okhttp3.internal.ws.WebSocketExtensions.kt Maven / Gradle / Ivy

There is a newer version: 4.0.0
Show newest version
/*
 * Copyright (C) 2020 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.internal.ws

import java.io.IOException
import okhttp3.Headers
import okhttp3.internal.delimiterOffset
import okhttp3.internal.trimSubstring

/**
 * Models the contents of a `Sec-WebSocket-Extensions` response header. OkHttp honors one extension
 * `permessage-deflate` and four parameters, `client_max_window_bits`, `client_no_context_takeover`,
 * `server_max_window_bits`, and `server_no_context_takeover`.
 *
 * Typically this will look like one of the following:
 *
 * ```
 * Sec-WebSocket-Extensions: permessage-deflate
 * Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits="15"
 * Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15
 * Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover
 * Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits="15"
 * Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15
 * Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover
 * Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover;
 *     client_no_context_takeover
 * Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits="15";
 *     client_max_window_bits="15"; server_no_context_takeover; client_no_context_takeover
 * ```
 *
 * If any other extension or parameter is specified, then [unknownValues] will be true. Such
 * responses should be refused as their web socket extensions will not be understood.
 *
 * Note that [java.util.zip.Deflater] is hardcoded to use 15 bits (32 KiB) for
 * `client_max_window_bits` and [java.util.zip.Inflater] is hardcoded to use 15 bits (32 KiB) for
 * `server_max_window_bits`. This harms our ability to support these parameters:
 *
 *  * If `client_max_window_bits` is less than 15, OkHttp must close the web socket with code 1010.
 *    Otherwise it would compress values in a way that servers could not decompress.
 *  * If `server_max_window_bits` is less than 15, OkHttp will waste memory on an oversized buffer.
 *
 * See [RFC 7692, 7.1][rfc_7692] for details on negotiation process.
 *
 * [rfc_7692]: https://tools.ietf.org/html/rfc7692#section-7.1
 */
data class WebSocketExtensions(
  /** True if the agreed upon extensions includes the permessage-deflate extension. */
  @JvmField val perMessageDeflate: Boolean = false,

  /** Should be a value in [8..15]. Only 15 is acceptable by OkHttp as Java APIs are limited. */
  @JvmField val clientMaxWindowBits: Int? = null,

  /** True if the agreed upon extension parameters includes "client_no_context_takeover". */
  @JvmField val clientNoContextTakeover: Boolean = false,

  /** Should be a value in [8..15]. Any value in that range is acceptable by OkHttp. */
  @JvmField val serverMaxWindowBits: Int? = null,

  /** True if the agreed upon extension parameters includes "server_no_context_takeover". */
  @JvmField val serverNoContextTakeover: Boolean = false,

  /**
   * True if the agreed upon extensions or parameters contained values unrecognized by OkHttp.
   * Typically this indicates that the client will need to close the web socket with code 1010.
   */
  @JvmField val unknownValues: Boolean = false
) {

  fun noContextTakeover(clientOriginated: Boolean): Boolean {
    return if (clientOriginated) {
      clientNoContextTakeover // Client is deflating.
    } else {
      serverNoContextTakeover // Server is deflating.
    }
  }

  companion object {
    private const val HEADER_WEB_SOCKET_EXTENSION = "Sec-WebSocket-Extensions"

    @Throws(IOException::class)
    fun parse(responseHeaders: Headers): WebSocketExtensions {
      // Note that this code does case-insensitive comparisons, even though the spec doesn't specify
      // whether extension tokens and parameters are case-insensitive or not.

      var compressionEnabled = false
      var clientMaxWindowBits: Int? = null
      var clientNoContextTakeover = false
      var serverMaxWindowBits: Int? = null
      var serverNoContextTakeover = false
      var unexpectedValues = false

      // Parse each header.
      for (i in 0 until responseHeaders.size) {
        if (!responseHeaders.name(i).equals(HEADER_WEB_SOCKET_EXTENSION, ignoreCase = true)) {
          continue // Not a header we're interested in.
        }
        val header = responseHeaders.value(i)

        // Parse each extension.
        var pos = 0
        while (pos < header.length) {
          val extensionEnd = header.delimiterOffset(',', pos)
          val extensionTokenEnd = header.delimiterOffset(';', pos, extensionEnd)
          val extensionToken = header.trimSubstring(pos, extensionTokenEnd)
          pos = extensionTokenEnd + 1

          when {
            extensionToken.equals("permessage-deflate", ignoreCase = true) -> {
              if (compressionEnabled) unexpectedValues = true // Repeated extension!
              compressionEnabled = true

              // Parse each permessage-deflate parameter.
              while (pos < extensionEnd) {
                val parameterEnd = header.delimiterOffset(';', pos, extensionEnd)
                val equals = header.delimiterOffset('=', pos, parameterEnd)
                val name = header.trimSubstring(pos, equals)
                val value = if (equals < parameterEnd) {
                  header.trimSubstring(equals + 1, parameterEnd).removeSurrounding("\"")
                } else {
                  null
                }
                pos = parameterEnd + 1
                when {
                  name.equals("client_max_window_bits", ignoreCase = true) -> {
                    if (clientMaxWindowBits != null) unexpectedValues = true // Repeated parameter!
                    clientMaxWindowBits = value?.toIntOrNull()
                    if (clientMaxWindowBits == null) unexpectedValues = true // Not an int!
                  }
                  name.equals("client_no_context_takeover", ignoreCase = true) -> {
                    if (clientNoContextTakeover) unexpectedValues = true // Repeated parameter!
                    if (value != null) unexpectedValues = true // Unexpected value!
                    clientNoContextTakeover = true
                  }
                  name.equals("server_max_window_bits", ignoreCase = true) -> {
                    if (serverMaxWindowBits != null) unexpectedValues = true // Repeated parameter!
                    serverMaxWindowBits = value?.toIntOrNull()
                    if (serverMaxWindowBits == null) unexpectedValues = true // Not an int!
                  }
                  name.equals("server_no_context_takeover", ignoreCase = true) -> {
                    if (serverNoContextTakeover) unexpectedValues = true // Repeated parameter!
                    if (value != null) unexpectedValues = true // Unexpected value!
                    serverNoContextTakeover = true
                  }
                  else -> {
                    unexpectedValues = true // Unexpected parameter.
                  }
                }
              }
            }

            else -> {
              unexpectedValues = true // Unexpected extension.
            }
          }
        }
      }

      return WebSocketExtensions(
          perMessageDeflate = compressionEnabled,
          clientMaxWindowBits = clientMaxWindowBits,
          clientNoContextTakeover = clientNoContextTakeover,
          serverMaxWindowBits = serverMaxWindowBits,
          serverNoContextTakeover = serverNoContextTakeover,
          unknownValues = unexpectedValues
      )
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy