rack 1.4.7 → 2.1.4
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 +5 -5
- data/CHANGELOG.md +77 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +122 -456
- data/Rakefile +32 -31
- data/SPEC +119 -29
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- 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 +8 -6
- data/lib/rack/auth/basic.rb +5 -2
- data/lib/rack/auth/digest/md5.rb +10 -8
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +4 -2
- data/lib/rack/body_proxy.rb +11 -9
- data/lib/rack/builder.rb +63 -20
- data/lib/rack/cascade.rb +10 -9
- data/lib/rack/chunked.rb +45 -11
- data/lib/rack/{commonlogger.rb → common_logger.rb} +24 -15
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -6
- data/lib/rack/config.rb +7 -0
- data/lib/rack/content_length.rb +12 -6
- data/lib/rack/content_type.rb +4 -2
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +73 -42
- data/lib/rack/directory.rb +77 -56
- data/lib/rack/etag.rb +25 -13
- data/lib/rack/events.rb +156 -0
- data/lib/rack/file.rb +4 -143
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +18 -17
- data/lib/rack/handler/fastcgi.rb +21 -17
- data/lib/rack/handler/lsws.rb +14 -12
- data/lib/rack/handler/scgi.rb +27 -21
- data/lib/rack/handler/thin.rb +19 -5
- data/lib/rack/handler/webrick.rb +66 -24
- data/lib/rack/handler.rb +29 -19
- data/lib/rack/head.rb +21 -14
- data/lib/rack/lint.rb +259 -65
- data/lib/rack/lobster.rb +17 -10
- data/lib/rack/lock.rb +19 -10
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/method_override.rb +52 -0
- data/lib/rack/mime.rb +43 -6
- data/lib/rack/mock.rb +109 -44
- data/lib/rack/multipart/generator.rb +11 -12
- data/lib/rack/multipart/parser.rb +302 -115
- data/lib/rack/multipart/uploaded_file.rb +4 -3
- data/lib/rack/multipart.rb +40 -9
- data/lib/rack/null_logger.rb +39 -0
- data/lib/rack/query_parser.rb +218 -0
- data/lib/rack/recursive.rb +14 -11
- data/lib/rack/reloader.rb +12 -5
- data/lib/rack/request.rb +484 -270
- data/lib/rack/response.rb +196 -77
- data/lib/rack/rewindable_input.rb +5 -14
- data/lib/rack/runtime.rb +13 -6
- data/lib/rack/sendfile.rb +44 -20
- data/lib/rack/server.rb +175 -61
- data/lib/rack/session/abstract/id.rb +276 -133
- data/lib/rack/session/cookie.rb +75 -40
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +24 -18
- data/lib/rack/show_exceptions.rb +392 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +11 -9
- data/lib/rack/static.rb +65 -38
- data/lib/rack/tempfile_reaper.rb +24 -0
- data/lib/rack/urlmap.rb +40 -15
- data/lib/rack/utils.rb +316 -285
- data/lib/rack.rb +78 -23
- data/rack.gemspec +26 -19
- metadata +44 -209
- data/KNOWN-ISSUES +0 -30
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -100
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/methodoverride.rb +0 -33
- data/lib/rack/nulllogger.rb +0 -18
- data/lib/rack/showexceptions.rb +0 -378
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/lighttpd.errors +0 -1
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth.rb +0 -57
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -69
- data/test/spec_builder.rb +0 -207
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -87
- data/test/spec_commonlogger.rb +0 -57
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -187
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -98
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -200
- data/test/spec_handler.rb +0 -59
- data/test/spec_head.rb +0 -48
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -167
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -72
- data/test/spec_mock.rb +0 -269
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -479
- data/test/spec_nulllogger.rb +0 -23
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -955
- data/test/spec_response.rb +0 -313
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -90
- data/test/spec_server.rb +0 -121
- data/test/spec_session_abstract_id.rb +0 -43
- data/test/spec_session_cookie.rb +0 -361
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -92
- data/test/spec_showstatus.rb +0 -84
- data/test/spec_static.rb +0 -145
- data/test/spec_thin.rb +0 -86
- data/test/spec_urlmap.rb +0 -213
- data/test/spec_utils.rb +0 -554
- data/test/spec_webrick.rb +0 -143
- data/test/static/another/index.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
|
@@ -1,184 +1,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rack/utils'
|
|
4
|
+
require 'strscan'
|
|
5
|
+
require 'rack/core_ext/regexp'
|
|
2
6
|
|
|
3
7
|
module Rack
|
|
4
8
|
module Multipart
|
|
5
|
-
class
|
|
9
|
+
class MultipartPartLimitError < Errno::EMFILE; end
|
|
6
10
|
|
|
7
11
|
class Parser
|
|
8
|
-
|
|
12
|
+
using ::Rack::RegexpExtensions
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
BUFSIZE = 1_048_576
|
|
15
|
+
TEXT_PLAIN = "text/plain"
|
|
16
|
+
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
|
17
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
class BoundedIO # :nodoc:
|
|
23
|
+
def initialize(io, content_length)
|
|
24
|
+
@io = io
|
|
25
|
+
@content_length = content_length
|
|
26
|
+
@cursor = 0
|
|
27
|
+
end
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
def read(size, outbuf = nil)
|
|
30
|
+
return if @cursor >= @content_length
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
loop do
|
|
32
|
+
left = @content_length - @cursor
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
str = if left < size
|
|
35
|
+
@io.read left, outbuf
|
|
36
|
+
else
|
|
37
|
+
@io.read size, outbuf
|
|
38
|
+
end
|
|
24
39
|
|
|
25
|
-
if
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
if str
|
|
41
|
+
@cursor += str.bytesize
|
|
42
|
+
else
|
|
43
|
+
# Raise an error for mismatching Content-Length and actual contents
|
|
44
|
+
raise EOFError, "bad content body"
|
|
28
45
|
end
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
str
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def rewind
|
|
51
|
+
@io.rewind
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
MultipartInfo = Struct.new :params, :tmp_files
|
|
56
|
+
EMPTY = MultipartInfo.new(nil, [])
|
|
57
|
+
|
|
58
|
+
def self.parse_boundary(content_type)
|
|
59
|
+
return unless content_type
|
|
60
|
+
data = content_type.match(MULTIPART)
|
|
61
|
+
return unless data
|
|
62
|
+
data[1]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
|
66
|
+
return EMPTY if 0 == content_length
|
|
67
|
+
|
|
68
|
+
boundary = parse_boundary content_type
|
|
69
|
+
return EMPTY unless boundary
|
|
70
|
+
|
|
71
|
+
io = BoundedIO.new(io, content_length) if content_length
|
|
72
|
+
outbuf = String.new
|
|
73
|
+
|
|
74
|
+
parser = new(boundary, tmpfile, bufsize, qp)
|
|
75
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
76
|
+
|
|
77
|
+
loop do
|
|
78
|
+
break if parser.state == :DONE
|
|
79
|
+
parser.on_read io.read(bufsize, outbuf)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
io.rewind
|
|
83
|
+
parser.result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class Collector
|
|
87
|
+
class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
|
|
88
|
+
def get_data
|
|
89
|
+
data = body
|
|
90
|
+
if filename == ""
|
|
91
|
+
# filename is blank which means no file has been selected
|
|
92
|
+
return
|
|
93
|
+
elsif filename
|
|
94
|
+
body.rewind if body.respond_to?(:rewind)
|
|
95
|
+
|
|
96
|
+
# Take the basename of the upload's original filename.
|
|
97
|
+
# This handles the full Windows paths given by Internet Explorer
|
|
98
|
+
# (and perhaps other broken user agents) without affecting
|
|
99
|
+
# those which give the lone filename.
|
|
100
|
+
fn = filename.split(/[\/\\]/).last
|
|
101
|
+
|
|
102
|
+
data = { filename: fn, type: content_type,
|
|
103
|
+
name: name, tempfile: body, head: head }
|
|
104
|
+
elsif !filename && content_type && body.is_a?(IO)
|
|
105
|
+
body.rewind
|
|
106
|
+
|
|
107
|
+
# Generic multipart cases, not coming from a form
|
|
108
|
+
data = { type: content_type,
|
|
109
|
+
name: name, tempfile: body, head: head }
|
|
110
|
+
end
|
|
34
111
|
|
|
35
|
-
|
|
112
|
+
yield data
|
|
36
113
|
end
|
|
114
|
+
end
|
|
37
115
|
|
|
38
|
-
|
|
116
|
+
class BufferPart < MimePart
|
|
117
|
+
def file?; false; end
|
|
118
|
+
def close; end
|
|
119
|
+
end
|
|
39
120
|
|
|
40
|
-
|
|
121
|
+
class TempfilePart < MimePart
|
|
122
|
+
def file?; true; end
|
|
123
|
+
def close; body.close; end
|
|
124
|
+
end
|
|
41
125
|
|
|
42
|
-
|
|
43
|
-
|
|
126
|
+
include Enumerable
|
|
127
|
+
|
|
128
|
+
def initialize tempfile
|
|
129
|
+
@tempfile = tempfile
|
|
130
|
+
@mime_parts = []
|
|
131
|
+
@open_files = 0
|
|
44
132
|
end
|
|
45
133
|
|
|
46
|
-
|
|
134
|
+
def each
|
|
135
|
+
@mime_parts.each { |part| yield part }
|
|
136
|
+
end
|
|
47
137
|
|
|
48
|
-
|
|
49
|
-
|
|
138
|
+
def on_mime_head mime_index, head, filename, content_type, name
|
|
139
|
+
if filename
|
|
140
|
+
body = @tempfile.call(filename, content_type)
|
|
141
|
+
body.binmode if body.respond_to?(:binmode)
|
|
142
|
+
klass = TempfilePart
|
|
143
|
+
@open_files += 1
|
|
144
|
+
else
|
|
145
|
+
body = String.new
|
|
146
|
+
klass = BufferPart
|
|
147
|
+
end
|
|
50
148
|
|
|
51
|
-
|
|
52
|
-
def setup_parse
|
|
53
|
-
return false unless @env['CONTENT_TYPE'] =~ MULTIPART
|
|
149
|
+
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
|
54
150
|
|
|
55
|
-
|
|
151
|
+
check_open_files
|
|
152
|
+
end
|
|
56
153
|
|
|
57
|
-
|
|
58
|
-
|
|
154
|
+
def on_mime_body mime_index, content
|
|
155
|
+
@mime_parts[mime_index].body << content
|
|
156
|
+
end
|
|
59
157
|
|
|
60
|
-
|
|
61
|
-
|
|
158
|
+
def on_mime_finish mime_index
|
|
159
|
+
end
|
|
62
160
|
|
|
63
|
-
|
|
161
|
+
private
|
|
64
162
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
163
|
+
def check_open_files
|
|
164
|
+
if Utils.multipart_part_limit > 0
|
|
165
|
+
if @open_files >= Utils.multipart_part_limit
|
|
166
|
+
@mime_parts.each(&:close)
|
|
167
|
+
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
|
168
|
+
end
|
|
169
|
+
end
|
|
68
170
|
end
|
|
69
|
-
true
|
|
70
171
|
end
|
|
71
172
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
173
|
+
attr_reader :state
|
|
174
|
+
|
|
175
|
+
def initialize(boundary, tempfile, bufsize, query_parser)
|
|
176
|
+
@query_parser = query_parser
|
|
177
|
+
@params = query_parser.make_params
|
|
178
|
+
@boundary = "--#{boundary}"
|
|
179
|
+
@bufsize = bufsize
|
|
75
180
|
|
|
76
|
-
|
|
77
|
-
@
|
|
181
|
+
@full_boundary = @boundary
|
|
182
|
+
@end_boundary = @boundary + '--'
|
|
183
|
+
@state = :FAST_FORWARD
|
|
184
|
+
@mime_index = 0
|
|
185
|
+
@collector = Collector.new tempfile
|
|
186
|
+
|
|
187
|
+
@sbuf = StringScanner.new("".dup)
|
|
188
|
+
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
|
189
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
|
190
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
|
78
191
|
end
|
|
79
192
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
193
|
+
def on_read content
|
|
194
|
+
handle_empty_content!(content)
|
|
195
|
+
@sbuf.concat content
|
|
196
|
+
run_parser
|
|
197
|
+
end
|
|
85
198
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
199
|
+
def result
|
|
200
|
+
@collector.each do |part|
|
|
201
|
+
part.get_data do |data|
|
|
202
|
+
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
|
203
|
+
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
|
89
204
|
end
|
|
90
|
-
|
|
91
|
-
raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
|
|
92
205
|
end
|
|
206
|
+
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
|
93
207
|
end
|
|
94
208
|
|
|
95
|
-
|
|
96
|
-
head = nil
|
|
97
|
-
body = ''
|
|
98
|
-
filename = content_type = name = nil
|
|
99
|
-
content = nil
|
|
209
|
+
private
|
|
100
210
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
211
|
+
def run_parser
|
|
212
|
+
loop do
|
|
213
|
+
case @state
|
|
214
|
+
when :FAST_FORWARD
|
|
215
|
+
break if handle_fast_forward == :want_read
|
|
216
|
+
when :CONSUME_TOKEN
|
|
217
|
+
break if handle_consume_token == :want_read
|
|
218
|
+
when :MIME_HEAD
|
|
219
|
+
break if handle_mime_head == :want_read
|
|
220
|
+
when :MIME_BODY
|
|
221
|
+
break if handle_mime_body == :want_read
|
|
222
|
+
when :DONE
|
|
223
|
+
break
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
104
227
|
|
|
105
|
-
|
|
228
|
+
def handle_fast_forward
|
|
229
|
+
if consume_boundary
|
|
230
|
+
@state = :MIME_HEAD
|
|
231
|
+
else
|
|
232
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
|
233
|
+
:want_read
|
|
234
|
+
end
|
|
235
|
+
end
|
|
106
236
|
|
|
107
|
-
|
|
108
|
-
|
|
237
|
+
def handle_consume_token
|
|
238
|
+
tok = consume_boundary
|
|
239
|
+
# break if we're at the end of a buffer, but not if it is the end of a field
|
|
240
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
|
241
|
+
:DONE
|
|
242
|
+
else
|
|
243
|
+
:MIME_HEAD
|
|
244
|
+
end
|
|
245
|
+
end
|
|
109
246
|
|
|
110
|
-
|
|
247
|
+
def handle_mime_head
|
|
248
|
+
if @sbuf.scan_until(@head_regex)
|
|
249
|
+
head = @sbuf[1]
|
|
250
|
+
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
251
|
+
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
|
252
|
+
name = Rack::Auth::Digest::Params::dequote(name)
|
|
253
|
+
else
|
|
254
|
+
name = head[MULTIPART_CONTENT_ID, 1]
|
|
255
|
+
end
|
|
111
256
|
|
|
112
|
-
|
|
113
|
-
body = Tempfile.new("RackMultipart")
|
|
114
|
-
body.binmode if body.respond_to?(:binmode)
|
|
115
|
-
end
|
|
257
|
+
filename = get_filename(head)
|
|
116
258
|
|
|
117
|
-
|
|
259
|
+
if name.nil? || name.empty?
|
|
260
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
|
118
261
|
end
|
|
119
262
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
263
|
+
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
|
264
|
+
@state = :MIME_BODY
|
|
265
|
+
else
|
|
266
|
+
:want_read
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def handle_mime_body
|
|
271
|
+
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
|
272
|
+
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
|
273
|
+
@collector.on_mime_body @mime_index, body
|
|
274
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
|
275
|
+
@state = :CONSUME_TOKEN
|
|
276
|
+
@mime_index += 1
|
|
277
|
+
else
|
|
278
|
+
# Save what we have so far
|
|
279
|
+
if @rx_max_size < @sbuf.rest_size
|
|
280
|
+
delta = @sbuf.rest_size - @rx_max_size
|
|
281
|
+
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
|
282
|
+
@sbuf.pos += delta
|
|
283
|
+
@sbuf.string = @sbuf.rest
|
|
123
284
|
end
|
|
285
|
+
:want_read
|
|
286
|
+
end
|
|
287
|
+
end
|
|
124
288
|
|
|
125
|
-
|
|
126
|
-
raise EOFError, "bad content body" if content.nil? || content.empty?
|
|
289
|
+
def full_boundary; @full_boundary; end
|
|
127
290
|
|
|
128
|
-
|
|
129
|
-
|
|
291
|
+
def consume_boundary
|
|
292
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
|
293
|
+
case read_buffer.strip
|
|
294
|
+
when full_boundary then return :BOUNDARY
|
|
295
|
+
when @end_boundary then return :END_BOUNDARY
|
|
296
|
+
end
|
|
297
|
+
return if @sbuf.eos?
|
|
130
298
|
end
|
|
131
|
-
|
|
132
|
-
[head, filename, content_type, name, body]
|
|
133
299
|
end
|
|
134
300
|
|
|
135
301
|
def get_filename(head)
|
|
136
302
|
filename = nil
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
filename =
|
|
142
|
-
|
|
303
|
+
case head
|
|
304
|
+
when RFC2183
|
|
305
|
+
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
|
306
|
+
|
|
307
|
+
if filename = params['filename']
|
|
308
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
|
309
|
+
elsif filename = params['filename*']
|
|
310
|
+
encoding, _, filename = filename.split("'", 3)
|
|
311
|
+
end
|
|
312
|
+
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
|
143
313
|
filename = $1
|
|
144
314
|
end
|
|
145
315
|
|
|
146
|
-
|
|
147
|
-
|
|
316
|
+
return unless filename
|
|
317
|
+
|
|
318
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
|
319
|
+
filename = Utils.unescape_path(filename)
|
|
148
320
|
end
|
|
149
|
-
|
|
321
|
+
|
|
322
|
+
filename.scrub!
|
|
323
|
+
|
|
324
|
+
if filename !~ /\\[^\\"]/
|
|
150
325
|
filename = filename.gsub(/\\(.)/, '\1')
|
|
151
326
|
end
|
|
327
|
+
|
|
328
|
+
if encoding
|
|
329
|
+
filename.force_encoding ::Encoding.find(encoding)
|
|
330
|
+
end
|
|
331
|
+
|
|
152
332
|
filename
|
|
153
333
|
end
|
|
154
334
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
335
|
+
CHARSET = "charset"
|
|
336
|
+
|
|
337
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
|
338
|
+
name = name.to_s
|
|
339
|
+
encoding = Encoding::UTF_8
|
|
340
|
+
|
|
341
|
+
name.force_encoding(encoding)
|
|
342
|
+
|
|
343
|
+
return if filename
|
|
344
|
+
|
|
345
|
+
if content_type
|
|
346
|
+
list = content_type.split(';')
|
|
347
|
+
type_subtype = list.first
|
|
348
|
+
type_subtype.strip!
|
|
349
|
+
if TEXT_PLAIN == type_subtype
|
|
350
|
+
rest = list.drop 1
|
|
351
|
+
rest.each do |param|
|
|
352
|
+
k, v = param.split('=', 2)
|
|
353
|
+
k.strip!
|
|
354
|
+
v.strip!
|
|
355
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
|
356
|
+
encoding = Encoding.find v if k == CHARSET
|
|
357
|
+
end
|
|
358
|
+
end
|
|
179
359
|
end
|
|
180
360
|
|
|
181
|
-
|
|
361
|
+
name.force_encoding(encoding)
|
|
362
|
+
body.force_encoding(encoding)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def handle_empty_content!(content)
|
|
366
|
+
if content.nil? || content.empty?
|
|
367
|
+
raise EOFError
|
|
368
|
+
end
|
|
182
369
|
end
|
|
183
370
|
end
|
|
184
371
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Rack
|
|
2
4
|
module Multipart
|
|
3
5
|
class UploadedFile
|
|
@@ -11,8 +13,7 @@ module Rack
|
|
|
11
13
|
raise "#{path} file does not exist" unless ::File.exist?(path)
|
|
12
14
|
@content_type = content_type
|
|
13
15
|
@original_filename = ::File.basename(path)
|
|
14
|
-
@tempfile = Tempfile.new(@original_filename)
|
|
15
|
-
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
|
16
|
+
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
|
16
17
|
@tempfile.binmode if binary
|
|
17
18
|
FileUtils.copy_file(path, @tempfile.path)
|
|
18
19
|
end
|
|
@@ -31,4 +32,4 @@ module Rack
|
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
|
-
end
|
|
35
|
+
end
|
data/lib/rack/multipart.rb
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/multipart/parser'
|
|
4
|
+
|
|
1
5
|
module Rack
|
|
2
6
|
# A multipart form data parser, adapted from IOWA.
|
|
3
7
|
#
|
|
4
8
|
# Usually, Rack::Request#POST takes care of calling this.
|
|
5
9
|
module Multipart
|
|
6
10
|
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
|
7
|
-
autoload :Parser, 'rack/multipart/parser'
|
|
8
11
|
autoload :Generator, 'rack/multipart/generator'
|
|
9
12
|
|
|
10
13
|
EOL = "\r\n"
|
|
11
14
|
MULTIPART_BOUNDARY = "AaB03x"
|
|
12
|
-
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|
|
|
15
|
+
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
|
13
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
|
14
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
|
18
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
|
19
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
|
20
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
|
19
21
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
|
22
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
|
21
23
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
|
24
|
+
# Updated definitions from RFC 2231
|
|
25
|
+
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
|
26
|
+
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
|
27
|
+
SECTION = /\*[0-9]+/
|
|
28
|
+
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
|
29
|
+
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
|
30
|
+
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
|
31
|
+
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
|
32
|
+
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
|
33
|
+
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
|
34
|
+
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
|
35
|
+
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
|
36
|
+
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
|
37
|
+
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
|
38
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
|
22
39
|
|
|
23
40
|
class << self
|
|
24
|
-
def parse_multipart(env)
|
|
25
|
-
|
|
41
|
+
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
|
42
|
+
extract_multipart Rack::Request.new(env), params
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extract_multipart(req, params = Rack::Utils.default_query_parser)
|
|
46
|
+
io = req.get_header(RACK_INPUT)
|
|
47
|
+
io.rewind
|
|
48
|
+
content_length = req.content_length
|
|
49
|
+
content_length = content_length.to_i if content_length
|
|
50
|
+
|
|
51
|
+
tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
|
|
52
|
+
bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
|
|
53
|
+
|
|
54
|
+
info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
|
|
55
|
+
req.set_header(RACK_TEMPFILES, info.tmp_files)
|
|
56
|
+
info.params
|
|
26
57
|
end
|
|
27
58
|
|
|
28
59
|
def build_multipart(params, first = true)
|
|
@@ -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
|