rack 2.2.20 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +557 -70
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +384 -0
  6. data/SPEC.rdoc +243 -277
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +5 -1
  9. data/lib/rack/auth/basic.rb +1 -3
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +108 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +22 -17
  15. data/lib/rack/conditional_get.rb +20 -16
  16. data/lib/rack/constants.rb +68 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +17 -23
  22. data/lib/rack/events.rb +25 -6
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +8 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +817 -648
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/media_type.rb +6 -7
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -300
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +147 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +242 -102
  36. data/lib/rack/multipart/uploaded_file.rb +45 -4
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +116 -121
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +272 -144
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +27 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +35 -30
  47. data/lib/rack/show_exceptions.rb +25 -6
  48. data/lib/rack/show_status.rb +17 -9
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +228 -238
  53. data/lib/rack/version.rb +3 -15
  54. data/lib/rack.rb +13 -90
  55. metadata +14 -40
  56. data/README.rdoc +0 -355
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -53
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -34
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/logger.rb +0 -20
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
@@ -1,14 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
3
6
  module Rack
4
7
  module Multipart
8
+ # Despite the misleading name, UploadedFile is designed for use for
9
+ # preparing multipart file upload bodies, generally for use in tests.
10
+ # It is not designed for and should not be used for handling uploaded
11
+ # files (there is no need for that, since Rack's multipart parser
12
+ # already creates Tempfiles for that). Using this with non-trusted
13
+ # filenames can create a security vulnerability.
14
+ #
15
+ # You should only use this class if you plan on passing the instances
16
+ # to Rack::MockRequest for use in creating multipart request bodies.
17
+ #
18
+ # UploadedFile delegates most methods to the tempfile it contains.
5
19
  class UploadedFile
6
- # The filename, *not* including the path, of the "uploaded" file
20
+ # The provided name of the file. This generally is the basename of
21
+ # path provided during initialization, but it can contain slashes if they
22
+ # were present in the filename argument when the instance was created.
7
23
  attr_reader :original_filename
8
24
 
9
- # The content type of the "uploaded" file
25
+ # The content type of the instance.
10
26
  attr_accessor :content_type
11
27
 
