rack 2.2.17 → 3.0.18

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.

Potentially problematic release.


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

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +292 -74
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +336 -0
  6. data/SPEC.rdoc +174 -126
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +2 -3
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -53
  12. data/lib/rack/auth/digest/params.rb +1 -54
  13. data/lib/rack/auth/digest/request.rb +1 -43
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +21 -3
  16. data/lib/rack/builder.rb +83 -63
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +22 -17
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +64 -0
  22. data/lib/rack/content_length.rb +12 -16
  23. data/lib/rack/content_type.rb +8 -5
  24. data/lib/rack/deflater.rb +40 -26
  25. data/lib/rack/directory.rb +9 -3
  26. data/lib/rack/etag.rb +17 -23
  27. data/lib/rack/events.rb +4 -0
  28. data/lib/rack/file.rb +2 -0
  29. data/lib/rack/files.rb +15 -17
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +758 -646
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +3 -8
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -300
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +155 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +127 -69
  43. data/lib/rack/multipart/uploaded_file.rb +4 -0
  44. data/lib/rack/multipart.rb +20 -40
  45. data/lib/rack/null_logger.rb +9 -0
  46. data/lib/rack/query_parser.rb +78 -46
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +224 -106
  50. data/lib/rack/response.rb +138 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +30 -25
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +8 -8
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +3 -1
  59. data/lib/rack/utils.rb +206 -180
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +13 -87
  62. metadata +15 -35
  63. data/README.rdoc +0 -347
  64. data/Rakefile +0 -130
  65. data/bin/rackup +0 -5
  66. data/contrib/rack.png +0 -0
  67. data/contrib/rack.svg +0 -150
  68. data/contrib/rack_logo.svg +0 -164
  69. data/contrib/rdoc.css +0 -412
  70. data/example/lobster.ru +0 -6
  71. data/example/protectedlobster.rb +0 -16
  72. data/example/protectedlobster.ru +0 -10
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ require_relative 'response'
6
+
7
+ module Rack
8
+ # Rack::MockResponse provides useful helpers for testing your apps.
9
+ # Usually, you don't create the MockResponse on your own, but use
10
+ # MockRequest.
11
+
12
+ class MockResponse < Rack::Response
13
+ begin
14
+ # Recent versions of the CGI gem may not provide `CGI::Cookie`.
15
+ require 'cgi/cookie'
16
+ Cookie = CGI::Cookie
17
+ rescue LoadError
18
+ class Cookie
19
+ attr_reader :name, :value, :path, :domain, :expires, :secure
20
+
21
+ def initialize(args)
22
+ @name = args["name"]
23
+ @value = args["value"]
24
+ @path = args["path"]
25
+ @domain = args["domain"]
26
+ @expires = args["expires"]
27
+ @secure = args["secure"]
28
+ end
29
+
30
+ def method_missing(method_name, *args, &block)
31
+ @value.send(method_name, *args, &block)
32
+ end
33
+ # :nocov:
34
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
35
+ # :nocov:
36
+
37
+ def respond_to_missing?(method_name, include_all = false)
38
+ @value.respond_to?(method_name, include_all) || super
39
+ end
40
+ end
41
+ end
42
+
43
+ class << self
44
+ alias [] new
45
+ end
46
+
47
+ # Headers
48
+ attr_reader :original_headers, :cookies
49
+
50
+ # Errors
51
+ attr_accessor :errors
52
+
53
+ def initialize(status, headers, body, errors = nil)
54
+ @original_headers = headers
55
+
56
+ if errors
57
+ @errors = errors.string if errors.respond_to?(:string)
58
+ else
59
+ @errors = ""
60
+ end
61
+
62
+ super(body, status, headers)
63
+
64
+ @cookies = parse_cookies_from_header
65
+ buffered_body!
66
+ end
67
+
68
+ def =~(other)
69
+ body =~ other
70
+ end
71
+
72
+ def match(other)
73
+ body.match other
74
+ end
75
+
76
+ def body
77
+ return @buffered_body if defined?(@buffered_body)
78
+
79
+ # FIXME: apparently users of MockResponse expect the return value of
80
+ # MockResponse#body to be a string. However, the real response object
81
+ # returns the body as a list.
82
+ #
83
+ # See spec_showstatus.rb:
84
+ #
85
+ # should "not replace existing messages" do
86
+ # ...
87
+ # res.body.should == "foo!"
88
+ # end
89
+ buffer = @buffered_body = String.new
90
+
91
+ @body.each do |chunk|
92
+ buffer << chunk
93
+ end
94
+
95
+ return buffer
96
+ end
97
+
98
+ def empty?
99
+ [201, 204, 304].include? status
100
+ end
101
+
102
+ def cookie(name)
103
+ cookies.fetch(name, nil)
104
+ end
105
+
106
+ private
107
+
108
+ def parse_cookies_from_header
109
+ cookies = Hash.new
110
+ if headers.has_key? 'set-cookie'
111
+ set_cookie_header = headers.fetch('set-cookie')
112
+ Array(set_cookie_header).each do |header_value|
113
+ header_value.split("\n").each do |cookie|
114
+ cookie_name, cookie_filling = cookie.split('=', 2)
115
+ cookie_attributes = identify_cookie_attributes cookie_filling
116
+ parsed_cookie = Cookie.new(
117
+ 'name' => cookie_name.strip,
118
+ 'value' => cookie_attributes.fetch('value'),
119
+ 'path' => cookie_attributes.fetch('path', nil),
120
+ 'domain' => cookie_attributes.fetch('domain', nil),
121
+ 'expires' => cookie_attributes.fetch('expires', nil),
122
+ 'secure' => cookie_attributes.fetch('secure', false)
123
+ )
124
+ cookies.store(cookie_name, parsed_cookie)
125
+ end
126
+ end
127
+ end
128
+ cookies
129
+ end
130
+
131
+ def identify_cookie_attributes(cookie_filling)
132
+ cookie_bits = cookie_filling.split(';')
133
+ cookie_attributes = Hash.new
134
+ cookie_attributes.store('value', Array(cookie_bits[0].strip))
135
+ cookie_bits.drop(1).each do |bit|
136
+ if bit.include? '='
137
+ cookie_attribute, attribute_value = bit.split('=', 2)
138
+ cookie_attributes.store(cookie_attribute.strip.downcase, attribute_value.strip)
139
+ end
140
+ if bit.include? 'secure'
141
+ cookie_attributes.store('secure', true)
142
+ end
143
+ end
144
+
145
+ if cookie_attributes.key? 'max-age'
146
+ cookie_attributes.store('expires', Time.now + cookie_attributes['max-age'].to_i)
147
+ elsif cookie_attributes.key? 'expires'
148
+ cookie_attributes.store('expires', Time.httpdate(cookie_attributes['expires']))
149
+ end
150
+
151
+ cookie_attributes
152
+ end
153
+
154
+ end
155
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'uploaded_file'
4
+
3
5
  module Rack
