rack 2.0.9.3 → 2.2.6.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +735 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +151 -147
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +35 -10
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +7 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +33 -25
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +59 -34
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +7 -4
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +3 -3
  40. data/lib/rack/handler/webrick.rb +15 -6
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +71 -25
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +2 -1
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +5 -3
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +97 -20
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +53 -56
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +4 -2
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +59 -30
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +222 -63
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +13 -9
  64. data/lib/rack/server.rb +95 -24
  65. data/lib/rack/session/abstract/id.rb +34 -21
  66. data/lib/rack/session/cookie.rb +11 -12
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +5 -3
  69. data/lib/rack/show_exceptions.rb +21 -17
  70. data/lib/rack/show_status.rb +9 -9
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +13 -7
  74. data/lib/rack/utils.rb +105 -111
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +39 -182
  79. data/HISTORY.md +0 -520
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -107
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -520
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -721
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1423
  166. data/test/spec_response.rb +0 -528
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -357
  175. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  176. data/test/spec_session_pool.rb +0 -247
  177. data/test/spec_show_exceptions.rb +0 -93
  178. data/test/spec_show_status.rb +0 -104
  179. data/test/spec_static.rb +0 -184
  180. data/test/spec_tempfile_reaper.rb +0 -64
  181. data/test/spec_thin.rb +0 -96
  182. data/test/spec_urlmap.rb +0 -237
  183. data/test/spec_utils.rb +0 -742
  184. data/test/spec_version.rb +0 -11
  185. data/test/spec_webrick.rb +0 -206
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/foo.html +0 -1
  188. data/test/static/index.html +0 -1
  189. data/test/testrequest.rb +0 -78
  190. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  191. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,4 +1,6 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
2
4
 
3
5
  module Rack
4
6
  module Multipart
@@ -6,12 +8,16 @@ module Rack
6
8
  class MultipartTotalPartLimitError < StandardError; end
7
9
 
8
10
  class Parser
9
- BUFSIZE = 16384
11
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
12
+
13
+ BUFSIZE = 1_048_576
10
14
  TEXT_PLAIN = "text/plain"
11
15
  TEMPFILE_FACTORY = lambda { |filename, content_type|
12
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
16
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
13
17
  }
14
18
 
19
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
20
+
15
21
  class BoundedIO # :nodoc:
16
22
  def initialize(io, content_length)
17
23
  @io = io
@@ -19,15 +25,15 @@ module Rack
19
25
  @cursor = 0
20
26
  end
21
27
 
22
- def read(size)
28
+ def read(size, outbuf = nil)
23
29
  return if @cursor >= @content_length
24
30
 
25
31
  left = @content_length - @cursor
26
32
 
27
33
  str = if left < size
28
- @io.read left
34
+ @io.read left, outbuf
29
35
  else
30
- @io.read size
36
+ @io.read size, outbuf
31
37
  end
32
38
 
33
39
  if str
@@ -62,13 +68,14 @@ module Rack
62
68
  return EMPTY unless boundary
63
69
 
64
70
  io = BoundedIO.new(io, content_length) if content_length
71
+ outbuf = String.new
65
72
 
66
73
  parser = new(boundary, tmpfile, bufsize, qp)
67
- parser.on_read io.read(bufsize)
74
+ parser.on_read io.read(bufsize, outbuf)
68
75
 
69
76
  loop do
70
77
  break if parser.state == :DONE
71
- parser.on_read io.read(bufsize)
78
+ parser.on_read io.read(bufsize, outbuf)
72
79
  end
73
80
 
74
81
  io.rewind
@@ -91,14 +98,8 @@ module Rack
91
98
  # those which give the lone filename.
92
99
  fn = filename.split(/[\/\\]/).last
93
100
 
94
- data = {:filename => fn, :type => content_type,
95
- :name => name, :tempfile => body, :head => head}
96
- elsif !filename && content_type && body.is_a?(IO)
97
- body.rewind
98
-
99
- # Generic multipart cases, not coming from a form
100
- data = {:type => content_type,
101
- :name => name, :tempfile => body, :head => head}
101
+ data = { filename: fn, type: content_type,
102
+ name: name, tempfile: body, head: head }
102
103
  end
