rack 2.0.9.3 → 2.2.8

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +740 -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} +44 -15
  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 +219 -184
  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 +55 -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 +12 -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 +36 -179
  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,18 @@ 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
+ extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
17
+
18
+ Tempfile.new(["RackMultipart", extension])
13
19
  }
14
20
 
21
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
+
15
23
  class BoundedIO # :nodoc:
16
24
  def initialize(io, content_length)
17
25
  @io = io
@@ -19,15 +27,15 @@ module Rack
19
27
  @cursor = 0
20
28
  end
21
29
 
22
- def read(size)
30
+ def read(size, outbuf = nil)
23
31
  return if @cursor >= @content_length
24
32
 
25
33
  left = @content_length - @cursor
26
34
 
27
35
  str = if left < size
28
- @io.read left
36
+ @io.read left, outbuf
29
37
  else
30
- @io.read size
38
+ @io.read size, outbuf
31
39
  end
32
40
 
33
41
  if str
@@ -62,13 +70,14 @@ module Rack
62
70
  return EMPTY unless boundary
63
71
 
64
72
  io = BoundedIO.new(io, content_length) if content_length
73
+ outbuf = String.new
65
74
 
66
75
  parser = new(boundary, tmpfile, bufsize, qp)
67
- parser.on_read io.read(bufsize)
76
+ parser.on_read io.read(bufsize, outbuf)
68
77
 
69
78
  loop do
70
79
  break if parser.state == :DONE
71
- parser.on_read io.read(bufsize)
80
+ parser.on_read io.read(bufsize, outbuf)
72
81
  end
73
82
 
74
83
  io.rewind
@@ -91,14 +100,8 @@ module Rack
91
100
  # those which give the lone filename.
92
101
  fn = filename.split(/[\/\\]/).last
93
102
 
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}
103
+ data = { filename: fn, type: content_type,
104
+ name: name, tempfile: body, head: head }
102
105
  end
103
106
 
104
107
  yield data
@@ -117,7 +120,7 @@ module Rack
117
120
 
118
121
  include Enumerable
119
122
 
120
- def initialize tempfile
123
+ def initialize(tempfile)
121
124
  @tempfile = tempfile
122
125
  @mime_parts = []
123
126
  @open_files = 0
@@ -127,7 +130,7 @@ module Rack
127
130
  @mime_parts.each { |part| yield part }
128
131
  end
129
132
 
130
- def on_mime_head mime_index, head, filename, content_type, name
133
+ def on_mime_head(mime_index, head, filename, content_type, name)
131
134
  if filename
132
135
  body = @tempfile.call(filename, content_type)
133
136
  body.binmode if body.respond_to?(:binmode)
@@ -143,11 +146,11 @@ module Rack
143
146
  check_part_limits
144
147
  end
145
148
 
146
- def on_mime_body mime_index, content
149
+ def on_mime_body(mime_index, content)
147
150
  @mime_parts[mime_index].body << content
148
151
  end
149
152
 
150
- def on_mime_finish mime_index
153
+ def on_mime_finish(mime_index)
151
154
  end
152
155
 
153
156
  private
@@ -175,25 +178,26 @@ module Rack
175
178
  attr_reader :state
176
179
 
177
180
  def initialize(boundary, tempfile, bufsize, query_parser)
178
- @buf = String.new
179
-
180
181
  @query_parser = query_parser
181
182
  @params = query_parser.make_params
182
183
  @boundary = "--#{boundary}"
183
184
  @bufsize = bufsize
184
185
 
185
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
186
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
187
186
  @full_boundary = @boundary
188
187
  @end_boundary = @boundary + '--'
189
188
  @state = :FAST_FORWARD
190
189
  @mime_index = 0
191
190
  @collector = Collector.new tempfile
