rack 2.0.6 → 2.2.7

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 (190) 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 +163 -145
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +29 -5
  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 +36 -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 +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 +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 +74 -67
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +6 -5
  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 +100 -22
  66. data/lib/rack/session/cookie.rb +22 -14
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +18 -9
  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 +127 -119
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +39 -181
  79. data/HISTORY.md +0 -505
  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 -95
  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 -515
  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 -722
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1398
  166. data/test/spec_response.rb +0 -510
  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 -320
  175. data/test/spec_session_pool.rb +0 -210
  176. data/test/spec_show_exceptions.rb +0 -93
  177. data/test/spec_show_status.rb +0 -104
  178. data/test/spec_static.rb +0 -184
  179. data/test/spec_tempfile_reaper.rb +0 -64
  180. data/test/spec_thin.rb +0 -96
  181. data/test/spec_urlmap.rb +0 -237
  182. data/test/spec_utils.rb +0 -742
  183. data/test/spec_version.rb +0 -11
  184. data/test/spec_webrick.rb +0 -206
  185. data/test/static/another/index.html +0 -1
  186. data/test/static/foo.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,16 +1,23 @@
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
8
+ class MultipartTotalPartLimitError < StandardError; end
6
9
 
7
10
  class Parser
8
- BUFSIZE = 16384
11
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
12
+
13
+ BUFSIZE = 1_048_576
9
14
  TEXT_PLAIN = "text/plain"
10
15
  TEMPFILE_FACTORY = lambda { |filename, content_type|
11
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
16
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
12
17
  }
13
18
 
19
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
20
+
14
21
  class BoundedIO # :nodoc:
15
22
  def initialize(io, content_length)
16
23
  @io = io
@@ -18,15 +25,15 @@ module Rack
18
25
  @cursor = 0
19
26
  end
20
27
 
21
- def read(size)
28
+ def read(size, outbuf = nil)
22
29
  return if @cursor >= @content_length
23
30
 
24
31
  left = @content_length - @cursor
25
32
 
26
33
  str = if left < size
27
- @io.read left
34
+ @io.read left, outbuf
28
35
  else
29
- @io.read size
36
+ @io.read size, outbuf
30
37
  end
31
38
 
32
39
  if str
@@ -39,8 +46,6 @@ module Rack
39
46
  str
40
47
  end
41
48
 
42
- def eof?; @content_length == @cursor; end
43
-
44
49
  def rewind
45
50
  @io.rewind
46
51
  end
@@ -63,13 +68,14 @@ module Rack
63
68
  return EMPTY unless boundary
64
69
 
65
70
  io = BoundedIO.new(io, content_length) if content_length
71
+ outbuf = String.new
66
72
 
67
73
  parser = new(boundary, tmpfile, bufsize, qp)
68
- parser.on_read io.read(bufsize), io.eof?
74
+ parser.on_read io.read(bufsize, outbuf)
69
75
 
70
76
  loop do
71
77
  break if parser.state == :DONE
72
- parser.on_read io.read(bufsize), io.eof?
78
+ parser.on_read io.read(bufsize, outbuf)
73
79
  end
74
80
 
75
81
  io.rewind
@@ -92,14 +98,8 @@ module Rack
92
98
  # those which give the lone filename.
93
99
  fn = filename.split(/[\/\\]/).last
94
100
 
95
- data = {:filename => fn, :type => content_type,
96
- :name => name, :tempfile => body, :head => head}
97
- elsif !filename && content_type && body.is_a?(IO)
98
- body.rewind
99
-
100
- # Generic multipart cases, not coming from a form
101
- data = {:type => content_type,
102
- :name => name, :tempfile => body, :head => head}
101
+ data = { filename: fn, type: content_type,
102
+ name: name, tempfile: body, head: head }
103
103
  end
104
104
 
105
105
  yield data
@@ -118,7 +118,7 @@ module Rack
118
118
 
119
119
  include Enumerable
120
120
 
121
- def initialize tempfile
121
+ def initialize(tempfile)
122
122
  @tempfile = tempfile
123
123
  @mime_parts = []
124
124
  @open_files = 0
@@ -128,7 +128,7 @@ module Rack
128
128
  @mime_parts.each { |part| yield part }
129
129
  end
130
130
 
131
- def on_mime_head mime_index, head, filename, content_type, name
131
+ def on_mime_head(mime_index, head, filename, content_type, name)
132
132
  if filename
133
133
  body = @tempfile.call(filename, content_type)
134
134
  body.binmode if body.respond_to?(:binmode)
@@ -140,50 +140,62 @@ module Rack
140
140
  end
141
141
 
142
142
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
143
- check_open_files
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
154
155
 
155
- def check_open_files
156
- if Utils.multipart_part_limit > 0
157
- if @open_files >= Utils.multipart_part_limit
156
+ def check_part_limits
157
+ file_limit = Utils.multipart_file_limit
158
+ part_limit = Utils.multipart_total_part_limit
159
+
160
+ if file_limit && file_limit > 0
161
+ if @open_files >= file_limit
158
162
  @mime_parts.each(&:close)
159
163
  raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
160
164
  end
161
165
  end
166
+
167
+ if part_limit && part_limit > 0
168
+ if @mime_parts.size >= part_limit
169
+ @mime_parts.each(&:close)
170
+ raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
171
+ end
172
+ end
162
173
  end
163
174
  end
164
175
 
165
176
  attr_reader :state
166
177
 
167
178
  def initialize(boundary, tempfile, bufsize, query_parser)
