lib/rack/utils.rb in rack-1.4.7 vs lib/rack/utils.rb in rack-1.5.0.beta.1

- old
+ new

@@ -49,27 +49,16 @@ DEFAULT_SEP = /[&;] */n class << self attr_accessor :key_space_limit - attr_accessor :param_depth_limit - attr_accessor :multipart_part_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 - # Default depth at which the parameter parser will raise an exception for - # being too deep. This helps prevent SystemStackErrors - self.param_depth_limit = 100 - # - # The maximum number of parts a request can contain. Accepting to many part - # can lead to the server running out of file handles. - # Set to `0` for no limit. - self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i - # 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 '&;'). @@ -109,13 +98,11 @@ return params.to_params_hash end module_function :parse_nested_query - def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit) - raise RangeError if depth <= 0 - + def normalize_params(params, name, v = nil) name =~ %r(\A[\[\]]*([^\[\]]+)\]*) k = $1 || '' after = $' || '' return if k.empty? @@ -129,18 +116,18 @@ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) child_key = $1 params[k] ||= [] raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) if params_hash_type?(params[k].last) && !params[k].last.key?(child_key) - normalize_params(params[k].last, child_key, v, depth - 1) + normalize_params(params[k].last, child_key, v) else - params[k] << normalize_params(params.class.new, child_key, v, depth - 1) + params[k] << normalize_params(params.class.new, child_key, v) end else params[k] ||= params.class.new raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) - params[k] = normalize_params(params[k], after, v, depth - 1) + params[k] = normalize_params(params[k], after, v) end return params end module_function :normalize_params @@ -178,10 +165,35 @@ prefix end end module_function :build_nested_query + def q_values(q_value_header) + q_value_header.to_s.split(/\s*,\s*/).map do |part| + value, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if md = /\Aq=([\d.]+)/.match(parameters) + quality = md[1].to_f + end + [value, quality] + end + end + module_function :q_values + + def best_q_match(q_value_header, available_mimes) + values = q_values(q_value_header) + + values.map do |req_mime, quality| + match = available_mimes.first { |am| Rack::Mime.match?(am, req_mime) } + next unless match + [match, quality] + end.compact.sort_by do |match, quality| + (match.split('/', 2).count('*') * -10) + quality + end.last.first + end + module_function :best_q_match + ESCAPE_HTML = { "&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "'" => "&#x27;", @@ -235,10 +247,11 @@ def set_cookie_header!(header, key, value) case value when Hash domain = "; domain=" + value[:domain] if value[:domain] path = "; path=" + value[:path] if value[:path] + max_age = "; max-age=" + value[:max_age] if value[:max_age] # According to RFC 2109, we need dashes here. # N.B.: cgi.rb uses spaces... expires = "; expires=" + rfc2822(value[:expires].clone.gmtime) if value[:expires] secure = "; secure" if value[:secure] @@ -246,11 +259,11 @@ value = value[:value] end value = [value] unless Array === value cookie = escape(key) + "=" + value.map { |v| escape v }.join("&") + - "#{domain}#{path}#{expires}#{secure}#{httponly}" + "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}" case header["Set-Cookie"] when nil, '' header["Set-Cookie"] = cookie when String @@ -285,10 +298,11 @@ header["Set-Cookie"] = cookies.join("\n") set_cookie_header!(header, key, {:value => '', :path => nil, :domain => nil, + :max_age => '0', :expires => Time.at(0) }.merge(value)) nil end module_function :delete_cookie_header! @@ -353,22 +367,10 @@ end ranges end module_function :byte_ranges - # Constant time string comparison. - def secure_compare(a, b) - return false unless bytesize(a) == bytesize(b) - - l = a.unpack("C*") - - r, i = 0, -1 - b.each_byte { |v| r |= v ^ l[i+=1] } - r == 0 - end - module_function :secure_compare - # Context allows the use of a compatible middleware at different points # in a request handling stack. A compatible middleware must define # #context which should take the arguments env and app. The first of which # would be the request environment. The second of which would be the rack # application that the request would be forwarded to. @@ -496,65 +498,75 @@ end end # Every standard HTTP code mapped to the appropriate message. # Generated with: - # curl -s http://www.iana.org/assignments/http-status-codes | \ - # ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and - # puts " #{m[1]} => \x27#{m[2].strip}x27,"' + # irb -ropen-uri -rnokogiri + # > Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each{|r| + # puts "#{r.css('value').text} => '#{r.css('description').text}'"} HTTP_STATUS_CODES = { - 100 => 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 207 => 'Multi-Status', - 226 => 'IM Used', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => 'Reserved', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 418 => "I'm a Teapot", - 422 => 'Unprocessable Entity', - 423 => 'Locked', - 424 => 'Failed Dependency', - 426 => 'Upgrade Required', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - 506 => 'Variant Also Negotiates', - 507 => 'Insufficient Storage', - 510 => 'Not Extended', + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade Required', + 427 => 'Unassigned', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 430 => 'Unassigned', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 509 => 'Unassigned', + 510 => 'Not Extended', + 511 => 'Network Authentication Required' } # Responses with HTTP status codes that should not have an entity body STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)