diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4fcf9f..6aef42c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.7 ,'3.0', 3.1, 3.2, 3.3, 3.4, 4.0, jruby, truffleruby] + ruby: [2.7 ,'3.0', 3.1, 3.2, 3.3, 3.4, 4.0, head, jruby, truffleruby] steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: RUBY_VERSION=`ruby -e 'puts RUBY_VERSION'` RUBY_PLATFORM=`ruby -e 'puts RUBY_PLATFORM'` RUBY_ENGINE=`ruby -e 'puts RUBY_ENGINE'` - if [[ "$RUBY_ENGINE" = "ruby" ]] && [[ ${RUBY_VERSION:0:1} = "3" ]] && [[ ! $RUBYOPT =~ "jit" ]]; then + if [[ "$RUBY_ENGINE" = "ruby" ]] && [[ ${RUBY_VERSION:0:1} = "4" ]] && [[ ! $RUBYOPT =~ "jit" ]]; then echo "running runtime type checking..." bundle exec rbs collection install export RUBYOPT="-rbundler/setup -rrbs/test/setup" diff --git a/.gitignore b/.gitignore index 124e488..90227d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea/ Gemfile.lock *.gem +spec/hpack-test-case \ No newline at end of file diff --git a/Rakefile b/Rakefile index 79ff5ad..0e71790 100644 --- a/Rakefile +++ b/Rakefile @@ -8,16 +8,17 @@ require_relative "tasks/generate_huffman_table" RUBY_MAJOR_MINOR = RUBY_VERSION.split(".").first(2).join(".") -begin - require "rspec/core/rake_task" - RSpec::Core::RakeTask.new(:spec) do |t| - t.exclude_pattern = "./spec/hpack_test_spec.rb" - end +require "rspec/core/rake_task" +RSpec::Core::RakeTask.new(:spec) do |t| + t.exclude_pattern = "./spec/hpack_test_spec.rb" +end - RSpec::Core::RakeTask.new(:hpack) do |t| - t.pattern = "./spec/hpack_test_spec.rb" - end -rescue LoadError +RSpec::Core::RakeTask.new(:hpack) do |t| + t.pattern = "./spec/hpack_test_spec.rb" +end + +task :prepare_hpack do + system("git clone --depth 1 https://github.com/http2jp/hpack-test-case.git spec/hpack-test-case") end begin @@ -103,6 +104,6 @@ end default_tasks = %i[spec] default_tasks << :rubocop if defined?(RuboCop) && RUBY_ENGINE == "ruby" -default_tasks += %i[h2spec_install h2spec] if ENV.key?("CI") +default_tasks += %i[prepare_hpack hpack h2spec_install h2spec] if ENV.key?("CI") task default: default_tasks -task all: %i[default hpack] +task all: default_tasks diff --git a/lib/http/2/header/decompressor.rb b/lib/http/2/header/decompressor.rb index 4a64286..002a641 100644 --- a/lib/http/2/header/decompressor.rb +++ b/lib/http/2/header/decompressor.rb @@ -17,6 +17,7 @@ class Decompressor # @param options [Hash] decoding options. Only :table_size is effective. def initialize(options = {}) + @accept_invalid_headers = options.fetch(:accept_invalid_headers, false) @cc = EncodingContext.new(options) end @@ -123,12 +124,14 @@ def decode(buf, frame = nil) next if field.nil? is_pseudo_header = field.start_with?(":") - if !decoding_pseudo_headers && is_pseudo_header + if !decoding_pseudo_headers && is_pseudo_header && !@accept_invalid_headers 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 !@accept_invalid_headers && FORBIDDEN_HEADERS.include?(field) + raise ProtocolError, "invalid header received: #{field}" + end if frame case field diff --git a/spec/hpack_test_spec.rb b/spec/hpack_test_spec.rb index 083ced1..b8e2f08 100644 --- a/spec/hpack_test_spec.rb +++ b/spec/hpack_test_spec.rb @@ -36,12 +36,12 @@ story = JSON.parse(File.read("#{path}/#{file}")) cases = story["cases"] table_size = cases[0]["header_table_size"] || 4096 - @dc = Decompressor.new(table_size: table_size) + @dc = Decompressor.new(table_size: table_size, accept_invalid_headers: true) cases.each do |c| wire = [c["wire"]].pack("H*").force_encoding(Encoding::BINARY) - @emitted = @dc.decode(HTTP2::Buffer.new(wire)) + @emitted = @dc.decode(wire) headers = c["headers"].flat_map(&:to_a) - expect(@emitted).to eq headers + expect(@emitted).to match_array(headers) end end end @@ -74,12 +74,12 @@ story = JSON.parse(File.read("#{path}/#{file}")) cases = story["cases"] @cc = Compressor.new(options) - @dc = Decompressor.new(options) + @dc = Decompressor.new(options.merge(accept_invalid_headers: true)) cases.each do |c| headers = c["headers"].flat_map(&:to_a) wire = @cc.encode(headers) - decoded = @dc.decode(HTTP2::Buffer.new(wire)) - expect(decoded).to eq headers + decoded = @dc.decode(wire) + expect(decoded).to match_array(headers) end end end