diff --git a/Gemfile b/Gemfile index b4e2a20b..8e10e273 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,9 @@ source "https://rubygems.org" gemspec + +gem "minitest", "~> 5.14" +gem "rake", "~> 13.0" +gem "rdoc", "~> 6.3" +gem "rubocop", "~> 1.12" +gem "brotli", ">= 0.5" unless RUBY_PLATFORM == "java" diff --git a/lib/mechanize/http/agent.rb b/lib/mechanize/http/agent.rb index 948f0c46..e5d3cbb7 100644 --- a/lib/mechanize/http/agent.rb +++ b/lib/mechanize/http/agent.rb @@ -2,7 +2,6 @@ require 'tempfile' require 'net/ntlm' require 'webrobots' -require 'brotli' ## # An HTTP (and local disk access) user agent. This class is an implementation @@ -498,14 +497,24 @@ def content_encoding_inflate body_io ## # Decodes a Brotli-encoded +body_io+ + # + # Although Mechanize will never request a Brotli-encoded response via `accept-encoding`, buggy + # servers may return brotli-encoded responses anyway. Let's try to handle that case if the Brotli + # gem is loaded. def content_encoding_brotli(body_io) - log.debug('deflate brotly body') if log + log.debug('deflate brotli body') if log + + unless defined?(::Brotli) + raise Mechanize::Error, "cannot deflate brotli-encoded response. Please install and require the 'brotli' gem." + end - return StringIO.new(Brotli.inflate(body_io.read)) - rescue Brotli::Error - log.error('unable to brotli-inflate response') if log - raise Mechanize::Error, "could not inflate brotli-encoded response" + begin + return StringIO.new(Brotli.inflate(body_io.read)) + rescue Brotli::Error + log.error("unable to brotli-inflate response") if log + raise Mechanize::Error, "error inflating brotli-encoded response." + end ensure body_io.close end diff --git a/mechanize.gemspec b/mechanize.gemspec index c23c4dab..beca19e3 100644 --- a/mechanize.gemspec +++ b/mechanize.gemspec @@ -58,7 +58,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency("http-cookie", ">= 1.0.3", "~> 1.0") spec.add_runtime_dependency("mime-types", "~> 3.0") spec.add_runtime_dependency("net-http-digest_auth", ">= 1.4.1", "~> 1.4") - spec.add_runtime_dependency("brotli", ">= 0.5") # careful! some folks are relying on older versions of net-http-persistent # - see the socks proxy patch in use at #507 and #464 @@ -72,9 +71,4 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency("rubyntlm", ">= 0.6.3", "~> 0.6") spec.add_runtime_dependency("base64") # removed from bundled gems in 3.4, and needed by rubyntlm (which doesn't declare this dependency) spec.add_runtime_dependency("nkf") # removed from bundled gems in 3.4 - - spec.add_development_dependency("minitest", "~> 5.14") - spec.add_development_dependency("rake", "~> 13.0") - spec.add_development_dependency("rdoc", "~> 6.3") - spec.add_development_dependency("rubocop", "~> 1.12") end diff --git a/test/test_mechanize_http_agent.rb b/test/test_mechanize_http_agent.rb index 128ee0f0..886bcd66 100644 --- a/test/test_mechanize_http_agent.rb +++ b/test/test_mechanize_http_agent.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require 'mechanize/test_case' +require "brotli" unless RUBY_PLATFORM == "java" class TestMechanizeHttpAgent < Mechanize::TestCase @@ -924,7 +925,23 @@ def @res.content_length() nil end assert_equal 'part', body.read end - def test_response_content_encoding_br + def test_response_content_encoding_brotli_when_brotli_not_loaded + skip("only test this on jruby which doesn't have brotli support") unless RUBY_ENGINE == 'jruby' + + @res.instance_variable_set :@header, 'content-encoding' => %w[br] + body_io = StringIO.new("content doesn't matter for this test") + + e = assert_raises(Mechanize::Error) do + @agent.response_content_encoding(@res, body_io) + end + assert_includes(e.message, "cannot deflate brotli-encoded response") + + assert(body_io.closed?) + end + + def test_response_content_encoding_brotli + skip("jruby does not have brotli support") if RUBY_ENGINE == 'jruby' + @res.instance_variable_set :@header, 'content-encoding' => %w[br] body_io = StringIO.new(Brotli.deflate("this is compressed by brotli")) @@ -934,14 +951,16 @@ def test_response_content_encoding_br assert(body_io.closed?) end - def test_response_content_encoding_br_corrupt + def test_response_content_encoding_brotli_corrupt + skip("jruby does not have brotli support") if RUBY_ENGINE == 'jruby' + @res.instance_variable_set :@header, 'content-encoding' => %w[br] body_io = StringIO.new("not a brotli payload") e = assert_raises(Mechanize::Error) do @agent.response_content_encoding(@res, body_io) end - assert_includes(e.message, "could not inflate brotli-encoded response") + assert_includes(e.message, "error inflating brotli-encoded response") assert_includes(e.cause.message, "ERROR_FORMAT_PADDING_2") assert(body_io.closed?)