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

org.http4s.hpack.Decoder.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022 http4s.org
 *
 * 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.
 */

/*
 * Copyright 2014 Twitter, 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 org.http4s.hpack;

import java.io.IOException;
import java.io.InputStream;

import org.http4s.hpack.HpackUtil.IndexType;

import org.http4s.hpack.HeaderField.HEADER_ENTRY_OVERHEAD;

private[http4s] final class Decoder(dynamicTable: DynamicTable) {

  private[this] val DECOMPRESSION_EXCEPTION =
    new IOException("decompression failure");
  private[this] val ILLEGAL_INDEX_VALUE =
    new IOException("illegal index value");
  private[this] val INVALID_MAX_DYNAMIC_TABLE_SIZE =
    new IOException("invalid max dynamic table size");
  private[this] val MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED =
    new IOException("max dynamic table size change required");

  private[this] val EMPTY = Array.emptyByteArray;

  private[this] var maxHeaderSize: Int = _
  private[this] var maxDynamicTableSize: Int = _
  private[this] var encoderMaxDynamicTableSize: Int = _
  private[this] var maxDynamicTableSizeChangeRequired: Boolean = _

  private[this] var headerSize: Long = _
  private[this] var state: State = _
  private[this] var indexType: IndexType = _
  private[this] var index: Int = _
  private[this] var huffmanEncoded: Boolean = _
  private[this] var skipLength: Int = _
  private[this] var nameLength: Int = _
  private[this] var valueLength: Int = _
  private[this] var name: Array[Byte] = _

  private sealed abstract class State
  private object State {
    case object READ_HEADER_REPRESENTATION extends State
    case object READ_MAX_DYNAMIC_TABLE_SIZE extends State
    case object READ_INDEXED_HEADER extends State
    case object READ_INDEXED_HEADER_NAME extends State
    case object READ_LITERAL_HEADER_NAME_LENGTH_PREFIX extends State
    case object READ_LITERAL_HEADER_NAME_LENGTH extends State
    case object READ_LITERAL_HEADER_NAME extends State
    case object SKIP_LITERAL_HEADER_NAME extends State
    case object READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX extends State
    case object READ_LITERAL_HEADER_VALUE_LENGTH extends State
    case object READ_LITERAL_HEADER_VALUE extends State
    case object SKIP_LITERAL_HEADER_VALUE extends State
  }

  /** Creates a new decoder.
    */
  def this(maxHeaderSize: Int, maxHeaderTableSize: Int) = {
    this(new DynamicTable(maxHeaderTableSize));
    this.maxHeaderSize = maxHeaderSize;
    maxDynamicTableSize = maxHeaderTableSize;
    encoderMaxDynamicTableSize = maxHeaderTableSize;
    maxDynamicTableSizeChangeRequired = false;
    reset();
  }

  private def reset(): Unit = {
    headerSize = 0;
    state = State.READ_HEADER_REPRESENTATION;
    indexType = IndexType.NONE;
  }

  /** Decode the header block into header fields.
    */
  @throws[IOException]
  def decode(in: InputStream, headerListener: HeaderListener): Unit = {
    while (in.available() > 0) {
      var b: Byte = 0
      import State._
      state match {
        case READ_HEADER_REPRESENTATION =>
          b = in.read().toByte;
          if (maxDynamicTableSizeChangeRequired && (b & 0xe0) != 0x20) {
            // Encoder MUST signal maximum dynamic table size change
            throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
          }
          if (b < 0) {
            // Indexed Header Field
            index = b & 0x7f;
            if (index == 0) {
              throw ILLEGAL_INDEX_VALUE;
            } else if (index == 0x7f) {
              state = State.READ_INDEXED_HEADER;
            } else {
              indexHeader(index, headerListener);
            }
          } else if ((b & 0x40) == 0x40) {
            // Literal Header Field with Incremental Indexing
            indexType = IndexType.INCREMENTAL;
            index = b & 0x3f;
            if (index == 0) {
              state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
            } else if (index == 0x3f) {
              state = State.READ_INDEXED_HEADER_NAME;
            } else {
              // Index was stored as the prefix
              readName(index);
              state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
            }
          } else if ((b & 0x20) == 0x20) {
            // Dynamic Table Size Update
            index = b & 0x1f;
            if (index == 0x1f) {
              state = State.READ_MAX_DYNAMIC_TABLE_SIZE;
            } else {
              setDynamicTableSize(index);
              state = State.READ_HEADER_REPRESENTATION;
            }
          } else {
            // Literal Header Field without Indexing / never Indexed
            indexType = if ((b & 0x10) == 0x10) IndexType.NEVER else IndexType.NONE;
            index = b & 0x0f;
            if (index == 0) {
              state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
            } else if (index == 0x0f) {
              state = State.READ_INDEXED_HEADER_NAME;
            } else {
              // Index was stored as the prefix
              readName(index);
              state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
            }
          }

        case READ_MAX_DYNAMIC_TABLE_SIZE =>
          val maxSize = decodeULE128(in);
          if (maxSize == -1) {
            return;
          }

          // Check for numerical overflow
          if (maxSize > Integer.MAX_VALUE - index) {
            throw DECOMPRESSION_EXCEPTION;
          }

          setDynamicTableSize(index + maxSize);
          state = State.READ_HEADER_REPRESENTATION;

        case READ_INDEXED_HEADER =>
          val headerIndex = decodeULE128(in);
          if (headerIndex == -1) {
            return;
          }

          // Check for numerical overflow
          if (headerIndex > Integer.MAX_VALUE - index) {
            throw DECOMPRESSION_EXCEPTION;
          }

          indexHeader(index + headerIndex, headerListener);
          state = State.READ_HEADER_REPRESENTATION;

        case READ_INDEXED_HEADER_NAME =>
          // Header Name matches an entry in the Header Table
          val nameIndex = decodeULE128(in);
          if (nameIndex == -1) {
            return;
          }

          // Check for numerical overflow
          if (nameIndex > Integer.MAX_VALUE - index) {
            throw DECOMPRESSION_EXCEPTION;
          }

          readName(index + nameIndex);
          state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;

        case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX =>
          b = in.read().toByte;
          huffmanEncoded = (b & 0x80) == 0x80;
          index = b & 0x7f;
          if (index == 0x7f) {
            state = State.READ_LITERAL_HEADER_NAME_LENGTH;
          } else {
            nameLength = index;

            // Disallow empty names -- they cannot be represented in HTTP/1.x
            if (nameLength == 0) {
              throw DECOMPRESSION_EXCEPTION;
            }

            def breakable(): Unit = {
              // Check name length against max header size
              if (exceedsMaxHeaderSize(nameLength)) {

                if (indexType == IndexType.NONE) {
                  // Name is unused so skip bytes
                  name = EMPTY;
                  skipLength = nameLength;
                  state = State.SKIP_LITERAL_HEADER_NAME;
                  return;
                }

                // Check name length against max dynamic table size
                if (nameLength + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity) {
                  dynamicTable.clear();
                  name = EMPTY;
                  skipLength = nameLength;
                  state = State.SKIP_LITERAL_HEADER_NAME;
                  return;
                }
              }
              state = State.READ_LITERAL_HEADER_NAME;
            }

            breakable()
          }

        case READ_LITERAL_HEADER_NAME_LENGTH =>
          // Header Name is a Literal String
          nameLength = decodeULE128(in);
          if (nameLength == -1) {
            return;
          }

          // Check for numerical overflow
          if (nameLength > Integer.MAX_VALUE - index) {
            throw DECOMPRESSION_EXCEPTION;
          }
          nameLength += index;

          def breakable(): Unit = {
            // Check name length against max header size
            if (exceedsMaxHeaderSize(nameLength)) {
              if (indexType == IndexType.NONE) {
                // Name is unused so skip bytes
                name = EMPTY;
                skipLength = nameLength;
                state = State.SKIP_LITERAL_HEADER_NAME;
                return;
              }

              // Check name length against max dynamic table size
              if (nameLength + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity) {
                dynamicTable.clear();
                name = EMPTY;
                skipLength = nameLength;
                state = State.SKIP_LITERAL_HEADER_NAME;
                return;
              }
            }
            state = State.READ_LITERAL_HEADER_NAME;
          }

          breakable()

        case READ_LITERAL_HEADER_NAME =>
          // Wait until entire name is readable
          if (in.available() < nameLength) {
            return;
          }

          name = readStringLiteral(in, nameLength);

          state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;

        case SKIP_LITERAL_HEADER_NAME =>
          skipLength -= in.skip(skipLength.toLong).toInt;

          if (skipLength == 0) {
            state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
          }

        case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX =>
          b = in.read().toByte;
          huffmanEncoded = (b & 0x80) == 0x80;
          index = b & 0x7f;
          if (index == 0x7f) {
            state = State.READ_LITERAL_HEADER_VALUE_LENGTH;
          } else {
            valueLength = index;

            // Check new header size against max header size
            val newHeaderSize = nameLength.toLong + valueLength.toLong;

            def breakable(): Unit = {
              if (exceedsMaxHeaderSize(newHeaderSize)) {
                // truncation will be reported during endHeaderBlock
                headerSize = maxHeaderSize + 1;

                if (indexType == IndexType.NONE) {
                  // Value is unused so skip bytes
                  state = State.SKIP_LITERAL_HEADER_VALUE;
                  return;
                }

                // Check new header size against max dynamic table size
                if (newHeaderSize + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity) {
                  dynamicTable.clear();
                  state = State.SKIP_LITERAL_HEADER_VALUE;
                  return;
                }
              }

              if (valueLength == 0) {
                insertHeader(headerListener, name, EMPTY, indexType);
                state = State.READ_HEADER_REPRESENTATION;
              } else {
                state = State.READ_LITERAL_HEADER_VALUE;
              }
            }

            breakable()
          }

        case READ_LITERAL_HEADER_VALUE_LENGTH =>
          // Header Value is a Literal String
          valueLength = decodeULE128(in);
          if (valueLength == -1) {
            return;
          }

          // Check for numerical overflow
          if (valueLength > Integer.MAX_VALUE - index) {
            throw DECOMPRESSION_EXCEPTION;
          }
          valueLength += index;

          // Check new header size against max header size
          val newHeaderSize = nameLength.toLong + valueLength.toLong;

          def breakable(): Unit = {
            if (newHeaderSize + headerSize > maxHeaderSize) {
              // truncation will be reported during endHeaderBlock
              headerSize = maxHeaderSize + 1;

              if (indexType == IndexType.NONE) {
                // Value is unused so skip bytes
                state = State.SKIP_LITERAL_HEADER_VALUE;
                return;
              }

              // Check new header size against max dynamic table size
              if (newHeaderSize + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity) {
                dynamicTable.clear();
                state = State.SKIP_LITERAL_HEADER_VALUE;
                return;
              }
            }
            state = State.READ_LITERAL_HEADER_VALUE;
          }

          breakable()

        case READ_LITERAL_HEADER_VALUE =>
          // Wait until entire value is readable
          if (in.available() < valueLength) {
            return;
          }

          val value = readStringLiteral(in, valueLength);
          insertHeader(headerListener, name, value, indexType);
          state = State.READ_HEADER_REPRESENTATION;

        case SKIP_LITERAL_HEADER_VALUE =>
          valueLength -= in.skip(valueLength.toLong).toInt;

          if (valueLength == 0) {
            state = State.READ_HEADER_REPRESENTATION;
          }

        case _ =>
          throw new IllegalStateException("should not reach here");
      }
    }
  }

  /** End the current header block. Returns if the header field has been truncated.
    * This must be called after the header block has been completely decoded.
    */
  def endHeaderBlock(): Boolean = {
    val truncated = headerSize > maxHeaderSize;
    reset();
    return truncated;
  }

  /** Set the maximum table size.
    * If this is below the maximum size of the dynamic table used by the encoder,
    * the beginning of the next header block MUST signal this change.
    */
  def setMaxHeaderTableSize(maxHeaderTableSize: Int): Unit = {
    maxDynamicTableSize = maxHeaderTableSize;
    if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
      // decoder requires less space than encoder
      // encoder MUST signal this change
      maxDynamicTableSizeChangeRequired = true;
      dynamicTable.setCapacity(maxDynamicTableSize);
    }
  }

  /** Return the maximum table size.
    * This is the maximum size allowed by both the encoder and the decoder.
    */
  def getMaxHeaderTableSize(): Int =
    return dynamicTable.capacity;

  /** Return the number of header fields in the dynamic table.
    * Exposed for testing.
    */
  private[hpack] def length(): Int =
    return dynamicTable.length();

  /** Return the size of the dynamic table.
    * Exposed for testing.
    */
  private[hpack] def size(): Int =
    return dynamicTable.size;

  /** Return the header field at the given index.
    * Exposed for testing.
    */
  private[hpack] def getHeaderField(index: Int): HeaderField =
    return dynamicTable.getEntry(index + 1);

  @throws[IOException]
  private[this] def setDynamicTableSize(dynamicTableSize: Int): Unit = {
    if (dynamicTableSize > maxDynamicTableSize) {
      throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
    }
    encoderMaxDynamicTableSize = dynamicTableSize;
    maxDynamicTableSizeChangeRequired = false;
    dynamicTable.setCapacity(dynamicTableSize);
  }

  @throws[IOException]
  private[this] def readName(index: Int): Unit =
    if (index <= StaticTable.length) {
      val headerField = StaticTable.getEntry(index);
      name = headerField.name;
    } else if (index - StaticTable.length <= dynamicTable.length()) {
      val headerField = dynamicTable.getEntry(index - StaticTable.length);
      name = headerField.name;
    } else {
      throw ILLEGAL_INDEX_VALUE;
    }

  @throws[IOException]
  private[this] def indexHeader(index: Int, headerListener: HeaderListener): Unit =
    if (index <= StaticTable.length) {
      val headerField = StaticTable.getEntry(index);
      addHeader(headerListener, headerField.name, headerField.value, false);
    } else if (index - StaticTable.length <= dynamicTable.length()) {
      val headerField = dynamicTable.getEntry(index - StaticTable.length);
      addHeader(headerListener, headerField.name, headerField.value, false);
    } else {
      throw ILLEGAL_INDEX_VALUE;
    }

  private[this] def insertHeader(
      headerListener: HeaderListener,
      name: Array[Byte],
      value: Array[Byte],
      indexType: IndexType,
  ): Unit = {
    addHeader(headerListener, name, value, indexType == IndexType.NEVER);

    import IndexType._

    indexType match {
      case NEVER | NONE =>

      case INCREMENTAL =>
        dynamicTable.add(new HeaderField(name, value));

      case _ =>
        throw new IllegalStateException("should not reach here");
    }
  }

  private[this] def addHeader(
      headerListener: HeaderListener,
      name: Array[Byte],
      value: Array[Byte],
      sensitive: Boolean,
  ): Unit = {
    if (name.length == 0) {
      throw new AssertionError("name is empty");
    }
    val newSize = headerSize + name.length + value.length;
    if (newSize <= maxHeaderSize) {
      headerListener.addHeader(name, value, sensitive);
      headerSize = newSize.toInt;
    } else {
      // truncation will be reported during endHeaderBlock
      headerSize = maxHeaderSize + 1;
    }
  }

  private[this] def exceedsMaxHeaderSize(size: Long): Boolean = {
    // Check new header size against max header size
    if (size + headerSize <= maxHeaderSize) {
      return false;
    }

    // truncation will be reported during endHeaderBlock
    headerSize = maxHeaderSize + 1;
    return true;
  }

  @throws[IOException]
  private[this] def readStringLiteral(in: InputStream, length: Int): Array[Byte] = {
    val buf = new Array[Byte](length);
    if (in.read(buf) != length) {
      throw DECOMPRESSION_EXCEPTION;
    }

    if (huffmanEncoded) {
      return Huffman.DECODER.decode(buf);
    } else {
      return buf;
    }
  }

  // Unsigned Little Endian Base 128 Variable-Length Integer Encoding
  @throws[IOException]
  private[this] def decodeULE128(in: InputStream): Int = {
    in.mark(5);
    var result = 0;
    var shift = 0;
    while (shift < 32) {
      if (in.available() == 0) {
        // Buffer does not contain entire integer,
        // reset reader index and return -1.
        in.reset();
        return -1;
      }
      val b = in.read().toByte;
      if (shift == 28 && (b & 0xf8) != 0) {
        break;
      }
      result |= (b & 0x7f) << shift;
      if ((b & 0x80) == 0) {
        return result;
      }
      shift += 7;
    }

    def break = {
      // Value exceeds Integer.MAX_VALUE
      in.reset();
      throw DECOMPRESSION_EXCEPTION;
    }

    break
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy