rack 2.1.4.4 → 2.2.17

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +713 -10
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +109 -38
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +35 -6
  7. data/lib/rack/auth/abstract/request.rb +0 -2
  8. data/lib/rack/auth/basic.rb +4 -5
  9. data/lib/rack/auth/digest/md5.rb +4 -4
  10. data/lib/rack/auth/digest/nonce.rb +2 -3
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +77 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +27 -19
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +45 -35
  21. data/lib/rack/directory.rb +77 -60
  22. data/lib/rack/etag.rb +4 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -1
  25. data/lib/rack/files.rb +96 -56
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +211 -179
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/media_type.rb +17 -7
  37. data/lib/rack/method_override.rb +1 -1
  38. data/lib/rack/mock.rb +54 -7
  39. data/lib/rack/multipart/generator.rb +11 -6
  40. data/lib/rack/multipart/parser.rb +17 -17
  41. data/lib/rack/multipart/uploaded_file.rb +13 -7
  42. data/lib/rack/multipart.rb +1 -1
  43. data/lib/rack/query_parser.rb +62 -16
  44. data/lib/rack/recursive.rb +1 -1
  45. data/lib/rack/reloader.rb +1 -3
  46. data/lib/rack/request.rb +184 -78
  47. data/lib/rack/response.rb +62 -19
  48. data/lib/rack/rewindable_input.rb +0 -1
  49. data/lib/rack/runtime.rb +3 -3
  50. data/lib/rack/sendfile.rb +1 -4
  51. data/lib/rack/server.rb +9 -8
  52. data/lib/rack/session/abstract/id.rb +21 -18
  53. data/lib/rack/session/cookie.rb +4 -6
  54. data/lib/rack/session/pool.rb +7 -2
  55. data/lib/rack/show_exceptions.rb +6 -8
  56. data/lib/rack/show_status.rb +5 -7
  57. data/lib/rack/static.rb +15 -7
  58. data/lib/rack/tempfile_reaper.rb +0 -2
  59. data/lib/rack/urlmap.rb +2 -5
  60. data/lib/rack/utils.rb +69 -58
  61. data/lib/rack/version.rb +29 -0
  62. data/lib/rack.rb +7 -16
  63. data/rack.gemspec +31 -29
  64. metadata +12 -16
data/lib/rack/lobster.rb CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  require 'zlib'
4
4
 
5
- require 'rack/request'
6
- require 'rack/response'
7
-
8
5
  module Rack
9
6
  # Paste has a Pony, Rack has a Lobster!
10
7
  class Lobster
@@ -64,9 +61,10 @@ module Rack
64
61
  end
65
62
 
66
63
  if $0 == __FILE__
67
- require 'rack'
68
- require 'rack/show_exceptions'
64
+ # :nocov:
65
+ require_relative '../rack'
69
66
  Rack::Server.start(
70
67
  app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
71
68
  )
69
+ # :nocov:
72
70
  end
data/lib/rack/lock.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thread'
4
- require 'rack/body_proxy'
5
4
 
6
5
  module Rack
7
6
  # Rack::Lock locks every request inside a mutex, so that every request
@@ -4,7 +4,7 @@ module Rack
4
4
  # Rack::MediaType parse media type and parameters out of content_type string
5
5
 
6
6
  class MediaType
7
- SPLIT_PATTERN = %r{\s*[;,]\s*}
7
+ SPLIT_PATTERN = /[;,]/
8
8
 
9
9
  class << self
10
10
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -15,7 +15,11 @@ module Rack
15
15
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
16
16
  def type(content_type)
17
17
  return nil unless content_type
18
- content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
18
+ if type = content_type.split(SPLIT_PATTERN, 2).first
19
+ type.rstrip!
20
+ type.downcase!
21
+ type
22
+ end
19
23
  end
20
24
 
21
25
  # The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -23,21 +27,27 @@ module Rack
23
27
  # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
24
28
  # this method responds with the following Hash:
25
29
  # { 'charset' => 'utf-8' }
30
+ #
31
+ # This will pass back parameters with empty strings in the hash if they
32
+ # lack a value (e.g., "text/plain;charset=" will return { 'charset' => '' },
33
+ # and "text/plain;charset" will return { 'charset' => '' }, similarly to
34
+ # the query params parser (barring the latter case, which returns nil instead)).
26
35
  def params(content_type)
27
36
  return {} if content_type.nil?
28
37
 
29
38
  content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
39
+ s.strip!
30
40
  k, v = s.split('=', 2)