4
6
  module Multipart
5
7
  class Generator
@@ -74,12 +76,12 @@ module Rack
74
76
 
75
77
  def content_for_tempfile(io, file, name)
76
78
  length = ::File.stat(file.path).size if file.path
77
- filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
79
+ filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\""
78
80
  <<-EOF
79
81
  --#{MULTIPART_BOUNDARY}\r
80
- Content-Disposition: form-data; name="#{name}"#{filename}\r
81
- Content-Type: #{file.content_type}\r
82
- #{"Content-Length: #{length}\r\n" if length}\r
82
+ content-disposition: form-data; name="#{name}"#{filename}\r
83
+ content-type: #{file.content_type}\r
84
+ #{"content-length: #{length}\r\n" if length}\r
83
85
  #{io.read}\r
84
86
  EOF
85
87
  end
@@ -87,7 +89,7 @@ EOF
87
89
  def content_for_other(file, name)
88
90
  <<-EOF
89
91
  --#{MULTIPART_BOUNDARY}\r
90
- Content-Disposition: form-data; name="#{name}"\r
92
+ content-disposition: form-data; name="#{name}"\r
91
93
  \r
92
94
  #{file}\r
93
95
  EOF
@@ -2,24 +2,54 @@
2
2
 
3
3
  require 'strscan'
4
4
 
5
+ require_relative '../utils'
6
+
5
7
  module Rack
6
8
  module Multipart
7
9
  class MultipartPartLimitError < Errno::EMFILE; end
10
+
8
11
  class MultipartTotalPartLimitError < StandardError; end
9
12
 
