diff options
author | Eric Wong <e@80x24.org> | 2016-11-29 03:30:02 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2016-11-29 21:01:54 +0000 |
commit | 7fe9d585056ea5002cd789d70c3ea4bd5000759c (patch) | |
tree | b211b22183dd66e532f1d26cf3bb3c09c404e800 | |
parent | 6e299e20c55aa8cc63597adf90ae4d370c086b83 (diff) | |
download | yahns-7fe9d585056ea5002cd789d70c3ea4bd5000759c.tar.gz |
We still need to iterate through all response headers to support response-only Rack hijacking. Previously, we only supported full hijacking on so-called "HTTP/0.9" clients. n.b. This diff will be easier to read with the -b/--ignore-space-change option of git-diff(1) or GNU diff(1)
-rw-r--r-- | lib/yahns/http_response.rb | 114 | ||||
-rw-r--r-- | test/test_rack_hijack.rb | 6 |
2 files changed, 62 insertions, 58 deletions
diff --git a/lib/yahns/http_response.rb b/lib/yahns/http_response.rb index e2fc940..a31ab70 100644 --- a/lib/yahns/http_response.rb +++ b/lib/yahns/http_response.rb @@ -131,68 +131,66 @@ module Yahns::HttpResponse # :nodoc: term = false hdr_only, chunk_ok = opt - if @hs.headers? - code = status.to_i - hdr_only ||= Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code) - msg = Rack::Utils::HTTP_STATUS_CODES[code] - buf = "#{response_start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \ - "Date: #{httpdate}\r\n".dup - headers.each do |key, value| - case key - when %r{\ADate\z}i - next - when %r{\AContent-Range\z}i - if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ value - offset = $1.to_i - count = $2.to_i - offset + 1 - end - kv_str(buf, key, value) - when %r{\AConnection\z}i - # allow Rack apps to tell us they want to drop the client - alive = false if value =~ /\bclose\b/i - when %r{\AContent-Length\z}i - term = true - clen = value.to_i - flags |= MSG_MORE if clen > 0 && !hdr_only - kv_str(buf, key, value) - when %r{\ATransfer-Encoding\z}i - term = true if value =~ /\bchunked\b/i - kv_str(buf, key, value) - when "rack.hijack" - hijack = value - else - kv_str(buf, key, value) + code = status.to_i + hdr_only ||= Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code) + msg = Rack::Utils::HTTP_STATUS_CODES[code] + buf = "#{response_start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \ + "Date: #{httpdate}\r\n".dup + headers.each do |key, value| + case key + when %r{\ADate\z}i + next + when %r{\AContent-Range\z}i + if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ value + offset = $1.to_i + count = $2.to_i - offset + 1 end - end - count ||= clen - - if !term && chunk_ok + kv_str(buf, key, value) + when %r{\AConnection\z}i + # allow Rack apps to tell us they want to drop the client + alive = false if value =~ /\bclose\b/i + when %r{\AContent-Length\z}i term = true - body = Yahns::ChunkBody.new(body, opt) - buf << "Transfer-Encoding: chunked\r\n".freeze + clen = value.to_i + flags |= MSG_MORE if clen > 0 && !hdr_only + kv_str(buf, key, value) + when %r{\ATransfer-Encoding\z}i + term = true if value =~ /\bchunked\b/i + kv_str(buf, key, value) + when "rack.hijack" + hijack = value + else + kv_str(buf, key, value) end - alive &&= term - buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze - : "Connection: close\r\n\r\n".freeze) - case rv = kgio_syssend(buf, flags) - when nil # all done, likely - buf.clear - buf = nil # recycle any memory we used ASAP - break - when String - flags = MSG_DONTWAIT - buf = rv # unlikely, hope the skb grows - when :wait_writable, :wait_readable # unlikely - if self.class.output_buffering - alive = hijack ? hijack : alive - rv = response_header_blocked(buf, body, alive, offset, count) - body = nil # ensure we do not close body in ensure - return rv - else - response_wait_write(rv) or return :close - end - end while true end + count ||= clen + + if !term && chunk_ok + term = true + body = Yahns::ChunkBody.new(body, opt) + buf << "Transfer-Encoding: chunked\r\n".freeze + end + alive &&= term + buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze + : "Connection: close\r\n\r\n".freeze) + case rv = kgio_syssend(buf, flags) + when nil # all done, likely + buf.clear + buf = nil # recycle any memory we used ASAP + break + when String + flags = MSG_DONTWAIT + buf = rv # unlikely, hope the skb grows + when :wait_writable, :wait_readable # unlikely + if self.class.output_buffering + alive = hijack ? hijack : alive + rv = response_header_blocked(buf, body, alive, offset, count) + body = nil # ensure we do not close body in ensure + return rv + else + response_wait_write(rv) or return :close + end + end while @hs.headers? return response_hijacked(hijack) if hijack return http_response_done(alive) if hdr_only diff --git a/test/test_rack_hijack.rb b/test/test_rack_hijack.rb index 671387e..c878552 100644 --- a/test/test_rack_hijack.rb +++ b/test/test_rack_hijack.rb @@ -86,6 +86,12 @@ class TestRackHijack < Testcase assert_equal "rack.input contents: BLAH", res.body assert_equal 201, res.code.to_i assert_equal "1.0", res.http_version + + # ancient "HTTP/0.9" + c = get_tcp_client(host, port) + c.write("GET /hijack_res\r\n\r\n") + res = Timeout.timeout(30) { c.read } + assert_equal 'response.hijacked', res ensure quit_wait(pid) end |