31
-
32
- hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
41
+ k.downcase!
42
+ hsh[k] = strip_doublequotes(v)
33
43
  end
34
44
  end
35
45
 
36
46
  private
37
47
 
38
- def strip_doublequotes(str)
39
- (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
40
- end
48
+ def strip_doublequotes(str)
49
+ (str && str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str || ''
50
+ end
41
51
  end
42
52
  end
43
53
  end
@@ -43,7 +43,7 @@ module Rack
43
43
 
44
44
  def method_override_param(req)
45
45
  req.POST[METHOD_OVERRIDE_PARAM_KEY]
46
- rescue Utils::InvalidParameterError, Utils::ParameterTypeError
46
+ rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError
47
47
  req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
48
48
  rescue EOFError
49
49
  req.get_header(RACK_ERRORS).puts "Bad request content body"
data/lib/rack/mock.rb CHANGED
@@ -2,11 +2,7 @@
2
2
 
3
3
  require 'uri'
4
4
  require 'stringio'
5
- require 'rack'
6
- require 'rack/lint'
7
- require 'rack/utils'
8
- require 'rack/response'
9
- require 'cgi/cookie'
5
+ require_relative '../rack'
10
6
 
11
7
  module Rack
12
8
  # Rack::MockRequest helps testing your Rack application without
@@ -56,14 +52,24 @@ module Rack
56
52
  @app = app
57
53
  end
58
54
 
55
+ # Make a GET request and return a MockResponse. See #request.
59
56
  def get(uri, opts = {}) request(GET, uri, opts) end
57
+ # Make a POST request and return a MockResponse. See #request.
60
58
  def post(uri, opts = {}) request(POST, uri, opts) end
59
+ # Make a PUT request and return a MockResponse. See #request.
61
60
  def put(uri, opts = {}) request(PUT, uri, opts) end
61
+ # Make a PATCH request and return a MockResponse. See #request.
62
62
  def patch(uri, opts = {}) request(PATCH, uri, opts) end
63
+ # Make a DELETE request and return a MockResponse. See #request.
63
64
  def delete(uri, opts = {}) request(DELETE, uri, opts) end
65
+ # Make a HEAD request and return a MockResponse. See #request.
64
66
  def head(uri, opts = {}) request(HEAD, uri, opts) end
67
+ # Make an OPTIONS request and return a MockResponse. See #request.
65
68
  def options(uri, opts = {}) request(OPTIONS, uri, opts) end
66
69
 
70
+ # Make a request using the given request method for the given
71
+ # uri to the rack application and return a MockResponse.
72
+ # Options given are passed to MockRequest.env_for.
67
73
  def request(method = GET, uri = "", opts = {})
68
74
  env = self.class.env_for(uri, opts.merge(method: method))
69
75
 
@@ -88,6 +94,13 @@ module Rack
88
94
  end
89
95
 
90
96
  # Return the Rack environment used for a request to +uri+.
97
+ # All options that are strings are added to the returned environment.
98
+ # Options:
99
+ # :fatal :: Whether to raise an exception if request outputs to rack.errors
100
+ # :input :: The rack.input to set
101
+ # :method :: The HTTP request method to use
102
+ # :params :: The params to use
103
+ # :script_name :: The SCRIPT_NAME to set
91
104
  def self.env_for(uri = "", opts = {})
92
105
  uri = parse_uri_rfc2396(uri)
93
106
  uri.path = "/#{uri.path}" unless uri.path[0] == ?/
@@ -157,6 +170,40 @@ module Rack
157
170
  # MockRequest.
158
171
 
159
172
  class MockResponse < Rack::Response
173
+ begin
174
+ # Recent versions of the CGI gem may not provide `CGI::Cookie`.
175
+ require 'cgi/cookie'
176
+ Cookie = CGI::Cookie
177
+ rescue LoadError
178
+ class Cookie
179
+ attr_reader :name, :value, :path, :domain, :expires, :secure
180
+
181
+ def initialize(args)
182
+ @name = args["name"]
183
+ @value = args["value"]
184
+ @path = args["path"]
185
+ @domain = args["domain"]
186
+ @expires = args["expires"]
187
+ @secure = args["secure"]
188
+ end
189
+
190
+ def method_missing(method_name, *args, &block)
191
+ @value.send(method_name, *args, &block)
192
+ end
193
+ # :nocov:
194
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
195
+ # :nocov:
196
+
197
+ def respond_to_missing?(method_name, include_all = false)
198
+ @value.respond_to?(method_name, include_all) || super
199
+ end
200
+ end
201
+ end
202
+
203
+ class << self
204
+ alias [] new
205
+ end
206
+
160
207
  # Headers
161
208
  attr_reader :original_headers, :cookies
162
209
 
@@ -218,7 +265,7 @@ module Rack
218
265
  set_cookie_header.split("\n").each do |cookie|
219
266
  cookie_name, cookie_filling = cookie.split('=', 2)
220
267
  cookie_attributes = identify_cookie_attributes cookie_filling
221
- parsed_cookie = CGI::Cookie.new(
268
+ parsed_cookie = Cookie.new(
222
269
  'name' => cookie_name.strip,
223
270
  'value' => cookie_attributes.fetch('value'),
224
271
  'path' => cookie_attributes.fetch('path', nil),
@@ -235,7 +282,7 @@ module Rack
235
282
  def identify_cookie_attributes(cookie_filling)
236
283
  cookie_bits = cookie_filling.split(';')
237
284
  cookie_attributes = Hash.new
238
- cookie_attributes.store('value', cookie_bits[0].strip)
285
+ cookie_attributes.store('value', Array(cookie_bits[0].strip))
239
286
  cookie_bits.each do |bit|
240
287
  if bit.include? '='
241
288
  cookie_attribute, attribute_value = bit.split('=')
@@ -17,9 +17,13 @@ module Rack
17
17
 
18
18
  flattened_params.map do |name, file|
19
19
  if file.respond_to?(:original_filename)
20
- ::File.open(file.path, 'rb') do |f|
21
- f.set_encoding(Encoding::BINARY)
22
- content_for_tempfile(f, file, name)
20
+ if file.path
21
+ ::File.open(file.path, 'rb') do |f|
22
+ f.set_encoding(Encoding::BINARY)
23
+ content_for_tempfile(f, file, name)
24
+ end
25
+ else
26
+ content_for_tempfile(file, file, name)
23
27
  end
24
28
  else
25
29
  content_for_other(file, name)
@@ -69,12 +73,13 @@ module Rack
69
73
  end
70
74
 
71
75
  def content_for_tempfile(io, file, name)
76
+ length = ::File.stat(file.path).size if file.path
77
+ filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
72
78
  <<-EOF
73
79
  --#{MULTIPART_BOUNDARY}\r
74
- Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
80
+ Content-Disposition: form-data; name="#{name}"#{filename}\r
75
81
  Content-Type: #{file.content_type}\r
76
- Content-Length: #{::File.stat(file.path).size}\r
77
- \r
82
+ #{"Content-Length: #{length}\r\n" if length}\r
78
83
  #{io.read}\r
79
84
  EOF
80
85
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
4
3
  require 'strscan'
5
- require 'rack/core_ext/regexp'
6
4
 
7
5
  module Rack
8
6
  module Multipart
@@ -10,12 +8,14 @@ module Rack
10
8
  class MultipartTotalPartLimitError < StandardError; end
11
9
 
12
10
  class Parser
13
- using ::Rack::RegexpExtensions
11
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
14
12
 
15
13
  BUFSIZE = 1_048_576
16
14
  TEXT_PLAIN = "text/plain"
17
15
  TEMPFILE_FACTORY = lambda { |filename, content_type|
18
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
16
+ extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
17
+
18
+ Tempfile.new(["RackMultipart", extension])
19
19
  }
20
20
 
21
21
  BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
@@ -102,12 +102,6 @@ module Rack
102
102
 
103
103
  data = { filename: fn, type: content_type,
104
104
  name: name, tempfile: body, head: head }
105
- elsif !filename && content_type && body.is_a?(IO)
106
- body.rewind
107
-
108
- # Generic multipart cases, not coming from a form
109
- data = { type: content_type,
110
- name: name, tempfile: body, head: head }
111
105
  end
112
106
 
113
107
  yield data
@@ -126,7 +120,7 @@ module Rack
126
120
 
127
121
  include Enumerable
128
122
 
129
- def initialize tempfile
123
+ def initialize(tempfile)
130
124
  @tempfile = tempfile
131
125
  @mime_parts = []
132
126
  @open_files = 0
@@ -136,7 +130,7 @@ module Rack
136
130
  @mime_parts.each { |part| yield part }
137
131
  end
138
132
 
139
- def on_mime_head mime_index, head, filename, content_type, name
133
+ def on_mime_head(mime_index, head, filename, content_type, name)
140
134
  if filename
141
135
  body = @tempfile.call(filename, content_type)
142
136
  body.binmode if body.respond_to?(:binmode)
@@ -152,11 +146,11 @@ module Rack
152
146
  check_part_limits
153
147
  end
154
148
 
155
- def on_mime_body mime_index, content
149
+ def on_mime_body(mime_index, content)
156
150
  @mime_parts[mime_index].body << content
157
151
  end
158
152
 
159
- def on_mime_finish mime_index
153
+ def on_mime_finish(mime_index)
160
154
  end
161
155
 
162
156
  private
@@ -197,11 +191,12 @@ module Rack
197
191
 
198
192
  @sbuf = StringScanner.new("".dup)
199
193
  @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
194
+ @end_boundary_size = boundary.bytesize + 6 # (-- at start, -- at finish, EOL at end)
200
195
  @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
201
196
  @head_regex = /(.*?#{EOL})#{EOL}/m
202
197
  end
203
198
 
204
- def on_read content
199
+ def on_read(content)
205
200
  handle_empty_content!(content)
206
201
  @sbuf.concat content
207
202
  run_parser
@@ -237,7 +232,12 @@ module Rack
237
232
  end
238
233
 
239
234
  def handle_fast_forward
240
- if consume_boundary
235
+ tok = consume_boundary
236
+
237
+ if tok == :END_BOUNDARY && @sbuf.pos == @end_boundary_size && @sbuf.eos?
238
+ # stop parsing a buffer if a buffer is only an end boundary.
239
+ @state = :DONE
240
+ elsif tok
241
241
  @state = :MIME_HEAD
242
242
  else
243
243
  raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
@@ -359,7 +359,7 @@ module Rack
359
359
  type_subtype = list.first
360
360
  type_subtype.strip!
361
361
  if TEXT_PLAIN == type_subtype
362
- rest = list.drop 1
362
+ rest = list.drop 1
363
363
  rest.each do |param|
364
364
  k, v = param.split('=', 2)
365
365
  k.strip!
@@ -9,17 +9,23 @@ module Rack
9
9
  # The content type of the "uploaded" file
10
10
  attr_accessor :content_type
11
11
 
12
- def initialize(path, content_type = "text/plain", binary = false)
13
- raise "#{path} file does not exist" unless ::File.exist?(path)
12
+ def initialize(filepath = nil, ct = "text/plain", bin = false,
13
+ path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
14
+ if io
15
+ @tempfile = io
16
+ @original_filename = filename
17
+ else
18
+ raise "#{path} file does not exist" unless ::File.exist?(path)
19
+ @original_filename = filename || ::File.basename(path)
20
+ @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
21
+ @tempfile.binmode if binary
22
+ FileUtils.copy_file(path, @tempfile.path)
23
+ end
14
24
  @content_type = content_type
15
- @original_filename = ::File.basename(path)
16
- @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
17
- @tempfile.binmode if binary
18
- FileUtils.copy_file(path, @tempfile.path)
19
25
  end
20
26
 
21
27
  def path
22
- @tempfile.path
28
+ @tempfile.path if @tempfile.respond_to?(:path)
23
29
  end
24
30
  alias_method :local_path, :path
25
31
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/multipart/parser'
3
+ require_relative 'multipart/parser'
4
4
 
5
5
  module Rack
6
6
  # A multipart form data parser, adapted from IOWA.
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'core_ext/regexp'
4
-
5
3
  module Rack
6
4
  class QueryParser
7
- using ::Rack::RegexpExtensions
5
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
8
6
 
9
7
  DEFAULT_SEP = /[&;] */n
10
8
  COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
@@ -18,16 +16,47 @@ module Rack
18
16
  # sequence.
19
17
  class InvalidParameterError < ArgumentError; end
20
18
 
21
- def self.make_default(key_space_limit, param_depth_limit)
22
- new Params, key_space_limit, param_depth_limit
19
+ # QueryLimitError is for errors raised when the query provided exceeds one
20
+ # of the query parser limits.
21
+ class QueryLimitError < RangeError
22
+ end
23
+
24
+ # ParamsTooDeepError is the old name for the error that is raised when params
25
+ # are recursively nested over the specified limit. Make it the same as
26
+ # as QueryLimitError, so that code that rescues ParamsTooDeepError error
27
+ # to handle bad query strings also now handles other limits.
28
+ ParamsTooDeepError = QueryLimitError
29
+
30
+ def self.make_default(key_space_limit, param_depth_limit, **options)
31
+ new(Params, key_space_limit, param_depth_limit, **options)
23
32
  end
24
33
 
25
34
  attr_reader :key_space_limit, :param_depth_limit
26
35
 
27
- def initialize(params_class, key_space_limit, param_depth_limit)
36
+ env_int = lambda do |key, val|
37
+ if str_val = ENV[key]
38
+ begin
39
+ val = Integer(str_val, 10)
40
+ rescue ArgumentError
41
+ raise ArgumentError, "non-integer value provided for environment variable #{key}"
42
+ end
43
+ end
44
+
45
+ val
46
+ end
47
+
48
+ BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
49
+ private_constant :BYTESIZE_LIMIT
50
+
51
+ PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
52
+ private_constant :PARAMS_LIMIT
53
+
54
+ def initialize(params_class, key_space_limit, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
28
55
  @params_class = params_class
29
56
  @key_space_limit = key_space_limit
30
57
  @param_depth_limit = param_depth_limit
58
+ @bytesize_limit = bytesize_limit
59
+ @params_limit = params_limit
31
60
  end
32
61
 
33
62
  # Stolen from Mongrel, with some small modifications:
@@ -40,7 +69,7 @@ module Rack
40
69
 
41
70
  params = make_params
42
71
 
43
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
72
+ check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
44
73
  next if p.empty?
45
74
  k, v = p.split('=', 2).map!(&unescaper)
46
75
 
@@ -64,25 +93,26 @@ module Rack
64
93
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
65
94
  # case.
66
95
  def parse_nested_query(qs, d = nil)
67
- return {} if qs.nil? || qs.empty?
68
96
  params = make_params
69
97
 
70
- qs.split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
71
- k, v = p.split('=', 2).map! { |s| unescape(s) }
98
+ unless qs.nil? || qs.empty?
99
+ check_query_string(qs, d).split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
100
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
72
101
 
73
- normalize_params(params, k, v, param_depth_limit)
102
+ normalize_params(params, k, v, param_depth_limit)
103
+ end
74
104
  end
75
105
 
76
106
  return params.to_h
77
107
  rescue ArgumentError => e
78
- raise InvalidParameterError, e.message
108
+ raise InvalidParameterError, e.message, e.backtrace
79
109
  end
80
110
 
81
111
  # normalize_params recursively expands parameters into structural types. If
82
112
  # the structural types represented by two different parameter names are in
83
113
  # conflict, a ParameterTypeError is raised.
84
114
  def normalize_params(params, name, v, depth)
85
- raise RangeError if depth <= 0
115
+ raise ParamsTooDeepError if depth <= 0
86
116
 
87
117
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
88
118
  k = $1 || ''
@@ -152,8 +182,24 @@ module Rack
152
182
  true
153
183
  end
154
184
 
155
- def unescape(s)
156
- Utils.unescape(s)
185
+ def check_query_string(qs, sep)
186
+ if qs
187
+ if qs.bytesize > @bytesize_limit
188
+ raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
189
+ end
190
+
191
+ if (param_count = qs.count(sep.is_a?(String) ? sep : '&')) >= @params_limit
192
+ raise QueryLimitError, "total number of query parameters (#{param_count+1}) exceeds limit (#{@params_limit})"
193
+ end
194
+
195
+ qs
196
+ else
197
+ ''
198
+ end
199
+ end
200
+
201
+ def unescape(string)
202
+ Utils.unescape(string)
157
203
  end
158
204
 
159
205
  class Params
@@ -169,7 +215,7 @@ module Rack
169
215
 
170
216
  def []=(key, value)
171
217
  @size += key.size if key && !@params.key?(key)
172
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
218
+ raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
173
219
  @params[key] = value
174
220
  end
175
221
 
@@ -19,7 +19,7 @@ module Rack
19
19
  @env[PATH_INFO] = @url.path
20
20
  @env[QUERY_STRING] = @url.query if @url.query
21
21
  @env[HTTP_HOST] = @url.host if @url.host
22
- @env["HTTP_PORT"] = @url.port if @url.port
22
+ @env[HTTP_PORT] = @url.port if @url.port
23
23
  @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
24
24
 
25
25
  super "forwarding to #{url}"
data/lib/rack/reloader.rb CHANGED
@@ -6,8 +6,6 @@
6
6
 
7
7
  require 'pathname'
8
8
 
9
- require_relative 'core_ext/regexp'
10
-
11
9
  module Rack
12
10
 
13
11
  # High performant source reloader
@@ -24,7 +22,7 @@ module Rack
24
22
  # It is performing a check/reload cycle at the start of every request, but
25
23
  # also respects a cool down time, during which nothing will be done.
26
24
  class Reloader
27
- using ::Rack::RegexpExtensions
25
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
28
26
 
29
27
  def initialize(app, cooldown = 10, backend = Stat)
30
28
  @app = app