Skip to content

Commit

Permalink
Backport Limit the size of parameter keys
Browse files Browse the repository at this point in the history
  • Loading branch information
raggi committed Dec 28, 2011
1 parent 67cc1c5 commit 09c5e53
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
40 changes: 40 additions & 0 deletions lib/rack/utils.rb
Expand Up @@ -28,6 +28,14 @@ def unescape(s)

DEFAULT_SEP = /[&;] */n

class << self
attr_accessor :key_space_limit
end

# The default number of bytes to allow parameter keys to take up.
# This helps prevent a rogue client from flooding a Request.
self.key_space_limit = 65536

# Stolen from Mongrel, with some small modifications:
# Parses a query string by breaking it up at the '&'
# and ';' characters. You can also use this to parse
Expand All @@ -36,8 +44,19 @@ def unescape(s)
def parse_query(qs, d = nil)
params = {}

max_key_space = Utils.key_space_limit
bytes = 0

(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) }

if k
bytes += k.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

if cur = params[k]
if cur.class == Array
params[k] << v
Expand All @@ -56,8 +75,19 @@ def parse_query(qs, d = nil)
def parse_nested_query(qs, d = nil)
params = {}

max_key_space = Utils.key_space_limit
bytes = 0

(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = unescape(p).split('=', 2)

if k
bytes += k.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

normalize_params(params, k, v)
end

Expand Down Expand Up @@ -489,6 +519,9 @@ def self.parse_multipart(env)

rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n

max_key_space = Utils.key_space_limit
bytes = 0

loop {
head = nil
body = ''
Expand All @@ -503,6 +536,13 @@ def self.parse_multipart(env)
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]

if name
bytes += name.size
if bytes > max_key_space
raise RangeError, "exceeded available parameter key space"
end
end

if content_type || filename
body = Tempfile.new("RackMultipart")
body.binmode if body.respond_to?(:binmode)
Expand Down
49 changes: 49 additions & 0 deletions test/spec_rack_request.rb
Expand Up @@ -79,6 +79,32 @@
req.params.should.equal "foo" => "bar", "quux" => "bla"
end

specify "limit the keys from the GET query string" do
env = Rack::MockRequest.env_for("/?foo=bar")

old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
begin
req = Rack::Request.new(env)
lambda { req.GET }.should.raise(RangeError)
ensure
Rack::Utils.key_space_limit = old
end
end

specify "limit the keys from the POST form data" do
env = Rack::MockRequest.env_for("",
"REQUEST_METHOD" => 'POST',
:input => "foo=bar&quux=bla")

old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
begin
req = Rack::Request.new(env)
lambda { req.POST }.should.raise(RangeError)
ensure
Rack::Utils.key_space_limit = old
end
end

specify "can parse POST data with explicit content type regardless of method" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
Expand Down Expand Up @@ -295,6 +321,29 @@
req.media_type_params['bling'].should.equal 'bam'
end

specify "raise RangeError if the key space is exhausted" do
input = <<EOF
--AaB03x\r
Content-Disposition: form-data; name="text"\r
Content-Type: text/plain; charset=US-ASCII\r
\r
contents\r
--AaB03x--\r
EOF

env = Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)

old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
begin
lambda { Rack::Utils::Multipart.parse_multipart(env) }.should.raise(RangeError)
ensure
Rack::Utils.key_space_limit = old
end
end

specify "can parse multipart form data" do
# Adapted from RFC 1867.
input = <<EOF
Expand Down

0 comments on commit 09c5e53

Please sign in to comment.