about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2016-11-29 03:30:02 +0000
committerEric Wong <e@80x24.org>2016-11-29 21:01:54 +0000
commit7fe9d585056ea5002cd789d70c3ea4bd5000759c (patch)
treeb211b22183dd66e532f1d26cf3bb3c09c404e800
parent6e299e20c55aa8cc63597adf90ae4d370c086b83 (diff)
downloadyahns-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.rb114
-rw-r--r--test/test_rack_hijack.rb6
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