191
+
192
+ @sbuf = StringScanner.new("".dup)
193
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
194
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
195
+ @head_regex = /(.*?#{EOL})#{EOL}/m
192
196
  end
193
197
 
194
- def on_read content
198
+ def on_read(content)
195
199
  handle_empty_content!(content)
196
- @buf << content
200
+ @sbuf.concat content
197
201
  run_parser
198
202
  end
199
203
 
@@ -204,7 +208,6 @@ module Rack
204
208
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
205
209
  end
206
210
  end
207
-
208
211
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
209
212
  end
210
213
 
@@ -231,7 +234,7 @@ module Rack
231
234
  if consume_boundary
232
235
  @state = :MIME_HEAD
233
236
  else
234
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
237
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
235
238
  :want_read
236
239
  end
237
240
  end
@@ -239,19 +242,16 @@ module Rack
239
242
  def handle_consume_token
240
243
  tok = consume_boundary
241
244
  # 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
245
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
246
+ :DONE
244
247
  else
245
- @state = :MIME_HEAD
248
+ :MIME_HEAD
246
249
  end
247
250
  end
248
251
 
249
252
  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
-
253
+ if @sbuf.scan_until(@head_regex)
254
+ head = @sbuf[1]
255
255
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
256
256
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
257
257
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -262,7 +262,7 @@ module Rack
262
262
  filename = get_filename(head)
263
263
 
264
264
  if name.nil? || name.empty?
265
- name = filename || "#{content_type || TEXT_PLAIN}[]"
265
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
266
266
  end
267
267
 
268
268
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -273,16 +273,19 @@ module Rack
273
273
  end
274
274
 
275
275
  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
276
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
277
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
278
+ @collector.on_mime_body @mime_index, body
279
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
280
280
  @state = :CONSUME_TOKEN
281
281
  @mime_index += 1
282
282
  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)
283
+ # Save what we have so far
284
+ if @rx_max_size < @sbuf.rest_size
285
+ delta = @sbuf.rest_size - @rx_max_size
286
+ @collector.on_mime_body @mime_index, @sbuf.peek(delta)
287
+ @sbuf.pos += delta
288
+ @sbuf.string = @sbuf.rest
286
289
  end
287
290
  :want_read
288
291
  end
@@ -290,16 +293,13 @@ module Rack
290
293
 
291
294
  def full_boundary; @full_boundary; end
292
295
 
293
- def rx; @rx; end
294
-
295
296
  def consume_boundary
296
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
297
- read_buffer = $1
297
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
298
  case read_buffer.strip
299
299
  when full_boundary then return :BOUNDARY
300
300
  when @end_boundary then return :END_BOUNDARY
301
301
  end
302
- return if @buf.empty?
302
+ return if @sbuf.eos?
303
303
  end
304
304
  end
305
305
 
@@ -321,8 +321,8 @@ module Rack
321
321
 
322
322
  return unless filename
323
323
 
324
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
325
- filename = Utils.unescape(filename)
324
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
325
+ filename = Utils.unescape_path(filename)
326
326
  end
327
327
 
328
328
  filename.scrub!
@@ -338,7 +338,7 @@ module Rack
338
338
  filename
339
339
  end
340
340
 
341
- CHARSET = "charset"
341
+ CHARSET = "charset"
342
342
 
343
343
  def tag_multipart_encoding(filename, content_type, name, body)
344
344
  name = name.to_s
@@ -353,12 +353,12 @@ module Rack
353
353
  type_subtype = list.first
354
354
  type_subtype.strip!
355
355
  if TEXT_PLAIN == type_subtype
356
- rest = list.drop 1
356
+ rest = list.drop 1
357
357
  rest.each do |param|
358
- k,v = param.split('=', 2)
358
+ k, v = param.split('=', 2)
359
359
  k.strip!
360
360
  v.strip!
361
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
361
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
362
362
  encoding = Encoding.find v if k == CHARSET
363
363
  end
364
364
  end
@@ -368,7 +368,6 @@ module Rack
368
368
  body.force_encoding(encoding)
369
369
  end
370
370
 
371
-
372
371
  def handle_empty_content!(content)
373
372
  if content.nil? || content.empty?
374
373
  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