rack 2.2.3 → 3.2.7

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 +774 -69
  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 -4
  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 +26 -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 +15 -6
  21. data/lib/rack/etag.rb +17 -21
  22. data/lib/rack/events.rb +25 -6
  23. data/lib/rack/files.rb +16 -18
  24. data/lib/rack/head.rb +8 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +863 -705
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/media_type.rb +18 -9
  29. data/lib/rack/method_override.rb +6 -2
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -271
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +156 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +349 -92
  36. data/lib/rack/multipart/uploaded_file.rb +45 -4
  37. data/lib/rack/multipart.rb +53 -41
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +150 -106
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +272 -141
  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 +70 -35
  47. data/lib/rack/show_exceptions.rb +25 -6
  48. data/lib/rack/show_status.rb +17 -9
  49. data/lib/rack/static.rb +17 -12
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +4 -2
  52. data/lib/rack/utils.rb +351 -250
  53. data/lib/rack/version.rb +3 -15
  54. data/lib/rack.rb +13 -90
  55. metadata +16 -45
  56. data/README.rdoc +0 -306
  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 -54
  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 -36
  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 -85
  87. data/rack.gemspec +0 -46
@@ -1,65 +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_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
20
- BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
21
- MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
22
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
23
- MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
24
- # Updated definitions from RFC 2231
25
- ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
26
- ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
27
- SECTION = /\*[0-9]+/
28
- REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
29
- REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
30
- EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
31
- EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
32
- EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
33
- EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
34
- EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
35
- EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
36
- EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
37
- DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
38
- 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
39
46
 
40
47
  class << self
41
48
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
42
- extract_multipart Rack::Request.new(env), params
43
- end
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
44
52
 
45
- def extract_multipart(req, params = Rack::Utils.default_query_parser)
46
- io = req.get_header(RACK_INPUT)
47
- io.rewind
48
- content_length = req.content_length
49
- 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
50
56
 
51
- tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
52
- bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
57
+ content_type = env['CONTENT_TYPE']
53
58
 
54
- info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
55
- req.set_header(RACK_TEMPFILES, info.tmp_files)
56
- 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)
57
70
  end
58
71
 
59
72
  def build_multipart(params, first = true)
60
73
  Generator.new(params, first).dump
61
74
  end
62
75
  end
63
-
64
76
  end
65
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,47 +1,79 @@
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
23
+
24
+ # QueryLimitError is for errors raised when the query provided exceeds one
25
+ # of the query parser limits.
26
+ class QueryLimitError < RangeError
27
+ include BadRequest
28
+ end
29
+
30
+ # ParamsTooDeepError is the old name for the error that is raised when params
31
+ # are recursively nested over the specified limit. Make it the same as
32
+ # as QueryLimitError, so that code that rescues ParamsTooDeepError error
33
+ # to handle bad query strings also now handles other limits.
34
+ ParamsTooDeepError = QueryLimitError
35
+
36
+ def self.make_default(param_depth_limit, **options)
37
+ new(Params, param_depth_limit, **options)
38
+ end
39
+
40
+ attr_reader :param_depth_limit
41
+
42
+ env_int = lambda do |key, val|
43
+ if str_val = ENV[key]
44
+ begin
45
+ val = Integer(str_val, 10)
46
+ rescue ArgumentError
47
+ raise ArgumentError, "non-integer value provided for environment variable #{key}"
48
+ end
49
+ end
18
50
 
19
- def self.make_default(key_space_limit, param_depth_limit)
20
- new Params, key_space_limit, param_depth_limit
51
+ val
21
52
  end
22
53
 
23
- attr_reader :key_space_limit, :param_depth_limit
54
+ BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
55
+ private_constant :BYTESIZE_LIMIT
24
56
 
25
- def initialize(params_class, key_space_limit, param_depth_limit)
57
+ PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
58
+ private_constant :PARAMS_LIMIT
59
+
60
+ attr_reader :bytesize_limit
61
+
62
+ def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
26
63
  @params_class = params_class
27
- @key_space_limit = key_space_limit
28
64
  @param_depth_limit = param_depth_limit
65
+ @bytesize_limit = bytesize_limit
66
+ @params_limit = params_limit
29
67
  end
30
68
 
31
69
  # Stolen from Mongrel, with some small modifications:
32
- # Parses a query string by breaking it up at the '&'
33
- # and ';' characters. You can also use this to parse
34
- # cookies by changing the characters used in the second
35
- # parameter (which defaults to '&;').
36
- def parse_query(qs, d = nil, &unescaper)
37
- unescaper ||= method(:unescape)
38
-
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)
39
74
  params = make_params
40
75
 
41
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
42
- next if p.empty?
43
- k, v = p.split('=', 2).map!(&unescaper)
44
-
76
+ each_query_pair(qs, separator, unescaper) do |k, v|
45
77
  if cur = params[k]
46
78
  if cur.class == Array
47
79
  params[k] << v
@@ -56,81 +88,117 @@ module Rack
56
88
  return params.to_h
57
89
  end
58
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
+
59
104
  # parse_nested_query expands a query string into structural types. Supported
60
105
  # types are Arrays, Hashes and basic value types. It is possible to supply
