lib/rack/utils.rb in rack-1.1.2 vs lib/rack/utils.rb in rack-1.1.3
- old
+ new
@@ -26,20 +26,39 @@
end
module_function :unescape
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
# cookies by changing the characters used in the second
# parameter (which defaults to '&;').
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
else
params[k] = [cur, v]
@@ -54,12 +73,23 @@
module_function :parse_query
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
return params
end
@@ -172,51 +202,78 @@
when Hash
domain = "; domain=" + value[:domain] if value[:domain]
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
- expires = "; expires=" + value[:expires].clone.gmtime.
- strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
+ expires = "; expires=" +
+ rfc2822(value[:expires].clone.gmtime) if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
end
value = [value] unless Array === value
cookie = escape(key) + "=" +
value.map { |v| escape v }.join("&") +
"#{domain}#{path}#{expires}#{secure}#{httponly}"
case header["Set-Cookie"]
- when Array
- header["Set-Cookie"] << cookie
- when String
- header["Set-Cookie"] = [header["Set-Cookie"], cookie]
- when nil
+ when nil, ''
header["Set-Cookie"] = cookie
+ when String
+ header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
+ when Array
+ header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
end
nil
end
module_function :set_cookie_header!
def delete_cookie_header!(header, key, value = {})
- unless Array === header["Set-Cookie"]
- header["Set-Cookie"] = [header["Set-Cookie"]].compact
+ case header["Set-Cookie"]
+ when nil, ''
+ cookies = []
+ when String
+ cookies = header["Set-Cookie"].split("\n")
+ when Array
+ cookies = header["Set-Cookie"]
end
- header["Set-Cookie"].reject! { |cookie|
- cookie =~ /\A#{escape(key)}=/
+ cookies.reject! { |cookie|
+ if value[:domain]
+ cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
+ else
+ cookie =~ /\A#{escape(key)}=/
+ end
}
+ header["Set-Cookie"] = cookies.join("\n")
+
set_cookie_header!(header, key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
nil
end
module_function :delete_cookie_header!
+ # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
+ # of '% %b %Y'.
+ # It assumes that the time is in GMT to comply to the RFC 2109.
+ #
+ # NOTE: I'm not sure the RFC says it requires GMT, but is ambigous enough
+ # that I'm certain someone implemented only that option.
+ # Do not use %a and %b from Time.strptime, it would use localized names for
+ # weekday and month.
+ #
+ def rfc2822(time)
+ wday = Time::RFC2822_DAY_NAME[time.wday]
+ mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
+ time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
+ end
+ module_function :rfc2822
+
# Return the bytesize of String; uses String#length under Ruby 1.8 and
# String#bytesize under 1.9.
if ''.respond_to?(:bytesize)
def bytesize(string)
string.bytesize
@@ -460,10 +517,13 @@
status = input.read(boundary_size, read_buffer)
raise EOFError, "bad content body" unless status == boundary + EOL
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
+ max_key_space = Utils.key_space_limit
+ bytes = 0
+
loop {
head = nil
body = ''
filename = content_type = name = nil
@@ -473,9 +533,16 @@
buf.slice!(0, 2) # Second \r\n
filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1]
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)
end