diff --git a/lib/rack/constants.rb b/lib/rack/constants.rb index 5b0c181e5..13365935b 100644 --- a/lib/rack/constants.rb +++ b/lib/rack/constants.rb @@ -54,13 +54,11 @@ module Rack RACK_RESPONSE_FINISHED = 'rack.response_finished' RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' - RACK_REQUEST_FORM_PAIRS = 'rack.request.form_pairs' RACK_REQUEST_FORM_VARS = 'rack.request.form_vars' RACK_REQUEST_FORM_ERROR = 'rack.request.form_error' RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash' RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string' RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' - RACK_REQUEST_QUERY_PAIRS = 'rack.request.query_pairs' RACK_REQUEST_QUERY_STRING = 'rack.request.query_string' RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method' end diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb index 4b02fb3e8..165b4db3a 100644 --- a/lib/rack/multipart.rb +++ b/lib/rack/multipart.rb @@ -19,31 +19,6 @@ class MissingInputError < StandardError include BadRequest end - # Accumulator for multipart form data, conforming to the QueryParser API. - # In future, the Parser could return the pair list directly, but that would - # change its API. - class ParamList # :nodoc: - def self.make_params - new - end - - def self.normalize_params(params, key, value) - params << [key, value] - end - - def initialize - @pairs = [] - end - - def <<(pair) - @pairs << pair - end - - def to_params_hash - @pairs - end - end - class << self def parse_multipart(env, params = Rack::Utils.default_query_parser) unless io = env[RACK_INPUT] diff --git a/lib/rack/query_parser.rb b/lib/rack/query_parser.rb index 1c05ae828..1592a01e3 100644 --- a/lib/rack/query_parser.rb +++ b/lib/rack/query_parser.rb @@ -37,42 +37,19 @@ def initialize(params_class, param_depth_limit) @param_depth_limit = param_depth_limit end - # Originally stolen from Mongrel, now with some modifications: + # Stolen from Mongrel, with some small modifications: # Parses a query string by breaking it up at the '&'. You can also use this # to parse cookies by changing the characters used in the second parameter # (which defaults to '&'). - # - # Returns an array of 2-element arrays, where the first element is the - # key and the second element is the value. - def split_query(qs, separator = nil, &unescaper) - pairs = [] - - if qs && !qs.empty? - unescaper ||= method(:unescape) - - qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| - next if p.empty? - pair = p.split('=', 2).map!(&unescaper) - pair << nil if pair.length == 1 - pairs << pair - end - end - - pairs - rescue ArgumentError => e - raise InvalidParameterError, e.message, e.backtrace - end - - # Parses a query string by breaking it up at the '&'. You can also use this - # to parse cookies by changing the characters used in the second parameter - # (which defaults to '&'). - # - # Returns a hash where each value is a string (when a key only appears once) - # or an array of strings (when a key appears more than once). def parse_query(qs, separator = nil, &unescaper) + unescaper ||= method(:unescape) + params = make_params - split_query(qs, separator, &unescaper).each do |k, v| + (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| + next if p.empty? + k, v = p.split('=', 2).map!(&unescaper) + if cur = params[k] if cur.class == Array params[k] << v @@ -84,7 +61,7 @@ def parse_query(qs, separator = nil, &unescaper) end end - params.to_h + return params.to_h end # parse_nested_query expands a query string into structural types. Supported @@ -95,11 +72,17 @@ def parse_query(qs, separator = nil, &unescaper) def parse_nested_query(qs, separator = nil) params = make_params - split_query(qs, separator).each do |k, v| - _normalize_params(params, k, v, 0) + unless qs.nil? || qs.empty? + (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map! { |s| unescape(s) } + + _normalize_params(params, k, v, 0) + end end - params.to_h + return params.to_h + rescue ArgumentError => e + raise InvalidParameterError, e.message, e.backtrace end # normalize_params recursively expands parameters into structural types. If diff --git a/lib/rack/request.rb b/lib/rack/request.rb index e69696450..a3eb9926a 100644 --- a/lib/rack/request.rb +++ b/lib/rack/request.rb @@ -483,22 +483,11 @@ def parseable_data? # Returns the data received in the query string. def GET if get_header(RACK_REQUEST_QUERY_STRING) == query_string - if query_hash = get_header(RACK_REQUEST_QUERY_HASH) - return query_hash - end - end - - set_header(RACK_REQUEST_QUERY_HASH, expand_params(query_param_list)) - end - - def query_param_list - if get_header(RACK_REQUEST_QUERY_STRING) == query_string - get_header(RACK_REQUEST_QUERY_PAIRS) + get_header(RACK_REQUEST_QUERY_HASH) else - query_pairs = split_query(query_string, '&') - set_header RACK_REQUEST_QUERY_STRING, query_string - set_header RACK_REQUEST_QUERY_HASH, nil - set_header(RACK_REQUEST_QUERY_PAIRS, query_pairs) + query_hash = parse_query(query_string, '&') + set_header(RACK_REQUEST_QUERY_STRING, query_string) + set_header(RACK_REQUEST_QUERY_HASH, query_hash) end end @@ -507,16 +496,6 @@ def query_param_list # This method support both application/x-www-form-urlencoded and # multipart/form-data. def POST - if get_header(RACK_REQUEST_FORM_INPUT).equal?(get_header(RACK_INPUT)) - if form_hash = get_header(RACK_REQUEST_FORM_HASH) - return form_hash - end - end - - set_header(RACK_REQUEST_FORM_HASH, expand_params(body_param_list)) - end - - def body_param_list if error = get_header(RACK_REQUEST_FORM_ERROR) raise error.class, error.message, cause: error.cause end @@ -524,36 +503,36 @@ def body_param_list begin rack_input = get_header(RACK_INPUT) - form_pairs = nil - - # If the form data has already been memoized from the same - # input: - if get_header(RACK_REQUEST_FORM_INPUT).equal?(rack_input) - if form_pairs = get_header(RACK_REQUEST_FORM_PAIRS) - return form_pairs + # If the form hash was already memoized: + if form_hash = get_header(RACK_REQUEST_FORM_HASH) + # And it was memoized from the same input: + if get_header(RACK_REQUEST_FORM_INPUT).equal?(rack_input) + return form_hash end end + # Otherwise, figure out how to parse the input: if rack_input.nil? - form_pairs = [] + set_header RACK_REQUEST_FORM_INPUT, nil + set_header(RACK_REQUEST_FORM_HASH, {}) elsif form_data? || parseable_data? - unless form_pairs = Rack::Multipart.extract_multipart(self, Rack::Multipart::ParamList) - form_vars = rack_input.read + unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart) + form_vars = get_header(RACK_INPUT).read # Fix for Safari Ajax postings that always append \0 # form_vars.sub!(/\0\z/, '') # performance replacement: form_vars.slice!(-1) if form_vars.end_with?("\0") set_header RACK_REQUEST_FORM_VARS, form_vars - form_pairs = split_query(form_vars, '&') + set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&') end + + set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) + get_header RACK_REQUEST_FORM_HASH else - form_pairs = [] + set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) + set_header(RACK_REQUEST_FORM_HASH, {}) end - - set_header RACK_REQUEST_FORM_INPUT, rack_input - set_header RACK_REQUEST_FORM_HASH, nil - set_header(RACK_REQUEST_FORM_PAIRS, form_pairs) rescue => error set_header(RACK_REQUEST_FORM_ERROR, error) raise @@ -693,28 +672,6 @@ def parse_multipart Rack::Multipart.extract_multipart(self, query_parser) end - def split_query(query, d = '&') - query_parser = query_parser() - unless query_parser.respond_to?(:split_query) - query_parser = Utils.default_query_parser - unless query_parser.respond_to?(:split_query) - query_parser = QueryParser.make_default(0) - end - end - - query_parser.split_query(query, d) - end - - def expand_params(pairs, query_parser = query_parser()) - params = query_parser.make_params - - pairs.each do |key, value| - query_parser.normalize_params(params, key, value) - end - - params.to_params_hash - end - def split_header(value) value ? value.strip.split(/[,\s]+/) : [] end diff --git a/test/spec_request.rb b/test/spec_request.rb index 2a3f792a3..9a94b35fc 100644 --- a/test/spec_request.rb +++ b/test/spec_request.rb @@ -572,12 +572,11 @@ def self.req(headers) end it "parse the query string" do - request = make_request(Rack::MockRequest.env_for("/?foo=bar&quux=bla¬hing&empty=")) - request.query_string.must_equal "foo=bar&quux=bla¬hing&empty=" - request.GET.must_equal "foo" => "bar", "quux" => "bla", "nothing" => "", "empty" => "" + request = make_request(Rack::MockRequest.env_for("/?foo=bar&quux=bla")) + request.query_string.must_equal "foo=bar&quux=bla" + request.GET.must_equal "foo" => "bar", "quux" => "bla" request.POST.must_be :empty? - request.params.must_equal "foo" => "bar", "quux" => "bla", "nothing" => "", "empty" => "" - request.query_param_list.must_equal [["foo", "bar"], ["quux", "bla"], ["nothing", nil], ["empty", ""]] + request.params.must_equal "foo" => "bar", "quux" => "bla" end it "handles invalid unicode in query string value" do