61
106
  # query strings with parameters of conflicting types, in this case a
62
107
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
63
108
  # case.
64
- def parse_nested_query(qs, d = nil)
109
+ def parse_nested_query(qs, separator = nil)
65
110
  params = make_params
66
111
 
67
- unless qs.nil? || qs.empty?
68
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
69
- k, v = p.split('=', 2).map! { |s| unescape(s) }
70
-
71
- normalize_params(params, k, v, param_depth_limit)
72
- end
112
+ each_query_pair(qs, separator) do |k, v|
113
+ _normalize_params(params, k, v, 0)
73
114
  end
74
115
 
75
116
  return params.to_h
76
- rescue ArgumentError => e
77
- raise InvalidParameterError, e.message, e.backtrace
78
117
  end
79
118
 
80
119
  # normalize_params recursively expands parameters into structural types. If
81
120
  # the structural types represented by two different parameter names are in
82
- # conflict, a ParameterTypeError is raised.
83
- def normalize_params(params, name, v, depth)
84
- raise RangeError if depth <= 0
85
-
86
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
87
- k = $1 || ''
88
- 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
89
127
 
90
- if k.empty?
91
- if !v.nil? && name == "[]"
92
- 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]
93
140
  else
94
- return
141
+ # Plain parameter with no nesting
142
+ k = name
143
+ after = ''
95
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 = ''
96
158
  end
97
159
 
160
+ return if k.empty?
161
+
98
162
  if after == ''
99
- params[k] = v
163
+ if k == '[]' && depth != 0
164
+ return [v]
165
+ else
166
+ params[k] = v
167
+ end
100
168
  elsif after == "["
101
169
  params[name] = v
102
170
  elsif after == "[]"
103
171
  params[k] ||= []
104
172
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
105
173
  params[k] << v
106
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
107
- 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
108
180
  params[k] ||= []
109
181
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
110
182
  if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
111
- normalize_params(params[k].last, child_key, v, depth - 1)
183
+ _normalize_params(params[k].last, child_key, v, depth + 1)
112
184
  else
113
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
185
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
114
186
  end
115
187
  else
116
188
  params[k] ||= make_params
117
189
  raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
118
- params[k] = normalize_params(params[k], after, v, depth - 1)
190
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
119
191
  end
120
192
 
121
193
  params
122
194
  end
123
195
 
124
196
  def make_params
125
- @params_class.new @key_space_limit
126
- end
127
-
128
- def new_space_limit(key_space_limit)
129
- self.class.new @params_class, key_space_limit, param_depth_limit
197
+ @params_class.new
130
198
  end
131
199
 
132
200
  def new_depth_limit(param_depth_limit)
133
- self.class.new @params_class, key_space_limit, param_depth_limit
201
+ self.class.new @params_class, param_depth_limit
134
202
  end
135
203
 
136
204
  private
@@ -151,66 +219,42 @@ module Rack
151
219
  true
152
220
  end
153
221
 
154
- def unescape(s)
155
- Utils.unescape(s)
156
- end
157
-
158
- class Params
159
- def initialize(limit)
160
- @limit = limit
161
- @size = 0
162
- @params = {}
163
- end
222
+ def each_query_pair(qs, separator, unescaper = nil)
223
+ return if !qs || qs.empty?
164
224
 
165
- def [](key)
166
- @params[key]
225
+ if qs.bytesize > @bytesize_limit
226
+ raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
167
227
  end
168
228
 
169
- def []=(key, value)
170
- @size += key.size if key && !@params.key?(key)
171
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
172
- @params[key] = value
173
- end
229
+ pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
174
230
 
175
- def key?(key)
176
- @params.key?(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})"
177
234
  end
178
235
 
179
- # Recursively unwraps nested `Params` objects and constructs an object
180
- # of the same shape, but using the objects' internal representations
181
- # (Ruby hashes) in place of the objects. The result is a hash consisting
182
- # purely of Ruby primitives.
183
- #
184
- # Mutation warning!
185
- #
186
- # 1. This method mutates the internal representation of the `Params`
187
- # objects in order to save object allocations.
188
- #
189
- # 2. The value you get back is a reference to the internal hash
190
- # representation, not a copy.
191
- #
192
- # 3. Because the `Params` object's internal representation is mutable
193
- # through the `#[]=` method, it is not thread safe. The result of
194
- # getting the hash representation while another thread is adding a
195
- # key to it is non-deterministic.
196
- #
197
- def to_h
198
- @params.each do |key, value|
199
- case value
200
- when self
201
- # Handle circular references gracefully.
202
- @params[key] = @params
203
- when Params
204
- @params[key] = value.to_h
205
- when Array
206
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
207
- else
208
- # Ignore anything that is not a `Params` object or
209
- # a collection that can contain one.
210
- end
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
211
247
  end
212
- @params
213
248
  end
249
+ rescue ArgumentError => e
250
+ raise InvalidParameterError, e.message, e.backtrace
251
+ end
252
+
253
+ def unescape(string, encoding = Encoding::UTF_8)
254
+ URI.decode_www_form_component(string, encoding)
255
+ end
256
+
257
+ class Params < Hash
214
258
  alias_method :to_params_hash, :to_h
215
259
  end
216
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