rack 2.0.9.3 → 2.2.0

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 +675 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -162
  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 -28
  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 +5 -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 +17 -11
  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 +72 -26
  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 +4 -2
  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 +58 -73
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +7 -4
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +53 -28
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +210 -61
  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 +33 -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 +17 -13
  70. data/lib/rack/show_status.rb +5 -5
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +12 -6
  74. data/lib/rack/utils.rb +105 -130
  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,17 +1,22 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
2
4
 
3
5
  module Rack
4
6
  module Multipart
5
7
  class MultipartPartLimitError < Errno::EMFILE; end
6
- class MultipartTotalPartLimitError < StandardError; end
7
8
 
8
9
  class Parser
9
- BUFSIZE = 16384
10
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
11
+
12
+ BUFSIZE = 1_048_576
10
13
  TEXT_PLAIN = "text/plain"
11
14
  TEMPFILE_FACTORY = lambda { |filename, content_type|
12
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
15
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
13
16
  }
14
17
 
18
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
19
+
15
20
  class BoundedIO # :nodoc:
16
21
  def initialize(io, content_length)
17
22
  @io = io
@@ -19,15 +24,15 @@ module Rack
19
24
  @cursor = 0
20
25
  end
21
26
 
22
- def read(size)
27
+ def read(size, outbuf = nil)
23
28
  return if @cursor >= @content_length
24
29
 
25
30
  left = @content_length - @cursor
26
31
 
27
32
  str = if left < size
28
- @io.read left
33
+ @io.read left, outbuf
29
34
  else
30
- @io.read size
35
+ @io.read size, outbuf
31
36
  end
32
37
 
33
38
  if str
@@ -62,13 +67,14 @@ module Rack
62
67
  return EMPTY unless boundary
63
68
 
64
69
  io = BoundedIO.new(io, content_length) if content_length
70
+ outbuf = String.new
65
71
 
66
72
  parser = new(boundary, tmpfile, bufsize, qp)
67
- parser.on_read io.read(bufsize)
73
+ parser.on_read io.read(bufsize, outbuf)
68
74
 
69
75
  loop do
70
76
  break if parser.state == :DONE
71
- parser.on_read io.read(bufsize)
77
+ parser.on_read io.read(bufsize, outbuf)
72
78
  end
73
79
 
74
80
  io.rewind
@@ -91,14 +97,8 @@ module Rack
91
97
  # those which give the lone filename.
92
98
  fn = filename.split(/[\/\\]/).last
93
99
 
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}
100
+ data = { filename: fn, type: content_type,
101
+ name: name, tempfile: body, head: head }
102
102
  end
103
103
 
104
104
  yield data
@@ -117,7 +117,7 @@ module Rack
117
117
 
118
118
  include Enumerable
119
119
 
120
- def initialize tempfile
120
+ def initialize(tempfile)
121
121
  @tempfile = tempfile
122
122
  @mime_parts = []
123
123
  @open_files = 0
@@ -127,7 +127,7 @@ module Rack
127
127
  @mime_parts.each { |part| yield part }
128
128
  end
129
129
 
130
- def on_mime_head mime_index, head, filename, content_type, name
130
+ def on_mime_head(mime_index, head, filename, content_type, name)
131
131
  if filename
132
132
  body = @tempfile.call(filename, content_type)
133
133
  body.binmode if body.respond_to?(:binmode)
@@ -140,60 +140,51 @@ module Rack
140
140
 
141
141
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
142
142
 
143
- check_part_limits
143
+ check_open_files
144
144
  end
145
145
 
146
- def on_mime_body mime_index, content
146
+ def on_mime_body(mime_index, content)
147
147
  @mime_parts[mime_index].body << content
148
148
  end
149
149
 
150
- def on_mime_finish mime_index
150
+ def on_mime_finish(mime_index)
151
151
  end
152
152
 
153
153
  private
154
154
 
155
- def check_part_limits
156
- file_limit = Utils.multipart_file_limit
157
- part_limit = Utils.multipart_total_part_limit
158
-
159
- if file_limit && file_limit > 0
160
- if @open_files >= file_limit
155
+ def check_open_files
156
+ if Utils.multipart_part_limit > 0
157
+ if @open_files >= Utils.multipart_part_limit
161
158
  @mime_parts.each(&:close)
162
159
  raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
163
160
  end
164
161
  end
