Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Backport `Limit the size of parameter keys`

  • Loading branch information...
commit 09c5e53f11a491c25bef873ed146842f3cd03228 1 parent 67cc1c5
@raggi raggi authored
Showing with 89 additions and 0 deletions.
  1. +40 −0 lib/rack/utils.rb
  2. +49 −0 test/spec_rack_request.rb
View
40 lib/rack/utils.rb
@@ -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
@@ -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
@@ -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
@@ -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 = ''
@@ -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)
View
49 test/spec_rack_request.rb
@@ -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("/",
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.