Class: HTTP2Next::Header::Decompressor

Inherits:
Object
  • Object
show all
Includes:
Error
Defined in:
lib/http/2/next/compressor.rb

Overview

Responsible for decoding received headers and maintaining compression context of the opposing peer. Decompressor must be initialized with appropriate starting context based on local role: client or server.

Examples:

server_role = Decompressor.new(:request)
client_role = Decompressor.new(:response)

Constant Summary collapse

FORBIDDEN_HEADERS =
%w[connection te].freeze

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Decompressor

Returns a new instance of Decompressor.

Parameters:

  • options (Hash) (defaults to: {})

    decoding options. Only :table_size is effective.



492
493
494
# File 'lib/http/2/next/compressor.rb', line 492

def initialize(options = {})
  @cc = EncodingContext.new(options)
end

Instance Method Details

#decode(buf, frame = nil) ⇒ Array

Decodes and processes header commands within provided buffer.

Parameters:

  • buf (Buffer)
  • frame (HTTP2Next::Frame, nil) (defaults to: nil)

Returns:

  • (Array)

    +[[name, value], …]



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'lib/http/2/next/compressor.rb', line 584

def decode(buf, frame = nil)
  list = []
  decoding_pseudo_headers = true
  @cc.listen_on_table do
    until buf.empty?
      field, value = @cc.process(header(buf))
      next if field.nil?

      is_pseudo_header = field.start_with? ":"
      if !decoding_pseudo_headers && is_pseudo_header
        raise ProtocolError, "one or more pseudo headers encountered after regular headers"
      end

      decoding_pseudo_headers = is_pseudo_header
      raise ProtocolError, "invalid header received: #{field}" if FORBIDDEN_HEADERS.include?(field)

      if frame
        case field
        when ":method"
          frame[:method] = value
        when "content-length"
          frame[:content_length] = Integer(value)
        when "trailer"
          (frame[:trailer] ||= []) << value
        end
      end
      list << [field, value]
    end
  end
  list
end

#header(buf) ⇒ Hash

Decodes header command from provided buffer.

Parameters:

Returns:

  • (Hash)

    command

Raises:



545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/http/2/next/compressor.rb', line 545

def header(buf)
  peek = buf.readbyte(0)

  header = {}
  header[:type], type = HEADREP.find do |_t, desc|
    mask = (peek >> desc[:prefix]) << desc[:prefix]
    mask == desc[:pattern]
  end

  raise CompressionError unless header[:type]

  header[:name] = integer(buf, type[:prefix])

  case header[:type]
  when :indexed
    raise CompressionError if (header[:name]).zero?

    header[:name] -= 1
  when :changetablesize
    header[:value] = header[:name]
  else
    if (header[:name]).zero?
      header[:name] = string(buf)
    else
      header[:name] -= 1
    end
    header[:value] = string(buf)
  end

  header
end

#integer(buf, n) ⇒ Integer

Decodes integer value from provided buffer.

Parameters:

  • buf (String)
  • n (Integer)

    number of available bits

Returns:

  • (Integer)


507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/http/2/next/compressor.rb', line 507

def integer(buf, n)
  limit = 2**n - 1
  i = !n.zero? ? (buf.getbyte & limit) : 0

  m = 0
  if i == limit
    while (byte = buf.getbyte)
      i += ((byte & 127) << m)
      m += 7

      break if (byte & 128).zero?
    end
  end

  i
end

#string(buf) ⇒ String

Decodes string value from provided buffer.

Parameters:

  • buf (String)

Returns:

  • (String)

    UTF-8 encoded string

Raises:



529
530
531
532
533
534
535
536
537
538
539
# File 'lib/http/2/next/compressor.rb', line 529

def string(buf)
  raise CompressionError, "invalid header block fragment" if buf.empty?

  huffman = (buf.readbyte(0) & 0x80) == 0x80
  len = integer(buf, 7)
  str = buf.read(len)
  raise CompressionError, "string too short" unless str.bytesize == len

  str = Huffman.new.decode(Buffer.new(str)) if huffman
  str.force_encoding(Encoding::UTF_8)
end

#table_size=(size) ⇒ Object

Set dynamic table size in EncodingContext

Parameters:

  • size (Integer)

    new dynamic table size



498
499
500
# File 'lib/http/2/next/compressor.rb', line 498

def table_size=(size)
  @cc.table_size = size
end