From 608e6243a2b15bfc28c3524ed45d5fc7598e8565 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jun 2009 13:47:41 -0700 Subject: Add trailer_parser for parsing trailers Eventually this (and ChunkedReader) may be done in C/Ragel along with the existing HttpParser. --- Manifest | 1 + lib/unicorn/trailer_parser.rb | 52 ++++++++++++++++++++++++++++++++++++++++ test/unit/test_trailer_parser.rb | 52 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 lib/unicorn/trailer_parser.rb create mode 100644 test/unit/test_trailer_parser.rb 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 -- cgit v1.2.3-24-ge0c7