diff --git a/lib/httpx/connection/http1.rb b/lib/httpx/connection/http1.rb index 3903a40a..01815058 100644 --- a/lib/httpx/connection/http1.rb +++ b/lib/httpx/connection/http1.rb @@ -163,13 +163,13 @@ module HTTPX end def handle_error(ex) - if ex.is_a?(EOFError) && @request && @request.response && + if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response && !@request.response.headers.key?("content-length") && !@request.response.headers.key?("transfer-encoding") # if the response does not contain a content-length header, the server closing the # connnection is the indicator of response consumed. # https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4 - on_complete + catch(:called) { on_complete } return end diff --git a/lib/httpx/parser/http1.rb b/lib/httpx/parser/http1.rb index da419ffd..919f14d0 100644 --- a/lib/httpx/parser/http1.rb +++ b/lib/httpx/parser/http1.rb @@ -66,7 +66,6 @@ module HTTPX @status_code = code.to_i raise(Error, "wrong status code (#{@status_code})") unless (100..599).cover?(@status_code) - # @buffer.slice!(0, idx + 1) @buffer = @buffer.byteslice((idx + 1)..-1) nextstate(:headers) end @@ -74,7 +73,8 @@ module HTTPX def parse_headers headers = @headers while (idx = @buffer.index("\n")) - line = @buffer.slice!(0, idx + 1).sub(/\s+\z/, "") + line = @buffer.byteslice(0..idx).sub(/\s+\z/, "") + @buffer = @buffer.byteslice((idx + 1)..-1) if line.empty? case @state when :headers @@ -96,11 +96,11 @@ module HTTPX separator_index = line.index(":") raise Error, "wrong header format" unless separator_index - key = line[0..separator_index - 1] + key = line.byteslice(0..(separator_index - 1)) raise Error, "wrong header format" if key.start_with?("\s", "\t") key.strip! - value = line[separator_index + 1..-1] + value = line.byteslice((separator_index + 1)..-1) value.strip! raise Error, "wrong header format" if value.nil? diff --git a/test/support/keep_alive_server.rb b/test/support/keep_alive_server.rb index 98df0be1..af8d94c3 100644 --- a/test/support/keep_alive_server.rb +++ b/test/support/keep_alive_server.rb @@ -2,6 +2,8 @@ require "webrick" require "logger" +require "zlib" +require "stringio" class TestServer < WEBrick::HTTPServer def initialize(options = {}) @@ -62,3 +64,40 @@ class Expect100Server < TestServer mount("/delay", DelayedExpect100App) end end + +class NoContentLengthServer < TestServer + module NoContentLength + def self.extended(obj) + super + obj.singleton_class.class_eval do + alias_method(:setup_header_without_clength, :setup_header) + alias_method(:setup_header, :setup_header_with_clength) + end + end + + def setup_header_with_clength + setup_header_without_clength + header.delete("content-length") + end + end + + class NoContentLengthApp < WEBrick::HTTPServlet::AbstractServlet + def do_GET(_req, res) # rubocop:disable Naming/MethodName + zipped = StringIO.new + Zlib::GzipWriter.wrap(zipped) do |gz| + gz.write("helloworld") + end + res.body = zipped.string + + res.status = 200 + res["Content-Encoding"] = "gzip" + + res.extend(NoContentLength) + end + end + + def initialize(options = {}) + super + mount("/", NoContentLengthApp) + end +end diff --git a/test/support/requests/plugins/compression.rb b/test/support/requests/plugins/compression.rb index e464e891..ac755d6e 100644 --- a/test/support/requests/plugins/compression.rb +++ b/test/support/requests/plugins/compression.rb @@ -85,6 +85,26 @@ module Requests assert compressed_data.bytesize < 8012, "body hasn't been compressed" end + # regression test + def test_plugin_compression_no_content_length + # run this only for http/1.1 mode, as this is a local test server + return unless origin.start_with?("http://") + + server = NoContentLengthServer.new + th = Thread.new { server.start } + begin + http = HTTPX.plugin(:compression) + uri = build_uri("/", server.origin) + response = http.get(uri) + verify_status(response, 200) + body = response.body.to_s + assert body == "helloworld" + ensure + server.shutdown + th.join + end + end + unless RUBY_ENGINE == "jruby" def test_plugin_compression_brotli session = HTTPX.plugin(:"compression/brotli") diff --git a/test/support/requests/plugins/cookies.rb b/test/support/requests/plugins/cookies.rb index d6bb9eed..1deca2b0 100644 --- a/test/support/requests/plugins/cookies.rb +++ b/test/support/requests/plugins/cookies.rb @@ -130,15 +130,20 @@ module Requests assert !path_jar[build_uri("/cookies/set")].empty? # Test expires - expires_jar = HTTPX::Plugins::Cookies::Jar.new - expires_jar.parse(%(a=b; Path=/; Max-Age=2)) - assert !expires_jar[cookies_uri].empty? + maxage_jar = HTTPX::Plugins::Cookies::Jar.new + maxage_jar.parse(%(a=b; Path=/; Max-Age=2)) + assert !maxage_jar[cookies_uri].empty? sleep 3 + assert maxage_jar[cookies_uri].empty? + + expires_jar = HTTPX::Plugins::Cookies::Jar.new + expires_jar.parse(%(a=b; Path=/; Expires=Sat, 02 Nov 2019 15:24:00 GMT)) assert expires_jar[cookies_uri].empty? - maxage_jar = HTTPX::Plugins::Cookies::Jar.new - maxage_jar.parse(%(a=b; Path=/; Expires=Sat, 02 Nov 2019 15:24:00 GMT)) - assert maxage_jar[cookies_uri].empty? + # regression test + rfc2616_expires_jar = HTTPX::Plugins::Cookies::Jar.new + rfc2616_expires_jar.parse(%(a=b; Path=/; Expires=Fri, 17-Feb-2023 12:43:41 GMT)) + assert !rfc2616_expires_jar[cookies_uri].empty? # Test domain domain_jar = HTTPX::Plugins::Cookies::Jar.new