10
- class Parser
11
- (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
13
+ # Use specific error class when parsing multipart request
14
+ # that ends early.
15
+ class EmptyContentError < ::EOFError; end
16
+
17
+ # Base class for multipart exceptions that do not subclass from
18
+ # other exception classes for backwards compatibility.
19
+ class Error < StandardError; end
20
+
21
+ EOL = "\r\n"
22
+ MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
23
+ TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
24
+ CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
25
+ VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
26
+ BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
27
+ MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
28
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
29
+ MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
30
+ # Updated definitions from RFC 2231
31
+ ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
32
+ ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
33
+ SECTION = /\*[0-9]+/
34
+ REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
35
+ REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
36
+ EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
37
+ EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
38
+ EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
39
+ EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
40
+ EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
41
+ EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
42
+ EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
43
+ DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
44
+ RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
12
45
 
46
+ class Parser
13
47
  BUFSIZE = 1_048_576
14
48
  TEXT_PLAIN = "text/plain"
15
49
  TEMPFILE_FACTORY = lambda { |filename, content_type|
16
- extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
17
-
18
- Tempfile.new(["RackMultipart", extension])
50
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
19
51
  }
20
52
 
21
- BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
-
23
53
  class BoundedIO # :nodoc:
24
54
  def initialize(io, content_length)
25
55
  @io = io
@@ -41,16 +71,12 @@ module Rack
41
71
  if str
42
72
  @cursor += str.bytesize
43
73
  else
44
- # Raise an error for mismatching Content-Length and actual contents
74
+ # Raise an error for mismatching content-length and actual contents
45
75
  raise EOFError, "bad content body"
46
76
  end
47
77
 
48
78
  str
49
79
  end
50
-
51
- def rewind
52
- @io.rewind
53
- end
54
80
  end
55
81
 
56
82
  MultipartInfo = Struct.new :params, :tmp_files
@@ -69,18 +95,17 @@ module Rack
69
95
  boundary = parse_boundary content_type
70
96
  return EMPTY unless boundary
71
97
 
98
+ if boundary.length > 70
99
+ # RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
100
+ # Most clients use no more than 55 characters.
101
+ raise Error, "multipart boundary size too large (#{boundary.length} characters)"
102
+ end
103
+
72
104
  io = BoundedIO.new(io, content_length) if content_length
73
- outbuf = String.new
74
105
 
75
106
  parser = new(boundary, tmpfile, bufsize, qp)
76
- parser.on_read io.read(bufsize, outbuf)
107
+ parser.parse(io)
77
108
 
78
- loop do
79
- break if parser.state == :DONE
80
- parser.on_read io.read(bufsize, outbuf)
81
- end
82
-
83
- io.rewind
84
109
  parser.result
85
110
  end
86
111
 
@@ -180,33 +205,47 @@ module Rack
180
205
  def initialize(boundary, tempfile, bufsize, query_parser)
181
206
  @query_parser = query_parser
182
207
  @params = query_parser.make_params
183
- @boundary = "--#{boundary}"
184
208
  @bufsize = bufsize
185
209
 
186
- @full_boundary = @boundary
187
- @end_boundary = @boundary + '--'
188
210
  @state = :FAST_FORWARD
189
211
  @mime_index = 0
190
212
  @collector = Collector.new tempfile
191
213
 
192
214
  @sbuf = StringScanner.new("".dup)
193
- @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
194
- @end_boundary_size = boundary.bytesize + 6 # (-- at start, -- at finish, EOL at end)
195
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
215
+ @body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
216
+ @end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish)
217
+ @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
196
218
  @head_regex = /(.*?#{EOL})#{EOL}/m
197
219
  end
198
220
 
199
- def on_read(content)
200
- handle_empty_content!(content)
201
- @sbuf.concat content
202
- run_parser
221
+ def parse(io)
222
+ outbuf = String.new
223
+ read_data(io, outbuf)
224
+
225
+ loop do
226
+ status =
227
+ case @state
228
+ when :FAST_FORWARD
229
+ handle_fast_forward
230
+ when :CONSUME_TOKEN
231
+ handle_consume_token
232
+ when :MIME_HEAD
233
+ handle_mime_head
234
+ when :MIME_BODY
235
+ handle_mime_body
236
+ else # when :DONE
237
+ return
238
+ end
239
+
240
+ read_data(io, outbuf) if status == :want_read
241
+ end
203
242
  end
204
243
 
205
244
  def result
206
245
  @collector.each do |part|
207
246
  part.get_data do |data|
208
247
  tag_multipart_encoding(part.filename, part.content_type, part.name, data)
209
- @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
248
+ @query_parser.normalize_params(@params, part.name, data)
210
249
  end
211
250
  end
212
251
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
@@ -214,34 +253,45 @@ module Rack
214
253
 
215
254
  private
216
255
 
217
- def run_parser
218
- loop do
219
- case @state
220
- when :FAST_FORWARD
221
- break if handle_fast_forward == :want_read
222
- when :CONSUME_TOKEN
223
- break if handle_consume_token == :want_read
224
- when :MIME_HEAD
225
- break if handle_mime_head == :want_read
226
- when :MIME_BODY
227
- break if handle_mime_body == :want_read
228
- when :DONE
229
- break
230
- end
231
- end
256
+ def dequote(str) # From WEBrick::HTTPUtils
257
+ ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
258
+ ret.gsub!(/\\(.)/, "\\1")
259
+ ret
260
+ end
261
+
262
+ def read_data(io, outbuf)
263
+ content = io.read(@bufsize, outbuf)
264
+ handle_empty_content!(content)
265
+ @sbuf.concat(content)
232
266
  end
233
267
 
268
+ # This handles the initial parser state. We read until we find the starting
269
+ # boundary, then we can transition to the next state. If we find the ending
270
+ # boundary, this is an invalid multipart upload, but keep scanning for opening
271
+ # boundary in that case. If no boundary found, we need to keep reading data
272
+ # and retry. It's highly unlikely the initial read will not consume the
273
+ # boundary. The client would have to deliberately craft a response
274
+ # with the opening boundary beyond the buffer size for that to happen.
234
275
  def handle_fast_forward
235
- tok = consume_boundary
276
+ while true
277
+ case consume_boundary
278
+ when :BOUNDARY
279
+ # found opening boundary, transition to next state
280
+ @state = :MIME_HEAD
281
+ return
282
+ when :END_BOUNDARY
283
+ # invalid multipart upload
284
+ if @sbuf.pos == @end_boundary_size && @sbuf.rest == EOL
285
+ # stop parsing a buffer if a buffer is only an end boundary.
286
+ @state = :DONE
287
+ return
288
+ end
236
289
 
237
- if tok == :END_BOUNDARY && @sbuf.pos == @end_boundary_size && @sbuf.eos?
238
- # stop parsing a buffer if a buffer is only an end boundary.
239
- @state = :DONE
240
- elsif tok
241
- @state = :MIME_HEAD
242
- else
243
- raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
244
- :want_read
290
+ # retry for opening boundary
291
+ else
292
+ # no boundary found, keep reading data
293
+ return :want_read
294
+ end
245
295
  end
246
296
  end
247
297
 
@@ -260,7 +310,7 @@ module Rack
260
310
  head = @sbuf[1]
261
311
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
262
312
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
263
- name = Rack::Auth::Digest::Params::dequote(name)
313
+ name = dequote(name)
264
314
  else
265
315
  name = head[MULTIPART_CONTENT_ID, 1]
266
316
  end
@@ -297,15 +347,16 @@ module Rack
297
347
  end
298
348
  end
299
349
 
300
- def full_boundary; @full_boundary; end
301
-
350
+ # Scan until the we find the start or end of the boundary.
351
+ # If we find it, return the appropriate symbol for the start or
352
+ # end of the boundary. If we don't find the start or end of the
353
+ # boundary, clear the buffer and return nil.
302
354
  def consume_boundary
303
- while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
304
- case read_buffer.strip
305
- when full_boundary then return :BOUNDARY
306
- when @end_boundary then return :END_BOUNDARY
307
- end
308
- return if @sbuf.eos?
355
+ if read_buffer = @sbuf.scan_until(@body_regex)
356
+ read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY
357
+ else
358
+ @sbuf.terminate
359
+ nil
309
360
  end
310
361
  end
311
362
 
@@ -315,10 +366,10 @@ module Rack
315
366
  when RFC2183
316
367
  params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
317
368
 
318
- if filename = params['filename']
319
- filename = $1 if filename =~ /^"(.*)"$/
320
- elsif filename = params['filename*']
369
+ if filename = params['filename*']
321
370
  encoding, _, filename = filename.split("'", 3)
371
+ elsif filename = params['filename']
372
+ filename = $1 if filename =~ /^"(.*)"$/
322
373
  end
323
374
  when BROKEN
324
375
  filename = $1
@@ -345,6 +396,7 @@ module Rack
345
396
  end
346
397
 
347
398
  CHARSET = "charset"
399
+ deprecate_constant :CHARSET
348
400
 
349
401
  def tag_multipart_encoding(filename, content_type, name, body)
350
402
  name = name.to_s
@@ -365,7 +417,13 @@ module Rack
365
417
  k.strip!
366
418
  v.strip!
367
419
  v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
368
- encoding = Encoding.find v if k == CHARSET
420
+ if k == "charset"
421
+ encoding = begin
422
+ Encoding.find v
423
+ rescue ArgumentError
424
+ Encoding::BINARY
425
+ end
426
+ end
369
427
  end
370
428
  end
371
429
  end
@@ -376,7 +434,7 @@ module Rack
376
434
 
377
435
  def handle_empty_content!(content)
378
436
  if content.nil? || content.empty?
379
- raise EOFError
437
+ raise EmptyContentError
380
438
  end
381
439
  end
382
440
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
3
6
  module Rack
4
7
  module Multipart
5
8
  class UploadedFile
9
+
6
10
  # The filename, *not* including the path, of the "uploaded" file
7
11
  attr_reader :original_filename
8
12
 
@@ -1,64 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  require_relative 'multipart/parser'
7
+ require_relative 'multipart/generator'
4
8
 
5
9
  module Rack
6
10
  # A multipart form data parser, adapted from IOWA.
7
11
  #
8
12
  # Usually, Rack::Request#POST takes care of calling this.
9
13
  module Multipart
10
- autoload :UploadedFile, 'rack/multipart/uploaded_file'
11
- autoload :Generator, 'rack/multipart/generator'
12
-
13
- EOL = "\r\n"
14
14
  MULTIPART_BOUNDARY = "AaB03x"
15
- MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
16
- TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
17
- CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
18
- VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
19
- BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
20
- MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
21
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
22
- MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
23
- # Updated definitions from RFC 2231
24
- ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
- ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
26
- SECTION = /\*[0-9]+/
27
- REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
28
- REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
29
- EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
30
- EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
31
- EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
32
- EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
33
- EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
34
- EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
35
- EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
36
- DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
37
- RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
38
15
 
39
16
  class << self
40
17
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
41
- extract_multipart Rack::Request.new(env), params
42
- end
18
+ io = env[RACK_INPUT]
19
+
20
+ if content_length = env['CONTENT_LENGTH']
21
+ content_length = content_length.to_i
22
+ end
43
23
 
44
- def extract_multipart(req, params = Rack::Utils.default_query_parser)
45
- io = req.get_header(RACK_INPUT)
46
- io.rewind
47
- content_length = req.content_length
48
- content_length = content_length.to_i if content_length
24
+ content_type = env['CONTENT_TYPE']
49
25
 
50
- tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
51
- bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
26
+ tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
27
+ bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
52
28
 
53
- info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
54
- req.set_header(RACK_TEMPFILES, info.tmp_files)
55
- info.params
29
+ info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
30
+ env[RACK_TEMPFILES] = info.tmp_files
31
+
32
+ return info.params
33
+ end
34
+
35
+ def extract_multipart(request, params = Rack::Utils.default_query_parser)
36
+ parse_multipart(request.env)
56
37
  end
57
38
 
58
39
  def build_multipart(params, first = true)
59
40
  Generator.new(params, first).dump
60
41
  end
61
42
  end
62
-
63
43
  end
64
44
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+
3
5
  module Rack
4
6
  class NullLogger
5
7
  def initialize(app)
@@ -22,6 +24,11 @@ module Rack
22
24
  def warn? ; end
23
25
  def error? ; end
24
26
  def fatal? ; end
27
+ def debug! ; end
28
+ def error! ; end
29
+ def fatal! ; end
30
+ def info! ; end
31
+ def warn! ; end
25
32
  def level ; end
26
33
  def progname ; end
27
34
  def datetime_format ; end
@@ -34,6 +41,8 @@ module Rack
34
41
  def sev_threshold=(sev_threshold); end
35
42
  def close ; end
36
43
  def add(severity, message = nil, progname = nil, &block); end
44
+ def log(severity, message = nil, progname = nil, &block); end
37
45
  def <<(msg); end
46
+ def reopen(logdev = nil); end
38
47
  end
39
48
  end