summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-03-21 19:29:44 -0700
committerEric Wong <normalperson@yhbt.net>2009-03-21 19:29:44 -0700
commit771ee626338c8fa00f8eedda9bdfbbddb5206491 (patch)
tree841000a8732a416e790b755c81e5085426969fb7
parent9a59450a8f8cb415fa11ddbf5bf375c9cb7829d3 (diff)
Rack uses a single newline character to represent multi-value
headers.  Thus { 'Set-Cookie' => "foo=bar\nbar=foo" }
will get you:

  Set-Cookie: foo=bar
  Set-Cookie: bar=foo

While RFC2616 says you can combine headers as:

  Set-Cookie: foo=bar,bar=foo

There are probably HTTP clients out there that don't handle
things correctly so don't bother...

Additionally, don't bother doing duplicate suppression anymore.
Just assume Rack or a higher layer knows what it's doing
regarding duplicates and we'll get a Hash most of the time
anyways.
-rw-r--r--lib/unicorn/http_response.rb27
-rw-r--r--test/unit/test_response.rb7
2 files changed, 18 insertions, 16 deletions
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 2635f14..c8aa3f9 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -21,30 +21,25 @@ module Unicorn
 
   class HttpResponse
 
-    # headers we allow duplicates for
-    ALLOWED_DUPLICATES = {
-      'Set-Cookie' => true,
-      'Set-Cookie2' => true,
-      'Warning' => true,
-      'WWW-Authenticate' => true,
-    }.freeze
+    # Rack does not set/require a Date: header.  We always override the
+    # Connection: and Date: headers no matter what (if anything) our
+    # Rack application sent us.
+    SKIP = { 'connection' => true, 'date' => true }.freeze
 
     # writes the rack_response to socket as an HTTP response
     def self.write(socket, rack_response)
       status, headers, body = rack_response
+      out = [ "Date: #{Time.now.httpdate}" ]
 
-      # Rack does not set/require Date, but don't worry about Content-Length
-      # since Rack applications that conform to Rack::Lint enforce that
-      out = [ "#{Const::DATE}: #{Time.now.httpdate}" ]
-      sent = { Const::CONNECTION => true, Const::DATE => true }
-
+      # Don't bother enforcing duplicate supression, it's a Hash most of
+      # the time anyways so just hope our app knows what it's doing
       headers.each do |key, value|
-        if ! sent[key] || ALLOWED_DUPLICATES[key]
-          sent[key] = true
-          out << "#{key}: #{value}"
-        end
+        next if SKIP.include?(key.downcase)
+        value.split(/\n/).each { |v| out << "#{key}: #{v}" }
       end
 
+      # Rack should enforce Content-Length or chunked transfer encoding,
+      # so don't worry or care about them.
       socket_write(socket,
                    "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n" \
                    "Connection: close\r\n" \
diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb
index c30a141..4c7423b 100644
--- a/test/unit/test_response.rb
+++ b/test/unit/test_response.rb
@@ -41,5 +41,12 @@ class ResponseTest < Test::Unit::TestCase
     io.rewind
     assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase")
   end
+
+  def test_rack_multivalue_headers
+    out = StringIO.new
+    HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
+    assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
+  end
+
 end