28
+ # Create a new UploadedFile. For backwards compatibility, this accepts
29
+ # both positional and keyword versions of the same arguments:
30
+ #
31
+ # filepath/path :: The path to the file
32
+ # ct/content_type :: The content_type of the file
33
+ # bin/binary :: Whether to set binmode on the file before copying data into it.
34
+ #
35
+ # If both positional and keyword arguments are present, the keyword arguments
36
+ # take precedence.
37
+ #
38
+ # The following keyword-only arguments are also accepted:
39
+ #
40
+ # filename :: Override the filename to use for the file. This is so the
41
+ # filename for the upload does not need to match the basename of
42
+ # the file path. This should not contain slashes, unless you are
43
+ # trying to test how an application handles invalid filenames in
44
+ # multipart upload bodies.
45
+ # io :: Use the given IO-like instance as the tempfile, instead of creating
46
+ # a Tempfile instance. This is useful for building multipart file
47
+ # upload bodies without a file being present on the filesystem. If you are
48
+ # providing this, you should also provide the filename argument.
12
49
  def initialize(filepath = nil, ct = "text/plain", bin = false,
13
50
  path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
14
51
  if io
@@ -24,15 +61,19 @@ module Rack
24
61
  @content_type = content_type
25
62
  end
26
63
 
64
+ # The path of the tempfile for the instance, if the tempfile has a path.
65
+ # nil if the tempfile does not have a path.
27
66
  def path
28
67
  @tempfile.path if @tempfile.respond_to?(:path)
29
68
  end
30
69
  alias_method :local_path, :path
31
70
 
32
- def respond_to?(*args)
33
- super or @tempfile.respond_to?(*args)
71
+ # Return true if the tempfile responds to the method.
72
+ def respond_to_missing?(*args)
73
+ @tempfile.respond_to?(*args)
34
74
  end
35
75
 
76
+ # Delegate method missing calls to the tempfile.
36
77
  def method_missing(method_name, *args, &block) #:nodoc:
37
78
  @tempfile.__send__(method_name, *args, &block)
38
79
  end
@@ -1,64 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  require_relative 'multipart/parser'
7
+ require_relative 'multipart/generator'
8
+
9
+ require_relative 'bad_request'
4
10
 
5
11
  module Rack
6
12
  # A multipart form data parser, adapted from IOWA.
7
13
  #
8
14
  # Usually, Rack::Request#POST takes care of calling this.
9
15
  module Multipart
10
- autoload :UploadedFile, 'rack/multipart/uploaded_file'
11
- autoload :Generator, 'rack/multipart/generator'
12
-
13
- EOL = "\r\n"
14
16
  MULTIPART_BOUNDARY = "AaB03x"
15
- MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
16
- TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
17
- CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
18
- VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
19
- BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
20
- MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
21
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
22
- MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
23
- # Updated definitions from RFC 2231
24
- ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
- ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
26
- SECTION = /\*[0-9]+/
27
- REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
28
- REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
29
- EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
30
- EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
31
- EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
32
- EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
33
- EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
34
- EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
35
- EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
36
- DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
37
- RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
17
+
18
+ class MissingInputError < StandardError
19
+ include BadRequest
20
+ end
21
+
22
+ # Accumulator for multipart form data, conforming to the QueryParser API.
23
+ # In future, the Parser could return the pair list directly, but that would
24
+ # change its API.
25
+ class ParamList # :nodoc:
26
+ def self.make_params
27
+ new
28
+ end
29
+
30
+ def self.normalize_params(params, key, value)
31
+ params << [key, value]
32
+ end
33
+
34
+ def initialize
35
+ @pairs = []
36
+ end
37
+
38
+ def <<(pair)
39
+ @pairs << pair
40
+ end
41
+
42
+ def to_params_hash
43
+ @pairs
44
+ end
45
+ end
38
46
 
39
47
  class << self
40
48
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
41
- extract_multipart Rack::Request.new(env), params
42
- end
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
43
52
 
44
- def extract_multipart(req, params = Rack::Utils.default_query_parser)
45
- io = req.get_header(RACK_INPUT)
46
- io.rewind
47
- content_length = req.content_length
48
- content_length = content_length.to_i if content_length
53
+ if content_length = env['CONTENT_LENGTH']
54
+ content_length = content_length.to_i
55
+ end
49
56
 
50
- tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
51
- bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
57
+ content_type = env['CONTENT_TYPE']
52
58
 
53
- info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
54
- req.set_header(RACK_TEMPFILES, info.tmp_files)
55
- info.params
59
+ tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
60
+ bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
61
+
62
+ info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
63
+ env[RACK_TEMPFILES] = info.tmp_files
64
+
65
+ return info.params
66
+ end
67
+
68
+ def extract_multipart(request, params = Rack::Utils.default_query_parser)
69
+ parse_multipart(request.env)
56
70
  end
57
71
 
58
72
  def build_multipart(params, first = true)
59
73
  Generator.new(params, first).dump
60
74
  end
61
75
  end
62
-
63
76
  end
64
77
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+
3
5
  module Rack
4
6
  class NullLogger
5
7
  def initialize(app)
@@ -22,6 +24,11 @@ module Rack
22
24
  def warn? ; end
23
25
  def error? ; end
24
26
  def fatal? ; end
27
+ def debug! ; end
28
+ def error! ; end
29
+ def fatal! ; end
30
+ def info! ; end
31
+ def warn! ; end
25
32
  def level ; end
26
33
  def progname ; end
27
34
  def datetime_format ; end
@@ -34,6 +41,8 @@ module Rack
34
41
  def sev_threshold=(sev_threshold); end
35
42
  def close ; end
36
43
  def add(severity, message = nil, progname = nil, &block); end
44
+ def log(severity, message = nil, progname = nil, &block); end
37
45
  def <<(msg); end
46
+ def reopen(logdev = nil); end
38
47
  end
39
48
  end
@@ -1,24 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'bad_request'
4
+ require 'uri'
5
+
3
6
  module Rack
4
7
  class QueryParser
5
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
6
-
7
- DEFAULT_SEP = /[&;] */n
8
- COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
8
+ DEFAULT_SEP = /& */n
9
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
9
10
 
10
11
  # ParameterTypeError is the error that is raised when incoming structural
11
12
  # parameters (parsed by parse_nested_query) contain conflicting types.
12
- class ParameterTypeError < TypeError; end
13
+ class ParameterTypeError < TypeError
14
+ include BadRequest
15
+ end
13
16
 
14
17
  # InvalidParameterError is the error that is raised when incoming structural
15
18
  # parameters (parsed by parse_nested_query) contain invalid format or byte
16
19
  # sequence.
17
- class InvalidParameterError < ArgumentError; end
20
+ class InvalidParameterError < ArgumentError
21
+ include BadRequest
22
+ end
18
23
 
19
24
  # QueryLimitError is for errors raised when the query provided exceeds one
20
25
  # of the query parser limits.
21
26
  class QueryLimitError < RangeError
27
+ include BadRequest
22
28
  end
23
29
 
24
30
  # ParamsTooDeepError is the old name for the error that is raised when params
@@ -27,11 +33,11 @@ module Rack
27
33
  # to handle bad query strings also now handles other limits.
28
34
  ParamsTooDeepError = QueryLimitError
29
35
 
30
- def self.make_default(key_space_limit, param_depth_limit, **options)
31
- new(Params, key_space_limit, param_depth_limit, **options)
36
+ def self.make_default(param_depth_limit, **options)
37
+ new(Params, param_depth_limit, **options)
32
38
  end
33
39
 
34
- attr_reader :key_space_limit, :param_depth_limit
40
+ attr_reader :param_depth_limit
35
41
 
36
42
  env_int = lambda do |key, val|
37
43
  if str_val = ENV[key]
@@ -53,28 +59,21 @@ module Rack
53
59
 
54
60
  attr_reader :bytesize_limit
55
61
 
56
- def initialize(params_class, key_space_limit, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
62
+ def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
57
63
  @params_class = params_class
58
- @key_space_limit = key_space_limit
59
64
  @param_depth_limit = param_depth_limit
60
65
  @bytesize_limit = bytesize_limit
61
66
  @params_limit = params_limit
62
67
  end
63
68
 
64
69
  # Stolen from Mongrel, with some small modifications:
65
- # Parses a query string by breaking it up at the '&'
66
- # and ';' characters. You can also use this to parse
67
- # cookies by changing the characters used in the second
68
- # parameter (which defaults to '&;').
69
- def parse_query(qs, d = nil, &unescaper)
70
- unescaper ||= method(:unescape)
71
-
70
+ # Parses a query string by breaking it up at the '&'. You can also use this
71
+ # to parse cookies by changing the characters used in the second parameter
72
+ # (which defaults to '&').
73
+ def parse_query(qs, separator = nil, &unescaper)
72
74
  params = make_params
73
75
 
74
- check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
75
- next if p.empty?
76
- k, v = p.split('=', 2).map!(&unescaper)
77
-
76
+ each_query_pair(qs, separator, unescaper) do |k, v|
78
77
  if cur = params[k]
79
78
  if cur.class == Array
80
79
  params[k] << v
@@ -89,81 +88,117 @@ module Rack
89
88
  return params.to_h
90
89
  end
91
90
 
91
+ # Parses a query string by breaking it up at the '&', returning all key-value
92
+ # pairs as an array of [key, value] arrays. Unlike parse_query, this preserves
93
+ # all duplicate keys rather than collapsing them.
94
+ def parse_query_pairs(qs, separator = nil)
95
+ pairs = []
96
+
97
+ each_query_pair(qs, separator) do |k, v|
98
+ pairs << [k, v]
99
+ end
100
+
101
+ pairs
102
+ end
103
+
92
104
  # parse_nested_query expands a query string into structural types. Supported
93
105
  # types are Arrays, Hashes and basic value types. It is possible to supply
94
106
  # query strings with parameters of conflicting types, in this case a
95
107
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
96
108
  # case.
97
- def parse_nested_query(qs, d = nil)
109
+ def parse_nested_query(qs, separator = nil)
98
110
  params = make_params
99
111
 
100
- unless qs.nil? || qs.empty?
101
- check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
102
- k, v = p.split('=', 2).map! { |s| unescape(s) }
103
-
104
- normalize_params(params, k, v, param_depth_limit)
105
- end
112
+ each_query_pair(qs, separator) do |k, v|
113
+ _normalize_params(params, k, v, 0)
106
114
  end
107
115
 
108
116
  return params.to_h
109
- rescue ArgumentError => e
110
- raise InvalidParameterError, e.message, e.backtrace
111
117
  end
112
118
 
113
119
  # normalize_params recursively expands parameters into structural types. If
114
120
  # the structural types represented by two different parameter names are in
115
- # conflict, a ParameterTypeError is raised.
116
- def normalize_params(params, name, v, depth)
117
- raise ParamsTooDeepError if depth <= 0
118
-
119
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
120
- k = $1 || ''
121
- after = $' || ''
121
+ # conflict, a ParameterTypeError is raised. The depth argument is deprecated
122
+ # and should no longer be used, it is kept for backwards compatibility with
123
+ # earlier versions of rack.
124
+ def normalize_params(params, name, v, _depth=nil)
125
+ _normalize_params(params, name, v, 0)
126
+ end
122
127
 
123
- if k.empty?
124
- if !v.nil? && name == "[]"
125
- return Array(v)
128
+ private def _normalize_params(params, name, v, depth)
129
+ raise ParamsTooDeepError if depth >= param_depth_limit
130
+
131
+ if !name
132
+ # nil name, treat same as empty string (required by tests)
133
+ k = after = ''
134
+ elsif depth == 0
135
+ # Start of parsing, don't treat [] or [ at start of string specially
136
+ if start = name.index('[', 1)
137
+ # Start of parameter nesting, use part before brackets as key
138
+ k = name[0, start]
139
+ after = name[start, name.length]
126
140
  else
127
- return
141
+ # Plain parameter with no nesting
142
+ k = name
143
+ after = ''
128
144
  end
145
+ elsif name.start_with?('[]')
146
+ # Array nesting
147
+ k = '[]'
148
+ after = name[2, name.length]
149
+ elsif name.start_with?('[') && (start = name.index(']', 1))
150
+ # Hash nesting, use the part inside brackets as the key
151
+ k = name[1, start-1]
152
+ after = name[start+1, name.length]
153
+ else
154
+ # Probably malformed input, nested but not starting with [
155
+ # treat full name as key for backwards compatibility.
156
+ k = name
157
+ after = ''
129
158
  end
130
159
 
160
+ return if k.empty?
161
+
131
162
  if after == ''
132
- params[k] = v
163
+ if k == '[]' && depth != 0
164
+ return [v]
165
+ else
166
+ params[k] = v
167
+ end
133
168
  elsif after == "["
134
169
  params[name] = v
135
170
  elsif after == "[]"
136
171
  params[k] ||= []
137
172
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
138
173
  params[k] << v
139
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
140
- child_key = $1
174
+ elsif after.start_with?('[]')
175
+ # Recognize x[][y] (hash inside array) parameters
176
+ unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
177
+ # Handle other nested array parameters
178
+ child_key = after[2, after.length]
179
+ end
141
180
  params[k] ||= []
142
181
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
143
182
  if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
144
- normalize_params(params[k].last, child_key, v, depth - 1)
183
+ _normalize_params(params[k].last, child_key, v, depth + 1)
145
184
  else
146
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
185
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
147
186
  end
148
187
  else
149
188
  params[k] ||= make_params
150
189
  raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
151
- params[k] = normalize_params(params[k], after, v, depth - 1)
190
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
152
191
  end
153
192
 
154
193
  params
155
194
  end
156
195
 
157
196
  def make_params
158
- @params_class.new @key_space_limit
159
- end
160
-
161
- def new_space_limit(key_space_limit)
162
- self.class.new @params_class, key_space_limit, param_depth_limit
197
+ @params_class.new
163
198
  end
164
199
 
165
200
  def new_depth_limit(param_depth_limit)
166
- self.class.new @params_class, key_space_limit, param_depth_limit
201
+ self.class.new @params_class, param_depth_limit
167
202
  end
168
203
 
169
204
  private
@@ -184,82 +219,42 @@ module Rack
184
219
  true
185
220
  end
186
221
 
187
- def check_query_string(qs, sep)
188
- if qs
189
- if qs.bytesize > @bytesize_limit
190
- raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
191
- end
192
-
193
- if (param_count = qs.count(sep.is_a?(String) ? sep : '&;')) >= @params_limit
194
- raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})"
195
- end
222
+ def each_query_pair(qs, separator, unescaper = nil)
223
+ return if !qs || qs.empty?
196
224
 
