summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-06-30 13:47:41 -0700
committerEric Wong <normalperson@yhbt.net>2009-06-30 14:02:10 -0700
commit608e6243a2b15bfc28c3524ed45d5fc7598e8565 (patch)
tree69bcc5904e91f87c605170605b0f564683558367
parent86cd3fec9a02352d10b53bac02f98267d2b77bdd (diff)
Eventually this (and ChunkedReader) may be done in C/Ragel
along with the existing HttpParser.
-rw-r--r--Manifest1
-rw-r--r--lib/unicorn/trailer_parser.rb52
-rw-r--r--test/unit/test_trailer_parser.rb52
3 files changed, 105 insertions, 0 deletions
diff --git a/Manifest b/Manifest
index 3e93cfd..fda8533 100644
--- a/Manifest
+++ b/Manifest
@@ -39,6 +39,7 @@ lib/unicorn/http_response.rb
 lib/unicorn/launcher.rb
 lib/unicorn/socket_helper.rb
 lib/unicorn/tee_input.rb
+lib/unicorn/trailer_parser.rb
 lib/unicorn/util.rb
 local.mk.sample
 setup.rb
diff --git a/lib/unicorn/trailer_parser.rb b/lib/unicorn/trailer_parser.rb
new file mode 100644
index 0000000..c65dc8a
--- /dev/null
+++ b/lib/unicorn/trailer_parser.rb
@@ -0,0 +1,52 @@
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+require 'unicorn'
+require 'unicorn/http11'
+
+# Eventually I should integrate this into HttpParser...
+module Unicorn
+  class TrailerParser
+
+    TR_FR = 'a-z-'.freeze
+    TR_TO = 'A-Z_'.freeze
+
+    # initializes HTTP trailer parser with acceptable +trailer+
+    def initialize(http_trailer)
+      @trailers = http_trailer.split(/\s*,\s*/).inject({}) { |hash, key|
+        hash[key.tr(TR_FR, TR_TO)] = true
+        hash
+      }
+    end
+
+    # Executes our TrailerParser on +data+ and modifies +env+  This will
+    # shrink +data+ as it is being consumed.  Returns true if it has
+    # parsed all trailers, false if not.  It raises HttpParserError on
+    # parse failure or unknown headers.  It has slightly smaller limits
+    # than the C-based HTTP parser but should not be an issue in practice
+    # since Content-MD5 is probably the only legitimate use for it.
+    def execute!(env, data)
+      data.size > 0xffff and
+        raise HttpParserError, "trailer buffer too large: #{data.size} bytes"
+
+      begin
+        data.sub!(/\A([^\r]+)\r\n/, Z) or return false # need more data
+
+        key, val = $1.split(/:\s*/, 2)
+
+        key.size > 256 and
+          raise HttpParserError, "trailer key #{key.inspect} is too long"
+        val.size > 8192 and
+          raise HttpParserError, "trailer value #{val.inspect} is too long"
+
+        key.tr!(TR_FR, TR_TO)
+
+        @trailers.delete(key.freeze) or
+          raise HttpParserError, "unknown trailer: #{key.inspect}"
+        env[key] = val
+
+        @trailers.empty? and return true
+      end while true
+    end
+
+  end
+end
diff --git a/test/unit/test_trailer_parser.rb b/test/unit/test_trailer_parser.rb
new file mode 100644
index 0000000..e41d00f
--- /dev/null
+++ b/test/unit/test_trailer_parser.rb
@@ -0,0 +1,52 @@
+require 'test/unit'
+require 'unicorn'
+require 'unicorn/http11'
+require 'unicorn/trailer_parser'
+
+class TestTrailerParser < Test::Unit::TestCase
+
+  def test_basic
+    tp = Unicorn::TrailerParser.new('Content-MD5')
+    env = {}
+    assert ! tp.execute!(env, "Content-MD5: asdf")
+    assert env.empty?
+    assert tp.execute!(env, "Content-MD5: asdf\r\n")
+    assert_equal 'asdf', env['CONTENT_MD5']
+    assert_equal 1, env.size
+  end
+
+  def test_invalid_trailer
+    tp = Unicorn::TrailerParser.new('Content-MD5')
+    env = {}
+    assert_raises(Unicorn::HttpParserError) {
+      tp.execute!(env, "Content-MD: asdf\r\n")
+    }
+    assert env.empty?
+  end
+
+  def test_multiple_trailer
+    tp = Unicorn::TrailerParser.new('Foo,Bar')
+    env = {}
+    buf = "Bar: a\r\nFoo: b\r\n"
+    assert tp.execute!(env, buf)
+    assert_equal 'a', env['BAR']
+    assert_equal 'b', env['FOO']
+  end
+
+  def test_too_big_key
+    tp = Unicorn::TrailerParser.new('Foo,Bar')
+    env = {}
+    buf = "Bar#{'a' * 1024}: a\r\nFoo: b\r\n"
+    assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
+    assert env.empty?
+  end
+
+  def test_too_big_value
+    tp = Unicorn::TrailerParser.new('Foo,Bar')
+    env = {}
+    buf = "Bar: #{'a' * (1024 * 1024)}: a\r\nFoo: b\r\n"
+    assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
+    assert env.empty?
+  end
+
+end