103
104
 
104
105
  yield data
@@ -117,7 +118,7 @@ module Rack
117
118
 
118
119
  include Enumerable
119
120
 
120
- def initialize tempfile
121
+ def initialize(tempfile)
121
122
  @tempfile = tempfile
122
123
  @mime_parts = []
123
124
  @open_files = 0
@@ -127,7 +128,7 @@ module Rack
127
128
  @mime_parts.each { |part| yield part }
128
129
  end
129
130
 
130
- def on_mime_head mime_index, head, filename, content_type, name
131
+ def on_mime_head(mime_index, head, filename, content_type, name)
131
132
  if filename
132
133
  body = @tempfile.call(filename, content_type)
133
134
  body.binmode if body.respond_to?(:binmode)
@@ -143,11 +144,11 @@ module Rack
143
144
  check_part_limits
144
145
  end
145
146
 
146
- def on_mime_body mime_index, content
147
+ def on_mime_body(mime_index, content)
147
148
  @mime_parts[mime_index].body << content
148
149
  end
149
150
 
150
- def on_mime_finish mime_index
151
+ def on_mime_finish(mime_index)
151
152
  end
152
153
 
153
154
  private
@@ -175,25 +176,26 @@ module Rack
175
176
  attr_reader :state
176
177
 
177
178
  def initialize(boundary, tempfile, bufsize, query_parser)
178
- @buf = String.new
179
-
180
179
  @query_parser = query_parser
181
180
  @params = query_parser.make_params
182
181
  @boundary = "--#{boundary}"
183
182
  @bufsize = bufsize
184
183
 
185
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
186
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
187
184
  @full_boundary = @boundary
188
185
  @end_boundary = @boundary + '--'
189
186
  @state = :FAST_FORWARD
190
187
  @mime_index = 0
191
188
  @collector = Collector.new tempfile
