rack 1.0.1 → 2.2.21
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +829 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +355 -0
- data/Rakefile +64 -98
- data/SPEC.rdoc +292 -0
- data/bin/rackup +3 -174
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +164 -111
- data/contrib/rdoc.css +412 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +7 -5
- data/lib/rack/auth/abstract/request.rb +12 -2
- data/lib/rack/auth/basic.rb +6 -4
- data/lib/rack/auth/digest/md5.rb +20 -13
- data/lib/rack/auth/digest/nonce.rb +6 -4
- data/lib/rack/auth/digest/params.rb +11 -12
- data/lib/rack/auth/digest/request.rb +15 -12
- data/lib/rack/body_proxy.rb +45 -0
- data/lib/rack/builder.rb +213 -19
- data/lib/rack/cascade.rb +50 -18
- data/lib/rack/chunked.rb +95 -27
- data/lib/rack/common_logger.rb +84 -0
- data/lib/rack/conditional_get.rb +83 -0
- data/lib/rack/config.rb +22 -0
- data/lib/rack/content_length.rb +19 -10
- data/lib/rack/content_type.rb +12 -5
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +90 -42
- data/lib/rack/directory.rb +118 -72
- data/lib/rack/etag.rb +77 -0
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -85
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +22 -24
- data/lib/rack/handler/fastcgi.rb +34 -24
- data/lib/rack/handler/lsws.rb +16 -15
- data/lib/rack/handler/scgi.rb +38 -29
- data/lib/rack/handler/thin.rb +23 -7
- data/lib/rack/handler/webrick.rb +95 -37
- data/lib/rack/handler.rb +49 -14
- data/lib/rack/head.rb +19 -13
- data/lib/rack/lint.rb +466 -217
- data/lib/rack/lobster.rb +19 -14
- data/lib/rack/lock.rb +24 -8
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/media_type.rb +53 -0
- data/lib/rack/method_override.rb +52 -0
- data/lib/rack/mime.rb +652 -171
- data/lib/rack/mock.rb +203 -64
- data/lib/rack/multipart/generator.rb +97 -0
- data/lib/rack/multipart/parser.rb +440 -0
- data/lib/rack/multipart/uploaded_file.rb +41 -0
- data/lib/rack/multipart.rb +64 -0
- data/lib/rack/null_logger.rb +39 -0
- data/lib/rack/query_parser.rb +266 -0
- data/lib/rack/recursive.rb +18 -11
- data/lib/rack/reloader.rb +13 -5
- data/lib/rack/request.rb +607 -191
- data/lib/rack/response.rb +245 -110
- data/lib/rack/rewindable_input.rb +15 -21
- data/lib/rack/runtime.rb +34 -0
- data/lib/rack/sendfile.rb +192 -0
- data/lib/rack/server.rb +466 -0
- data/lib/rack/session/abstract/id.rb +449 -68
- data/lib/rack/session/cookie.rb +159 -47
- data/lib/rack/session/memcache.rb +4 -103
- data/lib/rack/session/pool.rb +39 -49
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
- data/lib/rack/static.rb +164 -14
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +60 -18
- data/lib/rack/utils.rb +508 -277
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +85 -34
- data/rack.gemspec +43 -51
- metadata +119 -207
- data/KNOWN-ISSUES +0 -18
- data/RDOX +0 -428
- data/README +0 -364
- data/SPEC +0 -164
- data/lib/rack/adapter/camping.rb +0 -22
- data/lib/rack/auth/openid.rb +0 -480
- data/lib/rack/commonlogger.rb +0 -61
- data/lib/rack/conditionalget.rb +0 -47
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -87
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/methodoverride.rb +0 -27
- data/lib/rack/showexceptions.rb +0 -349
- data/test/cgi/lighttpd.conf +0 -20
- data/test/cgi/test +0 -9
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -7
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +0 -10
- data/test/multipart/ie +0 -6
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -10
- data/test/spec_rack_auth_basic.rb +0 -73
- data/test/spec_rack_auth_digest.rb +0 -226
- data/test/spec_rack_auth_openid.rb +0 -84
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -51
- data/test/spec_rack_cascade.rb +0 -50
- data/test/spec_rack_cgi.rb +0 -89
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -32
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_directory.rb +0 -61
- data/test/spec_rack_fastcgi.rb +0 -89
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lint.rb +0 -521
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_mock.rb +0 -157
- data/test/spec_rack_mongrel.rb +0 -189
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -504
- data/test/spec_rack_response.rb +0 -218
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_session_cookie.rb +0 -82
- data/test/spec_rack_session_memcache.rb +0 -240
- data/test/spec_rack_session_pool.rb +0 -172
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_thin.rb +0 -91
- data/test/spec_rack_urlmap.rb +0 -185
- data/test/spec_rack_utils.rb +0 -387
- data/test/spec_rack_webrick.rb +0 -130
- data/test/testrequest.rb +0 -57
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
5
|
+
module Rack
|
|
6
|
+
module Multipart
|
|
7
|
+
class MultipartPartLimitError < Errno::EMFILE; end
|
|
8
|
+
class MultipartTotalPartLimitError < StandardError; end
|
|
9
|
+
|
|
10
|
+
class Parser
|
|
11
|
+
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
|
12
|
+
|
|
13
|
+
BUFSIZE = 1_048_576
|
|
14
|
+
TEXT_PLAIN = "text/plain"
|
|
15
|
+
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
|
16
|
+
extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
|
|
17
|
+
|
|
18
|
+
Tempfile.new(["RackMultipart", extension])
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
|
22
|
+
|
|
23
|
+
BOUNDARY_START_LIMIT = 16 * 1024
|
|
24
|
+
private_constant :BOUNDARY_START_LIMIT
|
|
25
|
+
|
|
26
|
+
MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
|
|
27
|
+
private_constant :MIME_HEADER_BYTESIZE_LIMIT
|
|
28
|
+
|
|
29
|
+
env_int = lambda do |key, val|
|
|
30
|
+
if str_val = ENV[key]
|
|
31
|
+
begin
|
|
32
|
+
val = Integer(str_val, 10)
|
|
33
|
+
rescue ArgumentError
|
|
34
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
val
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
|
|
42
|
+
private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
|
43
|
+
|
|
44
|
+
class BoundedIO # :nodoc:
|
|
45
|
+
def initialize(io, content_length)
|
|
46
|
+
@io = io
|
|
47
|
+
@content_length = content_length
|
|
48
|
+
@cursor = 0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read(size, outbuf = nil)
|
|
52
|
+
return if @cursor >= @content_length
|
|
53
|
+
|
|
54
|
+
left = @content_length - @cursor
|
|
55
|
+
|
|
56
|
+
str = if left < size
|
|
57
|
+
@io.read left, outbuf
|
|
58
|
+
else
|
|
59
|
+
@io.read size, outbuf
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if str
|
|
63
|
+
@cursor += str.bytesize
|
|
64
|
+
else
|
|
65
|
+
# Raise an error for mismatching Content-Length and actual contents
|
|
66
|
+
raise EOFError, "bad content body"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
str
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rewind
|
|
73
|
+
@io.rewind
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
MultipartInfo = Struct.new :params, :tmp_files
|
|
78
|
+
EMPTY = MultipartInfo.new(nil, [])
|
|
79
|
+
|
|
80
|
+
def self.parse_boundary(content_type)
|
|
81
|
+
return unless content_type
|
|
82
|
+
data = content_type.match(MULTIPART)
|
|
83
|
+
return unless data
|
|
84
|
+
data[1]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
|
88
|
+
return EMPTY if 0 == content_length
|
|
89
|
+
|
|
90
|
+
boundary = parse_boundary content_type
|
|
91
|
+
return EMPTY unless boundary
|
|
92
|
+
|
|
93
|
+
io = BoundedIO.new(io, content_length) if content_length
|
|
94
|
+
outbuf = String.new
|
|
95
|
+
|
|
96
|
+
parser = new(boundary, tmpfile, bufsize, qp)
|
|
97
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
98
|
+
|
|
99
|
+
loop do
|
|
100
|
+
break if parser.state == :DONE
|
|
101
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
io.rewind
|
|
105
|
+
parser.result
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Collector
|
|
109
|
+
class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
|
|
110
|
+
def get_data
|
|
111
|
+
data = body
|
|
112
|
+
if filename == ""
|
|
113
|
+
# filename is blank which means no file has been selected
|
|
114
|
+
return
|
|
115
|
+
elsif filename
|
|
116
|
+
body.rewind if body.respond_to?(:rewind)
|
|
117
|
+
|
|
118
|
+
# Take the basename of the upload's original filename.
|
|
119
|
+
# This handles the full Windows paths given by Internet Explorer
|
|
120
|
+
# (and perhaps other broken user agents) without affecting
|
|
121
|
+
# those which give the lone filename.
|
|
122
|
+
fn = filename.split(/[\/\\]/).last
|
|
123
|
+
|
|
124
|
+
data = { filename: fn, type: content_type,
|
|
125
|
+
name: name, tempfile: body, head: head }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
yield data
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class BufferPart < MimePart
|
|
133
|
+
def file?; false; end
|
|
134
|
+
def close; end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class TempfilePart < MimePart
|
|
138
|
+
def file?; true; end
|
|
139
|
+
def close; body.close; end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
include Enumerable
|
|
143
|
+
|
|
144
|
+
def initialize(tempfile)
|
|
145
|
+
@tempfile = tempfile
|
|
146
|
+
@mime_parts = []
|
|
147
|
+
@open_files = 0
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def each
|
|
151
|
+
@mime_parts.each { |part| yield part }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
|
155
|
+
if filename
|
|
156
|
+
body = @tempfile.call(filename, content_type)
|
|
157
|
+
body.binmode if body.respond_to?(:binmode)
|
|
158
|
+
klass = TempfilePart
|
|
159
|
+
@open_files += 1
|
|
160
|
+
else
|
|
161
|
+
body = String.new
|
|
162
|
+
klass = BufferPart
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
|
166
|
+
|
|
167
|
+
check_part_limits
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def on_mime_body(mime_index, content)
|
|
171
|
+
@mime_parts[mime_index].body << content
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def on_mime_finish(mime_index)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def check_part_limits
|
|
180
|
+
file_limit = Utils.multipart_file_limit
|
|
181
|
+
part_limit = Utils.multipart_total_part_limit
|
|
182
|
+
|
|
183
|
+
if file_limit && file_limit > 0
|
|
184
|
+
if @open_files >= file_limit
|
|
185
|
+
@mime_parts.each(&:close)
|
|
186
|
+
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if part_limit && part_limit > 0
|
|
191
|
+
if @mime_parts.size >= part_limit
|
|
192
|
+
@mime_parts.each(&:close)
|
|
193
|
+
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
attr_reader :state
|
|
200
|
+
|
|
201
|
+
def initialize(boundary, tempfile, bufsize, query_parser)
|
|
202
|
+
@query_parser = query_parser
|
|
203
|
+
@params = query_parser.make_params
|
|
204
|
+
@boundary = "--#{boundary}"
|
|
205
|
+
@bufsize = bufsize
|
|
206
|
+
|
|
207
|
+
@full_boundary = @boundary
|
|
208
|
+
@end_boundary = @boundary + '--'
|
|
209
|
+
@state = :FAST_FORWARD
|
|
210
|
+
@mime_index = 0
|
|
211
|
+
@body_retained = nil
|
|
212
|
+
@retained_size = 0
|
|
213
|
+
@collector = Collector.new tempfile
|
|
214
|
+
|
|
215
|
+
@sbuf = StringScanner.new("".dup)
|
|
216
|
+
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
|
217
|
+
@end_boundary_size = boundary.bytesize + 6 # (-- at start, -- at finish, EOL at end)
|
|
218
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
|
219
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def on_read(content)
|
|
223
|
+
handle_empty_content!(content)
|
|
224
|
+
@sbuf.concat content
|
|
225
|
+
run_parser
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def result
|
|
229
|
+
@collector.each do |part|
|
|
230
|
+
part.get_data do |data|
|
|
231
|
+
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
|
232
|
+
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
def run_parser
|
|
241
|
+
loop do
|
|
242
|
+
case @state
|
|
243
|
+
when :FAST_FORWARD
|
|
244
|
+
break if handle_fast_forward == :want_read
|
|
245
|
+
when :CONSUME_TOKEN
|
|
246
|
+
break if handle_consume_token == :want_read
|
|
247
|
+
when :MIME_HEAD
|
|
248
|
+
break if handle_mime_head == :want_read
|
|
249
|
+
when :MIME_BODY
|
|
250
|
+
break if handle_mime_body == :want_read
|
|
251
|
+
when :DONE
|
|
252
|
+
break
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def handle_fast_forward
|
|
258
|
+
tok = consume_boundary
|
|
259
|
+
|
|
260
|
+
if tok == :END_BOUNDARY && @sbuf.pos == @end_boundary_size && @sbuf.eos?
|
|
261
|
+
# stop parsing a buffer if a buffer is only an end boundary.
|
|
262
|
+
@state = :DONE
|
|
263
|
+
elsif tok
|
|
264
|
+
@state = :MIME_HEAD
|
|
265
|
+
else
|
|
266
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
|
267
|
+
|
|
268
|
+
# We raise if we don't find the multipart boundary, to avoid unbounded memory
|
|
269
|
+
# buffering. Note that the actual limit is the higher of 16KB and the buffer size (1MB by default)
|
|
270
|
+
raise EOFError, "multipart boundary not found within limit" if @sbuf.string.bytesize > BOUNDARY_START_LIMIT
|
|
271
|
+
|
|
272
|
+
# no boundary found, keep reading data
|
|
273
|
+
return :want_read
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def handle_consume_token
|
|
278
|
+
tok = consume_boundary
|
|
279
|
+
# break if we're at the end of a buffer, but not if it is the end of a field
|
|
280
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
|
281
|
+
:DONE
|
|
282
|
+
else
|
|
283
|
+
:MIME_HEAD
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def handle_mime_head
|
|
288
|
+
if @sbuf.scan_until(@head_regex)
|
|
289
|
+
head = @sbuf[1]
|
|
290
|
+
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
291
|
+
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
|
292
|
+
name = Rack::Auth::Digest::Params::dequote(name)
|
|
293
|
+
else
|
|
294
|
+
name = head[MULTIPART_CONTENT_ID, 1]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
filename = get_filename(head)
|
|
298
|
+
|
|
299
|
+
if name.nil? || name.empty?
|
|
300
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Mime part head data is retained for both TempfilePart and BufferPart
|
|
304
|
+
# for the entireity of the parse, even though it isn't used for BufferPart.
|
|
305
|
+
update_retained_size(head.bytesize)
|
|
306
|
+
|
|
307
|
+
# If a filename is given, a TempfilePart will be used, so the body will
|
|
308
|
+
# not be buffered in memory. However, if a filename is not given, a BufferPart
|
|
309
|
+
# will be used, and the body will be buffered in memory.
|
|
310
|
+
@body_retained = !filename
|
|
311
|
+
|
|
312
|
+
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
|
313
|
+
@state = :MIME_BODY
|
|
314
|
+
else
|
|
315
|
+
# We raise if the mime part header is too large, to avoid unbounded memory
|
|
316
|
+
# buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
|
|
317
|
+
raise EOFError, "multipart mime part header too large" if @sbuf.rest.bytesize > MIME_HEADER_BYTESIZE_LIMIT
|
|
318
|
+
|
|
319
|
+
return :want_read
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def handle_mime_body
|
|
324
|
+
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
|
325
|
+
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
|
326
|
+
update_retained_size(body.bytesize) if @body_retained
|
|
327
|
+
@collector.on_mime_body @mime_index, body
|
|
328
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
|
329
|
+
@state = :CONSUME_TOKEN
|
|
330
|
+
@mime_index += 1
|
|
331
|
+
else
|
|
332
|
+
# Save what we have so far
|
|
333
|
+
if @rx_max_size < @sbuf.rest_size
|
|
334
|
+
delta = @sbuf.rest_size - @rx_max_size
|
|
335
|
+
body = @sbuf.peek(delta)
|
|
336
|
+
update_retained_size(body.bytesize) if @body_retained
|
|
337
|
+
@collector.on_mime_body @mime_index, body
|
|
338
|
+
@sbuf.pos += delta
|
|
339
|
+
@sbuf.string = @sbuf.rest
|
|
340
|
+
end
|
|
341
|
+
:want_read
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def full_boundary; @full_boundary; end
|
|
346
|
+
|
|
347
|
+
def update_retained_size(size)
|
|
348
|
+
@retained_size += size
|
|
349
|
+
if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
|
350
|
+
raise EOFError, "multipart data over retained size limit"
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Scan until the we find the start or end of the boundary.
|
|
355
|
+
# If we find it, return the appropriate symbol for the start or
|
|
356
|
+
# end of the boundary. If we don't find the start or end of the
|
|
357
|
+
# boundary, clear the buffer and return nil.
|
|
358
|
+
def consume_boundary
|
|
359
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
|
360
|
+
case read_buffer.strip
|
|
361
|
+
when full_boundary then return :BOUNDARY
|
|
362
|
+
when @end_boundary then return :END_BOUNDARY
|
|
363
|
+
end
|
|
364
|
+
return if @sbuf.eos?
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def get_filename(head)
|
|
369
|
+
filename = nil
|
|
370
|
+
case head
|
|
371
|
+
when RFC2183
|
|
372
|
+
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
|
373
|
+
|
|
374
|
+
if filename = params['filename']
|
|
375
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
|
376
|
+
elsif filename = params['filename*']
|
|
377
|
+
encoding, _, filename = filename.split("'", 3)
|
|
378
|
+
end
|
|
379
|
+
when BROKEN
|
|
380
|
+
filename = $1
|
|
381
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
return unless filename
|
|
385
|
+
|
|
386
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
|
387
|
+
filename = Utils.unescape_path(filename)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
filename.scrub!
|
|
391
|
+
|
|
392
|
+
if filename !~ /\\[^\\"]/
|
|
393
|
+
filename = filename.gsub(/\\(.)/, '\1')
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
if encoding
|
|
397
|
+
filename.force_encoding ::Encoding.find(encoding)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
filename
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
CHARSET = "charset"
|
|
404
|
+
|
|
405
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
|
406
|
+
name = name.to_s
|
|
407
|
+
encoding = Encoding::UTF_8
|
|
408
|
+
|
|
409
|
+
name.force_encoding(encoding)
|
|
410
|
+
|
|
411
|
+
return if filename
|
|
412
|
+
|
|
413
|
+
if content_type
|
|
414
|
+
list = content_type.split(';')
|
|
415
|
+
type_subtype = list.first
|
|
416
|
+
type_subtype.strip!
|
|
417
|
+
if TEXT_PLAIN == type_subtype
|
|
418
|
+
rest = list.drop 1
|
|
419
|
+
rest.each do |param|
|
|
420
|
+
k, v = param.split('=', 2)
|
|
421
|
+
k.strip!
|
|
422
|
+
v.strip!
|
|
423
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
|
424
|
+
encoding = Encoding.find v if k == CHARSET
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
name.force_encoding(encoding)
|
|
430
|
+
body.force_encoding(encoding)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def handle_empty_content!(content)
|
|
434
|
+
if content.nil? || content.empty?
|
|
435
|
+
raise EOFError
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
module Multipart
|
|
5
|
+
class UploadedFile
|
|
6
|
+
# The filename, *not* including the path, of the "uploaded" file
|
|
7
|
+
attr_reader :original_filename
|
|
8
|
+
|
|
9
|
+
# The content type of the "uploaded" file
|
|
10
|
+
attr_accessor :content_type
|
|
11
|
+
|
|
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
|
|
24
|
+
@content_type = content_type
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def path
|
|
28
|
+
@tempfile.path if @tempfile.respond_to?(:path)
|
|
29
|
+
end
|
|
30
|
+
alias_method :local_path, :path
|
|
31
|
+
|
|
32
|
+
def respond_to?(*args)
|
|
33
|
+
super or @tempfile.respond_to?(*args)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def method_missing(method_name, *args, &block) #:nodoc:
|
|
37
|
+
@tempfile.__send__(method_name, *args, &block)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'multipart/parser'
|
|
4
|
+
|
|
5
|
+
module Rack
|
|
6
|
+
# A multipart form data parser, adapted from IOWA.
|
|
7
|
+
#
|
|
8
|
+
# Usually, Rack::Request#POST takes care of calling this.
|
|
9
|
+
module Multipart
|
|
10
|
+
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
|
11
|
+
autoload :Generator, 'rack/multipart/generator'
|
|
12
|
+
|
|
13
|
+
EOL = "\r\n"
|
|
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
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
|
41
|
+
extract_multipart Rack::Request.new(env), params
|
|
42
|
+
end
|
|
43
|
+
|
|
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
|
|
49
|
+
|
|
50
|
+
tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
|
|
51
|
+
bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
|
|
52
|
+
|
|
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
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_multipart(params, first = true)
|
|
59
|
+
Generator.new(params, first).dump
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
class NullLogger
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
env[RACK_LOGGER] = self
|
|
11
|
+
@app.call(env)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def info(progname = nil, &block); end
|
|
15
|
+
def debug(progname = nil, &block); end
|
|
16
|
+
def warn(progname = nil, &block); end
|
|
17
|
+
def error(progname = nil, &block); end
|
|
18
|
+
def fatal(progname = nil, &block); end
|
|
19
|
+
def unknown(progname = nil, &block); end
|
|
20
|
+
def info? ; end
|
|
21
|
+
def debug? ; end
|
|
22
|
+
def warn? ; end
|
|
23
|
+
def error? ; end
|
|
24
|
+
def fatal? ; end
|
|
25
|
+
def level ; end
|
|
26
|
+
def progname ; end
|
|
27
|
+
def datetime_format ; end
|
|
28
|
+
def formatter ; end
|
|
29
|
+
def sev_threshold ; end
|
|
30
|
+
def level=(level); end
|
|
31
|
+
def progname=(progname); end
|
|
32
|
+
def datetime_format=(datetime_format); end
|
|
33
|
+
def formatter=(formatter); end
|
|
34
|
+
def sev_threshold=(sev_threshold); end
|
|
35
|
+
def close ; end
|
|
36
|
+
def add(severity, message = nil, progname = nil, &block); end
|
|
37
|
+
def <<(msg); end
|
|
38
|
+
end
|
|
39
|
+
end
|