197
- qs
198
- else
199
- ''
225
+ if qs.bytesize > @bytesize_limit
226
+ raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
200
227
  end
201
- end
202
-
203
- def unescape(string)
204
- Utils.unescape(string)
205
- end
206
228
 
207
- class Params
208
- def initialize(limit)
209
- @limit = limit
210
- @size = 0
211
- @params = {}
212
- end
229
+ pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
213
230
 
214
- def [](key)
215
- @params[key]
231
+ if pairs.size > @params_limit
232
+ param_count = pairs.size + pairs.last.count(separator || "&")
233
+ raise QueryLimitError, "total number of query parameters (#{param_count}) exceeds limit (#{@params_limit})"
216
234
  end
217
235
 
218
- def []=(key, value)
219
- @size += key.size if key && !@params.key?(key)
220
- raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
221
- @params[key] = value
236
+ if unescaper
237
+ pairs.each do |p|
238
+ next if p.empty?
239
+ k, v = p.split('=', 2).map!(&unescaper)
240
+ yield k, v
241
+ end
242
+ else
243
+ pairs.each do |p|
244
+ next if p.empty?
245
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
246
+ yield k, v
247
+ end
222
248
  end
249
+ rescue ArgumentError => e
250
+ raise InvalidParameterError, e.message, e.backtrace
251
+ end
223
252
 