165
-
166
- if part_limit && part_limit > 0
167
- if @mime_parts.size >= part_limit
168
- @mime_parts.each(&:close)
169
- raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
170
- end
171
- end
172
162
  end
173
163
  end
174
164
 
175
165
  attr_reader :state
176
166
 
177
167
  def initialize(boundary, tempfile, bufsize, query_parser)
178
- @buf = String.new
179
-
180
168
  @query_parser = query_parser
181
169
  @params = query_parser.make_params
182
170
  @boundary = "--#{boundary}"
183
171
  @bufsize = bufsize
184
172
 
185
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
186
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
187
173
  @full_boundary = @boundary
188
174
  @end_boundary = @boundary + '--'
189
175
  @state = :FAST_FORWARD
190
176
  @mime_index = 0
191
177
  @collector = Collector.new tempfile
178
+
179
+ @sbuf = StringScanner.new("".dup)
180
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
181
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
182
+ @head_regex = /(.*?#{EOL})#{EOL}/m
192
183
  end
193
184
 
194
- def on_read content
185
+ def on_read(content)
195
186
  handle_empty_content!(content)
196
- @buf << content
187
+ @sbuf.concat content
197
188
  run_parser
198
189
  end
199
190
 
@@ -204,7 +195,6 @@ module Rack
204
195
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
205
196
  end
206
197
  end
207
-
208
198
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
209
199
  end
210
200
 
@@ -231,7 +221,7 @@ module Rack
231
221
  if consume_boundary
232
222
  @state = :MIME_HEAD
233
223
  else
234
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
224
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
235
225
  :want_read
236
226
  end
237
227
  end
@@ -239,19 +229,16 @@ module Rack
239
229
  def handle_consume_token
240
230
  tok = consume_boundary
241
231
  # 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
232
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
233
+ :DONE
244
234
  else
245
- @state = :MIME_HEAD
235
+ :MIME_HEAD
246
236
  end
247
237
  end
248
238
 
249
239
  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
-
240
+ if @sbuf.scan_until(@head_regex)
241
+ head = @sbuf[1]
255
242
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
256
243
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
257
244
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -262,7 +249,7 @@ module Rack
262
249
  filename = get_filename(head)
263
250
 
264
251
  if name.nil? || name.empty?
265
- name = filename || "#{content_type || TEXT_PLAIN}[]"
252
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
266
253
  end
267
254
 
268
255
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -273,16 +260,19 @@ module Rack
273
260
  end
274
261
 
275
262
  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
263
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
264
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
265
+ @collector.on_mime_body @mime_index, body
266
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
280
267
  @state = :CONSUME_TOKEN
281
268
  @mime_index += 1
282
269
  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)
270
+ # Save what we have so far
271
+ if @rx_max_size < @sbuf.rest_size
272
+ delta = @sbuf.rest_size - @rx_max_size
273
+ @collector.on_mime_body @mime_index, @sbuf.peek(delta)
274
+ @sbuf.pos += delta
275
+ @sbuf.string = @sbuf.rest
286
276
  end
287
277
  :want_read
288
278
  end
@@ -290,16 +280,13 @@ module Rack
290
280
 
291
281
  def full_boundary; @full_boundary; end
292
282
 
293
- def rx; @rx; end
294
-
295
283
  def consume_boundary
296
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
297
- read_buffer = $1
284
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
285
  case read_buffer.strip
299
286
  when full_boundary then return :BOUNDARY
300
287
  when @end_boundary then return :END_BOUNDARY
301
288
  end
302
- return if @buf.empty?
289
+ return if @sbuf.eos?
303
290
  end
304
291
  end
305
292
 
@@ -314,15 +301,14 @@ module Rack
314
301
  elsif filename = params['filename*']
315
302
  encoding, _, filename = filename.split("'", 3)
316
303
  end
317
- when BROKEN
304
+ when BROKEN_QUOTED, BROKEN_UNQUOTED
318
305
  filename = $1
319
- filename = $1 if filename =~ /^"(.*)"$/
320
306
  end
321
307
 
322
308
  return unless filename
323
309
 
324
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
325
- filename = Utils.unescape(filename)
310
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
311
+ filename = Utils.unescape_path(filename)
326
312
  end
327
313
 
328
314
  filename.scrub!
@@ -338,7 +324,7 @@ module Rack
338
324
  filename
339
325
  end
