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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +292 -74
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +336 -0
- data/SPEC.rdoc +174 -126
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +2 -3
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -53
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +21 -3
- data/lib/rack/builder.rb +83 -63
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +22 -17
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +64 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +17 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +758 -646
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +3 -8
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -300
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +155 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +127 -69
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +78 -46
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +224 -106
- data/lib/rack/response.rb +138 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +3 -1
- data/lib/rack/utils.rb +206 -180
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +13 -87
- metadata +15 -35
- data/README.rdoc +0 -347
- data/Rakefile +0 -130
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -90
- 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.
|
79
|
+
filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\""
|
78
80
|
<<-EOF
|
79
81
|
--#{MULTIPART_BOUNDARY}\r
|
80
|
-
|
81
|
-
|
82
|
-
#{"
|
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
|
-
|
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
|
11
|
-
|
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
|
-
|
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
|
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.
|
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})
|
194
|
-
@end_boundary_size = boundary.bytesize +
|
195
|
-
@rx_max_size =
|
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
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
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
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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 =
|
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
|
-
|
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
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
-
|
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
|
437
|
+
raise EmptyContentError
|
380
438
|
end
|
381
439
|
end
|
382
440
|
end
|
data/lib/rack/multipart.rb
CHANGED
@@ -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
|
-
|
42
|
-
|
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
|
-
|
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 =
|
51
|
-
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
|
54
|
-
|
55
|
-
|
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
|
data/lib/rack/null_logger.rb
CHANGED
@@ -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
|