224
- def key?(key)
225
- @params.key?(key)
226
- end
253
+ def unescape(string, encoding = Encoding::UTF_8)
254
+ URI.decode_www_form_component(string, encoding)
255
+ end
227
256
 
228
- # Recursively unwraps nested `Params` objects and constructs an object
229
- # of the same shape, but using the objects' internal representations
230
- # (Ruby hashes) in place of the objects. The result is a hash consisting
231
- # purely of Ruby primitives.
232
- #
233
- # Mutation warning!
234
- #
235
- # 1. This method mutates the internal representation of the `Params`
236
- # objects in order to save object allocations.
237
- #
238
- # 2. The value you get back is a reference to the internal hash
239
- # representation, not a copy.
240
- #
241
- # 3. Because the `Params` object's internal representation is mutable
242
- # through the `#[]=` method, it is not thread safe. The result of
243
- # getting the hash representation while another thread is adding a
244
- # key to it is non-deterministic.
245
- #
246
- def to_h
247
- @params.each do |key, value|
248
- case value
249
- when self
250
- # Handle circular references gracefully.
251
- @params[key] = @params
252
- when Params
253
- @params[key] = value.to_h
254
- when Array
255
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
256
- else
257
- # Ignore anything that is not a `Params` object or
258
- # a collection that can contain one.
259
- end
260
- end
261
- @params
262
- end
257
+ class Params < Hash
263
258
  alias_method :to_params_hash, :to_h
264
259
  end
265
260
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
7
9
  # the current request to the app at +url+.
data/lib/rack/reloader.rb CHANGED
@@ -22,8 +22,6 @@ module Rack
22
22
  # It is performing a check/reload cycle at the start of every request, but
23
23
  # also respects a cool down time, during which nothing will be done.
24
24
  class Reloader
25
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
26
-
27
25
  def initialize(app, cooldown = 10, backend = Stat)
28
26
  @app = app
29
27
  @cooldown = cooldown