340
326
 
341
- CHARSET = "charset"
327
+ CHARSET = "charset"
342
328
 
343
329
  def tag_multipart_encoding(filename, content_type, name, body)
344
330
  name = name.to_s
@@ -353,12 +339,12 @@ module Rack
353
339
  type_subtype = list.first
354
340
  type_subtype.strip!
355
341
  if TEXT_PLAIN == type_subtype
356
- rest = list.drop 1
342
+ rest = list.drop 1
357
343
  rest.each do |param|
358
- k,v = param.split('=', 2)
344
+ k, v = param.split('=', 2)
359
345
  k.strip!
360
346
  v.strip!
361
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
347
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
362
348
  encoding = Encoding.find v if k == CHARSET
363
349
  end
364
350
  end
@@ -368,7 +354,6 @@ module Rack
368
354
  body.force_encoding(encoding)
369
355
  end
370
356
 
371
-
372
357
  def handle_empty_content!(content)
373
358
  if content.nil? || content.empty?
374
359
  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.
@@ -14,12 +16,13 @@ module Rack
14
16
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
15
17
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
- BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
19
+ BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
20
+ BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
18
21
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
19
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s+name=(#{VALUE})/ni
22
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
20
23
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
21
24
  # Updated definitions from RFC 2231
22
- ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
+ ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
23
26
  ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
24
27
  SECTION = /\*[0-9]+/
25
28
  REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
@@ -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
 
@@ -36,7 +40,7 @@ module Rack
36
40
 
37
41
  (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
38
42
  next if p.empty?
39
- k, v = p.split('='.freeze, 2).map!(&unescaper)
43
+ k, v = p.split('=', 2).map!(&unescaper)
40
44
 
41
45
  if cur = params[k]
42
46
  if cur.class == Array
@@ -49,7 +53,7 @@ module Rack
49
53
  end
50
54
  end
51
55
 
52
- return params.to_params_hash
56
+ return params.to_h
53
57
  end
54
58
 
55
59
  # parse_nested_query expands a query string into structural types. Supported
@@ -58,18 +62,19 @@ module Rack
58
62
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
59
63
  # case.
60
64
  def parse_nested_query(qs, d = nil)
61
- return {} if qs.nil? || qs.empty?
62
65
  params = make_params
63
66
 
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) }
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) }
66
70
 
67
- normalize_params(params, k, v, param_depth_limit)
71
+ normalize_params(params, k, v, param_depth_limit)
72
+ end
68
73
  end
69
74
 
70
- return params.to_params_hash
75
+ return params.to_h
71
76
  rescue ArgumentError => e
72
- raise InvalidParameterError, e.message
77
+ raise InvalidParameterError, e.message, e.backtrace
73
78
  end
74
79
 
75
80
  # normalize_params recursively expands parameters into structural types. If
@@ -79,22 +84,22 @@ module Rack
79
84
  raise RangeError if depth <= 0
80
85
 
81
86
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
- k = $1 || ''.freeze
83
- after = $' || ''.freeze
87
+ k = $1 || ''
88
+ after = $' || ''
84
89
 
85
90
  if k.empty?
86
- if !v.nil? && name == "[]".freeze
91
+ if !v.nil? && name == "[]"
87
92
  return Array(v)
88
93
  else
89
94
  return
90
95
  end
91
96
  end
92
97
 
93
- if after == ''.freeze
98
+ if after == ''
94
99
  params[k] = v
95
- elsif after == "[".freeze
100
+ elsif after == "["
96
101
  params[name] = v
97
- elsif after == "[]".freeze
102
+ elsif after == "[]"
98
103
  params[k] ||= []
99
104
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
100
105
  params[k] << v
@@ -135,7 +140,7 @@ module Rack
135
140
  end
136
141
 
137
142
  def params_hash_has_key?(hash, key)
138
- return false if key =~ /\[\]/
143
+ return false if /\[\]/.match?(key)
139
144
 
140
145
  key.split(/[\[\]]+/).inject(hash) do |h, part|
141
146
  next h if part == ''
@@ -171,22 +176,42 @@ module Rack
171
176
  @params.key?(key)
172
177
  end
173
178
 
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}
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.
186
210
  end
187
211
  end
188
- hash
212
+ @params
189
213
  end
214
+ alias_method :to_params_hash, :to_h
190
215
  end
191
216
  end
192
217
  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