189
+
190
+ @sbuf = StringScanner.new("".dup)
191
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
192
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
193
+ @head_regex = /(.*?#{EOL})#{EOL}/m
192
194
  end
193
195
 
194
- def on_read content
196
+ def on_read(content)
195
197
  handle_empty_content!(content)
196
- @buf << content
198
+ @sbuf.concat content
197
199
  run_parser
198
200
  end
199
201
 
@@ -204,7 +206,6 @@ module Rack
204
206
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
205
207
  end
206
208
  end
207
-
208
209
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
209
210
  end
210
211
 
@@ -231,7 +232,7 @@ module Rack
231
232
  if consume_boundary
232
233
  @state = :MIME_HEAD
233
234
  else
234
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
235
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
235
236
  :want_read
236
237
  end
237
238
  end
@@ -239,19 +240,16 @@ module Rack
239
240
  def handle_consume_token
240
241
  tok = consume_boundary
241
242
  # break if we're at the end of a buffer, but not if it is the end of a field
242
- if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
243
- @state = :DONE
243
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
244
+ :DONE
244
245
  else
245
- @state = :MIME_HEAD
246
+ :MIME_HEAD
246
247
  end
247
248
  end
248
249
 
249
250
  def handle_mime_head
250
- if @buf.index(EOL + EOL)
251
- i = @buf.index(EOL+EOL)
252
- head = @buf.slice!(0, i+2) # First \r\n
253
- @buf.slice!(0, 2) # Second \r\n
254
-
251
+ if @sbuf.scan_until(@head_regex)
252
+ head = @sbuf[1]
255
253
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
256
254
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
257
255
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -262,7 +260,7 @@ module Rack
262
260
  filename = get_filename(head)
263
261
 
264
262
  if name.nil? || name.empty?
265
- name = filename || "#{content_type || TEXT_PLAIN}[]"
263
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
266
264
  end
267
265
 
268
266
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -273,16 +271,19 @@ module Rack
273
271
  end
274
272
 
275
273
  def handle_mime_body
276
- if i = @buf.index(rx)
277
- # Save the rest.
278
- @collector.on_mime_body @mime_index, @buf.slice!(0, i)
279
- @buf.slice!(0, 2) # Remove \r\n after the content
274
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
275
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
276
+ @collector.on_mime_body @mime_index, body
277
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
280
278
  @state = :CONSUME_TOKEN
281
279
  @mime_index += 1
282
280
  else
283
- # Save the read body part.
284
- if @rx_max_size < @buf.size
285
- @collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
281
+ # Save what we have so far
282
+ if @rx_max_size < @sbuf.rest_size
283
+ delta = @sbuf.rest_size - @rx_max_size
284
+ @collector.on_mime_body @mime_index, @sbuf.peek(delta)
285
+ @sbuf.pos += delta
286
+ @sbuf.string = @sbuf.rest
286
287
  end
287
288
  :want_read
288
289
  end
@@ -290,16 +291,13 @@ module Rack
290
291
 
291
292
  def full_boundary; @full_boundary; end
292
293
 
293
- def rx; @rx; end
294
-
295
294
  def consume_boundary
296
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
297
- read_buffer = $1
295
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
296
  case read_buffer.strip
299
297
  when full_boundary then return :BOUNDARY
300
298
  when @end_boundary then return :END_BOUNDARY
301
299
  end
302
- return if @buf.empty?
300
+ return if @sbuf.eos?
303
301
  end
304
302
  end
305
303
 
@@ -321,8 +319,8 @@ module Rack
321
319
 
322
320
  return unless filename
323
321
 
324
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
325
- filename = Utils.unescape(filename)
322
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
323
+ filename = Utils.unescape_path(filename)
326
324
  end
327
325
 
328
326
  filename.scrub!
@@ -338,7 +336,7 @@ module Rack
338
336
  filename
339
337
  end
340
338
 
341
- CHARSET = "charset"
339
+ CHARSET = "charset"
342
340
 
343
341
  def tag_multipart_encoding(filename, content_type, name, body)
344
342
  name = name.to_s
@@ -353,12 +351,12 @@ module Rack
353
351
  type_subtype = list.first
354
352
  type_subtype.strip!
355
353
  if TEXT_PLAIN == type_subtype
356
- rest = list.drop 1
354
+ rest = list.drop 1
357
355
  rest.each do |param|
358
- k,v = param.split('=', 2)
356
+ k, v = param.split('=', 2)
359
357
  k.strip!
360
358
  v.strip!
361
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
359
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
362
360
  encoding = Encoding.find v if k == CHARSET
363
361
  end
364
362
  end
@@ -368,7 +366,6 @@ module Rack
368
366
  body.force_encoding(encoding)
369
367
  end
370
368
 
371
-
372
369
  def handle_empty_content!(content)
373
370
  if content.nil? || content.empty?
374
371
  raise EOFError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class UploadedFile
@@ -7,17 +9,23 @@ module Rack
7
9
  # The content type of the "uploaded" file
8
10
  attr_accessor :content_type
9
11
 
10
- def initialize(path, content_type = "text/plain", binary = false)
11
- 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
12
24
  @content_type = content_type
13
- @original_filename = ::File.basename(path)
14
- @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
15
- @tempfile.binmode if binary
16
- FileUtils.copy_file(path, @tempfile.path)
17
25
  end
18
26
 
19
27
  def path
20
- @tempfile.path
28
+ @tempfile.path if @tempfile.respond_to?(:path)
21
29
  end
22
30
  alias_method :local_path, :path
23
31
 
@@ -1,4 +1,6 @@
1
- require 'rack/multipart/parser'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'multipart/parser'
2
4
 
3
5
  module Rack
4
6
  # A multipart form data parser, adapted from IOWA.
@@ -16,7 +18,7 @@ module Rack
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
19
  BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
18
20
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
19
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s+name=(#{VALUE})/ni
21
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
20
22
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
21
23
  # Updated definitions from RFC 2231
22
24
  ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class NullLogger
3
5
  def initialize(app)
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class QueryParser
5
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
6
+
3
7
  DEFAULT_SEP = /[&;] */n
4
8
  COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
5
9
 
@@ -12,6 +16,10 @@ module Rack
12
16
  # sequence.
13
17
  class InvalidParameterError < ArgumentError; end
14
18
 
19
+ # ParamsTooDeepError is the error that is raised when params are recursively
20
+ # nested over the specified limit.
21
+ class ParamsTooDeepError < RangeError; end
22
+
15
23
  def self.make_default(key_space_limit, param_depth_limit)
16
24
  new Params, key_space_limit, param_depth_limit
17
25
  end
@@ -36,7 +44,7 @@ module Rack
36
44
 
37
45
  (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
38
46
  next if p.empty?
39
- k, v = p.split('='.freeze, 2).map!(&unescaper)
47
+ k, v = p.split('=', 2).map!(&unescaper)
40
48
 
41
49
  if cur = params[k]
42
50
  if cur.class == Array
@@ -49,7 +57,7 @@ module Rack
49
57
  end
50
58
  end
51
59
 
52
- return params.to_params_hash
60
+ return params.to_h
53
61
  end
54
62
 
55
63
  # parse_nested_query expands a query string into structural types. Supported
@@ -58,43 +66,44 @@ module Rack
58
66
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
59
67
  # case.
60
68
  def parse_nested_query(qs, d = nil)
61
- return {} if qs.nil? || qs.empty?
62
69
  params = make_params
63
70
 
64
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
65
- k, v = p.split('='.freeze, 2).map! { |s| unescape(s) }
71
+ unless qs.nil? || qs.empty?
72
+ (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
73
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
66
74
 
67
- normalize_params(params, k, v, param_depth_limit)
75
+ normalize_params(params, k, v, param_depth_limit)
76
+ end
68
77
  end
69
78
 
70
- return params.to_params_hash
79
+ return params.to_h
71
80
  rescue ArgumentError => e
72
- raise InvalidParameterError, e.message
81
+ raise InvalidParameterError, e.message, e.backtrace
73
82
  end
74
83
 
75
84
  # normalize_params recursively expands parameters into structural types. If
76
85
  # the structural types represented by two different parameter names are in
77
86
  # conflict, a ParameterTypeError is raised.
78
87
  def normalize_params(params, name, v, depth)
79
- raise RangeError if depth <= 0
88
+ raise ParamsTooDeepError if depth <= 0
80
89
 
81
90
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
- k = $1 || ''.freeze
83
- after = $' || ''.freeze
91
+ k = $1 || ''
92
+ after = $' || ''
84
93
 
85
94
  if k.empty?
86
- if !v.nil? && name == "[]".freeze
95
+ if !v.nil? && name == "[]"
87
96
  return Array(v)
88
97
  else
89
98
  return
90
99
  end
91
100
  end
92
101
 
93
- if after == ''.freeze
102
+ if after == ''
94
103
  params[k] = v
95
- elsif after == "[".freeze
104
+ elsif after == "["
96
105
  params[name] = v
97
- elsif after == "[]".freeze
106
+ elsif after == "[]"
98
107
  params[k] ||= []
99
108
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
100
109
  params[k] << v
@@ -135,7 +144,7 @@ module Rack
135
144
  end
136
145
 
137
146
  def params_hash_has_key?(hash, key)
138
- return false if key =~ /\[\]/
147
+ return false if /\[\]/.match?(key)
139
148
 
140
149
  key.split(/[\[\]]+/).inject(hash) do |h, part|
141
150
  next h if part == ''
@@ -163,7 +172,7 @@ module Rack
163
172
 
164
173
  def []=(key, value)
165
174
  @size += key.size if key && !@params.key?(key)
166
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
175
+ raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
167
176
  @params[key] = value
168
177
  end
169
178
 
@@ -171,22 +180,42 @@ module Rack
171
180
  @params.key?(key)
172
181
  end
173
182
 
174
- def to_params_hash
175
- hash = @params
176
- hash.keys.each do |key|
177
- value = hash[key]
178
- if value.kind_of?(self.class)
179
- if value.object_id == self.object_id
180
- hash[key] = hash
181
- else
182
- hash[key] = value.to_params_hash
183
- end
184
- elsif value.kind_of?(Array)
185
- value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
183
+ # Recursively unwraps nested `Params` objects and constructs an object
184
+ # of the same shape, but using the objects' internal representations
185
+ # (Ruby hashes) in place of the objects. The result is a hash consisting
186
+ # purely of Ruby primitives.
187
+ #
188
+ # Mutation warning!
189
+ #
190
+ # 1. This method mutates the internal representation of the `Params`
191
+ # objects in order to save object allocations.
192
+ #
193
+ # 2. The value you get back is a reference to the internal hash
194
+ # representation, not a copy.
195
+ #
196
+ # 3. Because the `Params` object's internal representation is mutable
197
+ # through the `#[]=` method, it is not thread safe. The result of
198
+ # getting the hash representation while another thread is adding a
199
+ # key to it is non-deterministic.
200
+ #
201
+ def to_h
202
+ @params.each do |key, value|
203
+ case value
204
+ when self
205
+ # Handle circular references gracefully.
206
+ @params[key] = @params
207
+ when Params
208
+ @params[key] = value.to_h
209
+ when Array
210
+ value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
211
+ else
212
+ # Ignore anything that is not a `Params` object or
213
+ # a collection that can contain one.
186
214
  end
187
215
  end
188
- hash
216
+ @params
189
217
  end
218
+ alias_method :to_params_hash, :to_h
190
219
  end
191
220
  end
192
221
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  module Rack
@@ -10,14 +12,14 @@ module Rack
10
12
  class ForwardRequest < Exception
11
13
  attr_reader :url, :env
12
14
 
13
- def initialize(url, env={})
15
+ def initialize(url, env = {})
14
16
  @url = URI(url)
15
17
  @env = env
16
18
 
17
- @env[PATH_INFO] = @url.path
18
- @env[QUERY_STRING] = @url.query if @url.query
19
- @env[HTTP_HOST] = @url.host if @url.host
20
- @env["HTTP_PORT"] = @url.port if @url.port
19
+ @env[PATH_INFO] = @url.path
20
+ @env[QUERY_STRING] = @url.query if @url.query
21
+ @env[HTTP_HOST] = @url.host if @url.host
22
+ @env[HTTP_PORT] = @url.port if @url.port
21
23
  @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
22
24
 
23
25
  super "forwarding to #{url}"
data/lib/rack/reloader.rb CHANGED
@@ -1,6 +1,8 @@
1
- # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
2
- # Rack::Reloader is subject to the terms of an MIT-style license.
3
- # See COPYING or http://www.opensource.org/licenses/mit-license.php.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
4
+ # Rack::Reloader is subject to the terms of an MIT-style license.
5
+ # See MIT-LICENSE or https://opensource.org/licenses/MIT.
4
6
 
5
7
  require 'pathname'
6
8
 
@@ -20,6 +22,8 @@ module Rack
20
22
  # It is performing a check/reload cycle at the start of every request, but
21
23
  # also respects a cool down time, during which nothing will be done.
22
24
  class Reloader
25
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
26
+
23
27
  def initialize(app, cooldown = 10, backend = Stat)
24
28
  @app = app
25
29
  @cooldown = cooldown
@@ -69,7 +73,7 @@ module Rack
69
73
  paths = ['./', *$LOAD_PATH].uniq
70
74
 
71
75
  files.map{|file|
72
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
76
+ next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
73
77
 
74
78
  found, stat = figure_path(file, paths)
75
79
  next unless found && stat && mtime = stat.mtime