168
- @buf = String.new
169
-
170
179
  @query_parser = query_parser
171
180
  @params = query_parser.make_params
172
181
  @boundary = "--#{boundary}"
173
182
  @bufsize = bufsize
174
183
 
175
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
176
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
177
184
  @full_boundary = @boundary
178
185
  @end_boundary = @boundary + '--'
179
186
  @state = :FAST_FORWARD
180
187
  @mime_index = 0
181
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
182
194
  end
183
195
 
184
- def on_read content, eof
185
- handle_empty_content!(content, eof)
186
- @buf << content
196
+ def on_read(content)
197
+ handle_empty_content!(content)
198
+ @sbuf.concat content
187
199
  run_parser
188
200
  end
189
201
 
@@ -194,7 +206,6 @@ module Rack
194
206
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
195
207
  end
196
208
  end
197
-
198
209
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
199
210
  end
200
211
 
@@ -221,7 +232,7 @@ module Rack
221
232
  if consume_boundary
222
233
  @state = :MIME_HEAD
223
234
  else
224
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
235
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
225
236
  :want_read
226
237
  end
227
238
  end
@@ -229,19 +240,16 @@ module Rack
229
240
  def handle_consume_token
230
241
  tok = consume_boundary
231
242
  # break if we're at the end of a buffer, but not if it is the end of a field
232
- if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
233
- @state = :DONE
243
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
244
+ :DONE
234
245
  else
235
- @state = :MIME_HEAD
246
+ :MIME_HEAD
236
247
  end
237
248
  end
238
249
 
239
250
  def handle_mime_head
240
- if @buf.index(EOL + EOL)
241
- i = @buf.index(EOL+EOL)
242
- head = @buf.slice!(0, i+2) # First \r\n
243
- @buf.slice!(0, 2) # Second \r\n
244
-
251
+ if @sbuf.scan_until(@head_regex)
252
+ head = @sbuf[1]
245
253
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
246
254
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
247
255
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -252,7 +260,7 @@ module Rack
252
260
  filename = get_filename(head)
253
261
 
254
262
  if name.nil? || name.empty?
255
- name = filename || "#{content_type || TEXT_PLAIN}[]"
263
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
256
264
  end
257
265
 
258
266
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -263,16 +271,19 @@ module Rack
263
271
  end
264
272
 
265
273
  def handle_mime_body
266
- if i = @buf.index(rx)
267
- # Save the rest.
268
- @collector.on_mime_body @mime_index, @buf.slice!(0, i)
269
- @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
270
278
  @state = :CONSUME_TOKEN
271
279
  @mime_index += 1
272
280
  else
273
- # Save the read body part.
274
- if @rx_max_size < @buf.size
275
- @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
276
287
  end
277
288
  :want_read
278
289
  end
@@ -280,16 +291,13 @@ module Rack
280
291
 
281
292
  def full_boundary; @full_boundary; end
282
293
 
283
- def rx; @rx; end
284
-
285
294
  def consume_boundary
286
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
287
- read_buffer = $1
295
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
288
296
  case read_buffer.strip
289
297
  when full_boundary then return :BOUNDARY
290
298
  when @end_boundary then return :END_BOUNDARY
291
299
  end
292
- return if @buf.empty?
300
+ return if @sbuf.eos?
293
301
  end
294
302
  end
295
303
 
@@ -304,14 +312,15 @@ module Rack
304
312
  elsif filename = params['filename*']
305
313
  encoding, _, filename = filename.split("'", 3)
306
314
  end
307
- when BROKEN_QUOTED, BROKEN_UNQUOTED
315
+ when BROKEN
308
316
  filename = $1
317
+ filename = $1 if filename =~ /^"(.*)"$/
309
318
  end
310
319
 
311
320
  return unless filename
312
321
 
313
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
314
- filename = Utils.unescape(filename)
322
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
323
+ filename = Utils.unescape_path(filename)
315
324
  end
316
325
 
317
326
  filename.scrub!
@@ -327,7 +336,7 @@ module Rack
327
336
  filename
328
337
  end
329
338
 
330
- CHARSET = "charset"
339
+ CHARSET = "charset"
331
340
 
332
341
  def tag_multipart_encoding(filename, content_type, name, body)
333
342
  name = name.to_s
@@ -342,12 +351,12 @@ module Rack
342
351
  type_subtype = list.first
343
352
  type_subtype.strip!
344
353
  if TEXT_PLAIN == type_subtype
345
- rest = list.drop 1
354
+ rest = list.drop 1
346
355
  rest.each do |param|
347
- k,v = param.split('=', 2)
356
+ k, v = param.split('=', 2)
348
357
  k.strip!
349
358
  v.strip!
350
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
359
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
351
360
  encoding = Encoding.find v if k == CHARSET
352
361
  end
353
362
  end
@@ -357,11 +366,9 @@ module Rack
357
366
  body.force_encoding(encoding)
358
367
  end
359
368
 
360
-
361
- def handle_empty_content!(content, eof)
369
+ def handle_empty_content!(content)
362
370
  if content.nil? || content.empty?
363
- raise EOFError if eof
364
- return true
371
+ raise EOFError
365
372
  end
366
373
  end
367
374
  end
@@ -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,13 +16,12 @@ module Rack
14
16
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
15
17
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
- BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
18
- BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
19
+ BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
19
20
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
20
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
21
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
21
22
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
22
23
  # Updated definitions from RFC 2231
23
- ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
24
+ ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
24
25
  ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
25
26
  SECTION = /\*[0-9]+/
26
27
  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
 
@@ -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