rack 1.6.13 → 2.0.9.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/COPYING +1 -1
- data/HISTORY.md +153 -8
- data/README.rdoc +35 -31
- data/Rakefile +6 -14
- data/SPEC +10 -11
- data/contrib/rack_logo.svg +164 -111
- data/example/protectedlobster.rb +1 -1
- data/example/protectedlobster.ru +1 -1
- data/lib/rack/auth/abstract/request.rb +5 -1
- data/lib/rack/auth/digest/params.rb +2 -3
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/body_proxy.rb +14 -9
- data/lib/rack/builder.rb +3 -3
- data/lib/rack/chunked.rb +5 -5
- data/lib/rack/{commonlogger.rb → common_logger.rb} +6 -3
- data/lib/rack/content_length.rb +2 -2
- data/lib/rack/deflater.rb +4 -39
- data/lib/rack/directory.rb +66 -54
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +64 -40
- data/lib/rack/handler/cgi.rb +15 -16
- data/lib/rack/handler/fastcgi.rb +13 -14
- data/lib/rack/handler/lsws.rb +11 -11
- data/lib/rack/handler/scgi.rb +15 -15
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +24 -26
- data/lib/rack/handler.rb +3 -25
- data/lib/rack/head.rb +15 -17
- data/lib/rack/lint.rb +41 -41
- data/lib/rack/lobster.rb +1 -1
- data/lib/rack/lock.rb +15 -10
- data/lib/rack/logger.rb +2 -2
- data/lib/rack/media_type.rb +38 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +6 -6
- data/lib/rack/mime.rb +18 -5
- data/lib/rack/mock.rb +36 -54
- data/lib/rack/multipart/generator.rb +5 -5
- data/lib/rack/multipart/parser.rb +283 -157
- data/lib/rack/multipart/uploaded_file.rb +1 -2
- data/lib/rack/multipart.rb +36 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
- data/lib/rack/query_parser.rb +192 -0
- data/lib/rack/recursive.rb +8 -8
- data/lib/rack/request.rb +394 -305
- data/lib/rack/response.rb +130 -57
- data/lib/rack/rewindable_input.rb +1 -12
- data/lib/rack/runtime.rb +10 -18
- data/lib/rack/sendfile.rb +5 -7
- data/lib/rack/server.rb +30 -23
- data/lib/rack/session/abstract/id.rb +110 -75
- data/lib/rack/session/cookie.rb +24 -17
- data/lib/rack/session/memcache.rb +9 -9
- data/lib/rack/session/pool.rb +8 -8
- data/lib/rack/show_exceptions.rb +386 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
- data/lib/rack/static.rb +30 -5
- data/lib/rack/tempfile_reaper.rb +2 -2
- data/lib/rack/urlmap.rb +15 -14
- data/lib/rack/utils.rb +156 -217
- data/lib/rack.rb +70 -21
- data/rack.gemspec +10 -9
- data/test/builder/an_underscore_app.rb +5 -0
- data/test/builder/options.ru +1 -1
- data/test/cgi/test.fcgi +1 -0
- data/test/cgi/test.gz +0 -0
- data/test/helper.rb +34 -0
- data/test/multipart/filename_with_encoded_words +7 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +1 -1
- data/test/multipart/filename_with_single_quote +7 -0
- data/test/multipart/quoted +15 -0
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/unity3d_wwwform +11 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
- data/test/spec_auth_basic.rb +27 -19
- data/test/spec_auth_digest.rb +47 -46
- data/test/spec_body_proxy.rb +27 -27
- data/test/spec_builder.rb +51 -41
- data/test/spec_cascade.rb +24 -22
- data/test/spec_cgi.rb +49 -67
- data/test/spec_chunked.rb +37 -35
- data/test/{spec_commonlogger.rb → spec_common_logger.rb} +35 -21
- data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
- data/test/spec_config.rb +3 -2
- data/test/spec_content_length.rb +18 -17
- data/test/spec_content_type.rb +13 -12
- data/test/spec_deflater.rb +85 -49
- data/test/spec_directory.rb +87 -27
- data/test/spec_etag.rb +32 -31
- data/test/spec_events.rb +133 -0
- data/test/spec_fastcgi.rb +50 -72
- data/test/spec_file.rb +120 -77
- data/test/spec_handler.rb +19 -34
- data/test/spec_head.rb +15 -14
- data/test/spec_lint.rb +169 -199
- data/test/spec_lobster.rb +24 -23
- data/test/spec_lock.rb +79 -39
- data/test/spec_logger.rb +4 -3
- data/test/spec_media_type.rb +42 -0
- data/test/{spec_methodoverride.rb → spec_method_override.rb} +34 -35
- data/test/spec_mime.rb +19 -19
- data/test/spec_mock.rb +206 -144
- data/test/spec_multipart.rb +329 -208
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +796 -605
- data/test/spec_response.rb +233 -112
- data/test/spec_rewindable_input.rb +50 -40
- data/test/spec_runtime.rb +11 -10
- data/test/spec_sendfile.rb +30 -35
- data/test/spec_server.rb +78 -52
- data/test/spec_session_abstract_id.rb +11 -33
- data/test/spec_session_abstract_session_hash.rb +45 -0
- data/test/spec_session_cookie.rb +99 -67
- data/test/spec_session_memcache.rb +67 -68
- data/test/spec_session_pool.rb +52 -51
- data/test/{spec_showexceptions.rb → spec_show_exceptions.rb} +23 -28
- data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
- data/test/spec_static.rb +71 -32
- data/test/spec_tempfile_reaper.rb +11 -10
- data/test/spec_thin.rb +55 -50
- data/test/spec_urlmap.rb +79 -78
- data/test/spec_utils.rb +441 -346
- data/test/spec_version.rb +2 -8
- data/test/spec_webrick.rb +93 -71
- data/test/static/foo.html +1 -0
- data/test/testrequest.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
- metadata +95 -74
- data/KNOWN-ISSUES +0 -44
- 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 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/spec_mongrel.rb +0 -182
- /data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
@@ -3,164 +3,320 @@ require 'rack/utils'
|
|
3
3
|
module Rack
|
4
4
|
module Multipart
|
5
5
|
class MultipartPartLimitError < Errno::EMFILE; end
|
6
|
+
class MultipartTotalPartLimitError < StandardError; end
|
6
7
|
|
7
8
|
class Parser
|
8
9
|
BUFSIZE = 16384
|
9
|
-
|
10
|
+
TEXT_PLAIN = "text/plain"
|
11
|
+
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
12
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
|
13
|
+
}
|
14
|
+
|
15
|
+
class BoundedIO # :nodoc:
|
16
|
+
def initialize(io, content_length)
|
17
|
+
@io = io
|
18
|
+
@content_length = content_length
|
19
|
+
@cursor = 0
|
20
|
+
end
|
10
21
|
|
11
|
-
|
12
|
-
|
22
|
+
def read(size)
|
23
|
+
return if @cursor >= @content_length
|
13
24
|
|
14
|
-
|
15
|
-
|
25
|
+
left = @content_length - @cursor
|
26
|
+
|
27
|
+
str = if left < size
|
28
|
+
@io.read left
|
29
|
+
else
|
30
|
+
@io.read size
|
31
|
+
end
|
16
32
|
|
17
|
-
|
18
|
-
|
33
|
+
if str
|
34
|
+
@cursor += str.bytesize
|
35
|
+
else
|
36
|
+
# Raise an error for mismatching Content-Length and actual contents
|
37
|
+
raise EOFError, "bad content body"
|
38
|
+
end
|
19
39
|
|
20
|
-
|
21
|
-
|
22
|
-
bufsize = env['rack.multipart.buffer_size'] || BUFSIZE
|
40
|
+
str
|
41
|
+
end
|
23
42
|
|
24
|
-
|
43
|
+
def rewind
|
44
|
+
@io.rewind
|
45
|
+
end
|
25
46
|
end
|
26
47
|
|
27
|
-
|
28
|
-
|
48
|
+
MultipartInfo = Struct.new :params, :tmp_files
|
49
|
+
EMPTY = MultipartInfo.new(nil, [])
|
29
50
|
|
30
|
-
|
31
|
-
|
32
|
-
|
51
|
+
def self.parse_boundary(content_type)
|
52
|
+
return unless content_type
|
53
|
+
data = content_type.match(MULTIPART)
|
54
|
+
return unless data
|
55
|
+
data[1]
|
56
|
+
end
|
33
57
|
|
34
|
-
|
35
|
-
|
36
|
-
@io = io
|
37
|
-
@content_length = content_length
|
38
|
-
@boundary_size = Utils.bytesize(@boundary) + EOL.size
|
39
|
-
@env = env
|
40
|
-
@tempfile = tempfile
|
41
|
-
@bufsize = bufsize
|
58
|
+
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
59
|
+
return EMPTY if 0 == content_length
|
42
60
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
61
|
+
boundary = parse_boundary content_type
|
62
|
+
return EMPTY unless boundary
|
46
63
|
|
47
|
-
|
48
|
-
@full_boundary = @boundary + EOL
|
49
|
-
end
|
64
|
+
io = BoundedIO.new(io, content_length) if content_length
|
50
65
|
|
51
|
-
|
52
|
-
|
66
|
+
parser = new(boundary, tmpfile, bufsize, qp)
|
67
|
+
parser.on_read io.read(bufsize)
|
53
68
|
|
54
|
-
opened_files = 0
|
55
69
|
loop do
|
70
|
+
break if parser.state == :DONE
|
71
|
+
parser.on_read io.read(bufsize)
|
72
|
+
end
|
56
73
|
|
57
|
-
|
58
|
-
|
74
|
+
io.rewind
|
75
|
+
parser.result
|
76
|
+
end
|
59
77
|
|
60
|
-
|
61
|
-
|
62
|
-
|
78
|
+
class Collector
|
79
|
+
class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
|
80
|
+
def get_data
|
81
|
+
data = body
|
82
|
+
if filename == ""
|
83
|
+
# filename is blank which means no file has been selected
|
84
|
+
return
|
85
|
+
elsif filename
|
86
|
+
body.rewind if body.respond_to?(:rewind)
|
87
|
+
|
88
|
+
# Take the basename of the upload's original filename.
|
89
|
+
# This handles the full Windows paths given by Internet Explorer
|
90
|
+
# (and perhaps other broken user agents) without affecting
|
91
|
+
# those which give the lone filename.
|
92
|
+
fn = filename.split(/[\/\\]/).last
|
93
|
+
|
94
|
+
data = {:filename => fn, :type => content_type,
|
95
|
+
:name => name, :tempfile => body, :head => head}
|
96
|
+
elsif !filename && content_type && body.is_a?(IO)
|
97
|
+
body.rewind
|
98
|
+
|
99
|
+
# Generic multipart cases, not coming from a form
|
100
|
+
data = {:type => content_type,
|
101
|
+
:name => name, :tempfile => body, :head => head}
|
102
|
+
end
|
103
|
+
|
104
|
+
yield data
|
63
105
|
end
|
106
|
+
end
|
64
107
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
108
|
+
class BufferPart < MimePart
|
109
|
+
def file?; false; end
|
110
|
+
def close; end
|
111
|
+
end
|
69
112
|
|
70
|
-
|
71
|
-
end
|
113
|
+
class TempfilePart < MimePart
|
114
|
+
def file?; true; end
|
115
|
+
def close; body.close; end
|
116
|
+
end
|
72
117
|
|
73
|
-
|
74
|
-
tag_multipart_encoding(filename, content_type, name, data)
|
118
|
+
include Enumerable
|
75
119
|
|
76
|
-
|
77
|
-
|
120
|
+
def initialize tempfile
|
121
|
+
@tempfile = tempfile
|
122
|
+
@mime_parts = []
|
123
|
+
@open_files = 0
|
124
|
+
end
|
78
125
|
|
79
|
-
|
80
|
-
|
126
|
+
def each
|
127
|
+
@mime_parts.each { |part| yield part }
|
81
128
|
end
|
82
129
|
|
83
|
-
|
130
|
+
def on_mime_head mime_index, head, filename, content_type, name
|
131
|
+
if filename
|
132
|
+
body = @tempfile.call(filename, content_type)
|
133
|
+
body.binmode if body.respond_to?(:binmode)
|
134
|
+
klass = TempfilePart
|
135
|
+
@open_files += 1
|
136
|
+
else
|
137
|
+
body = String.new
|
138
|
+
klass = BufferPart
|
139
|
+
end
|
84
140
|
|
85
|
-
|
86
|
-
end
|
141
|
+
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
87
142
|
|
88
|
-
|
89
|
-
|
143
|
+
check_part_limits
|
144
|
+
end
|
90
145
|
|
91
|
-
|
146
|
+
def on_mime_body mime_index, content
|
147
|
+
@mime_parts[mime_index].body << content
|
148
|
+
end
|
92
149
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
150
|
+
def on_mime_finish mime_index
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def check_part_limits
|
156
|
+
file_limit = Utils.multipart_file_limit
|
157
|
+
part_limit = Utils.multipart_total_part_limit
|
98
158
|
|
99
|
-
|
100
|
-
|
101
|
-
|
159
|
+
if file_limit && file_limit > 0
|
160
|
+
if @open_files >= file_limit
|
161
|
+
@mime_parts.each(&:close)
|
162
|
+
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
163
|
+
end
|
102
164
|
end
|
103
165
|
|
104
|
-
|
166
|
+
if part_limit && part_limit > 0
|
167
|
+
if @mime_parts.size >= part_limit
|
168
|
+
@mime_parts.each(&:close)
|
169
|
+
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
170
|
+
end
|
171
|
+
end
|
105
172
|
end
|
106
173
|
end
|
107
174
|
|
108
|
-
|
109
|
-
|
110
|
-
|
175
|
+
attr_reader :state
|
176
|
+
|
177
|
+
def initialize(boundary, tempfile, bufsize, query_parser)
|
178
|
+
@buf = String.new
|
179
|
+
|
180
|
+
@query_parser = query_parser
|
181
|
+
@params = query_parser.make_params
|
182
|
+
@boundary = "--#{boundary}"
|
183
|
+
@bufsize = bufsize
|
184
|
+
|
185
|
+
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
186
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
187
|
+
@full_boundary = @boundary
|
188
|
+
@end_boundary = @boundary + '--'
|
189
|
+
@state = :FAST_FORWARD
|
190
|
+
@mime_index = 0
|
191
|
+
@collector = Collector.new tempfile
|
192
|
+
end
|
193
|
+
|
194
|
+
def on_read content
|
195
|
+
handle_empty_content!(content)
|
196
|
+
@buf << content
|
197
|
+
run_parser
|
198
|
+
end
|
111
199
|
|
112
|
-
|
113
|
-
|
200
|
+
def result
|
201
|
+
@collector.each do |part|
|
202
|
+
part.get_data do |data|
|
203
|
+
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
204
|
+
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
205
|
+
end
|
114
206
|
end
|
115
207
|
|
116
|
-
|
208
|
+
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
209
|
+
end
|
117
210
|
|
118
|
-
|
119
|
-
if !head && i = @buf.index(EOL+EOL)
|
120
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
211
|
+
private
|
121
212
|
|
122
|
-
|
213
|
+
def run_parser
|
214
|
+
loop do
|
215
|
+
case @state
|
216
|
+
when :FAST_FORWARD
|
217
|
+
break if handle_fast_forward == :want_read
|
218
|
+
when :CONSUME_TOKEN
|
219
|
+
break if handle_consume_token == :want_read
|
220
|
+
when :MIME_HEAD
|
221
|
+
break if handle_mime_head == :want_read
|
222
|
+
when :MIME_BODY
|
223
|
+
break if handle_mime_body == :want_read
|
224
|
+
when :DONE
|
225
|
+
break
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
123
229
|
|
124
|
-
|
125
|
-
|
230
|
+
def handle_fast_forward
|
231
|
+
if consume_boundary
|
232
|
+
@state = :MIME_HEAD
|
233
|
+
else
|
234
|
+
raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
|
235
|
+
:want_read
|
236
|
+
end
|
237
|
+
end
|
126
238
|
|
127
|
-
|
239
|
+
def handle_consume_token
|
240
|
+
tok = consume_boundary
|
241
|
+
# break if we're at the end of a buffer, but not if it is the end of a field
|
242
|
+
if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
|
243
|
+
@state = :DONE
|
244
|
+
else
|
245
|
+
@state = :MIME_HEAD
|
246
|
+
end
|
247
|
+
end
|
128
248
|
|
129
|
-
|
130
|
-
|
131
|
-
|
249
|
+
def handle_mime_head
|
250
|
+
if @buf.index(EOL + EOL)
|
251
|
+
i = @buf.index(EOL+EOL)
|
252
|
+
head = @buf.slice!(0, i+2) # First \r\n
|
253
|
+
@buf.slice!(0, 2) # Second \r\n
|
254
|
+
|
255
|
+
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
256
|
+
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
257
|
+
name = Rack::Auth::Digest::Params::dequote(name)
|
258
|
+
else
|
259
|
+
name = head[MULTIPART_CONTENT_ID, 1]
|
260
|
+
end
|
132
261
|
|
133
|
-
|
134
|
-
(@env['rack.tempfiles'] ||= []) << body = @tempfile.call(filename, content_type)
|
135
|
-
body.binmode if body.respond_to?(:binmode)
|
136
|
-
end
|
262
|
+
filename = get_filename(head)
|
137
263
|
|
138
|
-
|
264
|
+
if name.nil? || name.empty?
|
265
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
139
266
|
end
|
140
267
|
|
268
|
+
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
269
|
+
@state = :MIME_BODY
|
270
|
+
else
|
271
|
+
:want_read
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def handle_mime_body
|
276
|
+
if i = @buf.index(rx)
|
277
|
+
# Save the rest.
|
278
|
+
@collector.on_mime_body @mime_index, @buf.slice!(0, i)
|
279
|
+
@buf.slice!(0, 2) # Remove \r\n after the content
|
280
|
+
@state = :CONSUME_TOKEN
|
281
|
+
@mime_index += 1
|
282
|
+
else
|
141
283
|
# Save the read body part.
|
142
|
-
if
|
143
|
-
|
284
|
+
if @rx_max_size < @buf.size
|
285
|
+
@collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
|
144
286
|
end
|
287
|
+
:want_read
|
288
|
+
end
|
289
|
+
end
|
145
290
|
|
146
|
-
|
147
|
-
raise EOFError, "bad content body" if content.nil? || content.empty?
|
291
|
+
def full_boundary; @full_boundary; end
|
148
292
|
|
149
|
-
|
150
|
-
@content_length -= content.size if @content_length
|
151
|
-
end
|
293
|
+
def rx; @rx; end
|
152
294
|
|
153
|
-
|
295
|
+
def consume_boundary
|
296
|
+
while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
|
297
|
+
read_buffer = $1
|
298
|
+
case read_buffer.strip
|
299
|
+
when full_boundary then return :BOUNDARY
|
300
|
+
when @end_boundary then return :END_BOUNDARY
|
301
|
+
end
|
302
|
+
return if @buf.empty?
|
303
|
+
end
|
154
304
|
end
|
155
305
|
|
156
306
|
def get_filename(head)
|
157
307
|
filename = nil
|
158
308
|
case head
|
159
309
|
when RFC2183
|
160
|
-
|
161
|
-
|
162
|
-
|
310
|
+
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
311
|
+
|
312
|
+
if filename = params['filename']
|
313
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
314
|
+
elsif filename = params['filename*']
|
315
|
+
encoding, _, filename = filename.split("'", 3)
|
316
|
+
end
|
317
|
+
when BROKEN
|
163
318
|
filename = $1
|
319
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
164
320
|
end
|
165
321
|
|
166
322
|
return unless filename
|
@@ -169,84 +325,54 @@ module Rack
|
|
169
325
|
filename = Utils.unescape(filename)
|
170
326
|
end
|
171
327
|
|
172
|
-
|
328
|
+
filename.scrub!
|
173
329
|
|
174
330
|
if filename !~ /\\[^\\"]/
|
175
331
|
filename = filename.gsub(/\\(.)/, '\1')
|
176
332
|
end
|
177
|
-
filename
|
178
|
-
end
|
179
333
|
|
180
|
-
|
181
|
-
|
182
|
-
unless filename.valid_encoding?
|
183
|
-
# FIXME: this force_encoding is for Ruby 2.0 and 1.9 support.
|
184
|
-
# We can remove it after they are dropped
|
185
|
-
filename.force_encoding(Encoding::ASCII_8BIT)
|
186
|
-
filename.encode!(:invalid => :replace, :undef => :replace)
|
187
|
-
end
|
334
|
+
if encoding
|
335
|
+
filename.force_encoding ::Encoding.find(encoding)
|
188
336
|
end
|
189
337
|
|
190
|
-
|
191
|
-
|
338
|
+
filename
|
339
|
+
end
|
340
|
+
|
341
|
+
CHARSET = "charset"
|
192
342
|
|
193
|
-
|
194
|
-
|
343
|
+
def tag_multipart_encoding(filename, content_type, name, body)
|
344
|
+
name = name.to_s
|
345
|
+
encoding = Encoding::UTF_8
|
195
346
|
|
196
|
-
|
347
|
+
name.force_encoding(encoding)
|
197
348
|
|
198
|
-
|
349
|
+
return if filename
|
199
350
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
351
|
+
if content_type
|
352
|
+
list = content_type.split(';')
|
353
|
+
type_subtype = list.first
|
354
|
+
type_subtype.strip!
|
355
|
+
if TEXT_PLAIN == type_subtype
|
356
|
+
rest = list.drop 1
|
357
|
+
rest.each do |param|
|
358
|
+
k,v = param.split('=', 2)
|
359
|
+
k.strip!
|
360
|
+
v.strip!
|
361
|
+
v = v[1..-2] if v[0] == '"' && v[-1] == '"'
|
362
|
+
encoding = Encoding.find v if k == CHARSET
|
212
363
|
end
|
213
364
|
end
|
214
|
-
|
215
|
-
name.force_encoding encoding
|
216
|
-
body.force_encoding encoding
|
217
|
-
end
|
218
|
-
else
|
219
|
-
def scrub_filename(filename)
|
220
365
|
end
|
221
|
-
def tag_multipart_encoding(filename, content_type, name, body)
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def get_data(filename, body, content_type, name, head)
|
226
|
-
data = body
|
227
|
-
if filename == ""
|
228
|
-
# filename is blank which means no file has been selected
|
229
|
-
return
|
230
|
-
elsif filename
|
231
|
-
body.rewind if body.respond_to?(:rewind)
|
232
366
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
# those which give the lone filename.
|
237
|
-
filename = filename.split(/[\/\\]/).last
|
367
|
+
name.force_encoding(encoding)
|
368
|
+
body.force_encoding(encoding)
|
369
|
+
end
|
238
370
|
|
239
|
-
data = {:filename => filename, :type => content_type,
|
240
|
-
:name => name, :tempfile => body, :head => head}
|
241
|
-
elsif !filename && content_type && body.is_a?(IO)
|
242
|
-
body.rewind
|
243
371
|
|
244
|
-
|
245
|
-
|
246
|
-
|
372
|
+
def handle_empty_content!(content)
|
373
|
+
if content.nil? || content.empty?
|
374
|
+
raise EOFError
|
247
375
|
end
|
248
|
-
|
249
|
-
yield data
|
250
376
|
end
|
251
377
|
end
|
252
378
|
end
|
@@ -11,8 +11,7 @@ module Rack
|
|
11
11
|
raise "#{path} file does not exist" unless ::File.exist?(path)
|
12
12
|
@content_type = content_type
|
13
13
|
@original_filename = ::File.basename(path)
|
14
|
-
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)])
|
15
|
-
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
14
|
+
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
16
15
|
@tempfile.binmode if binary
|
17
16
|
FileUtils.copy_file(path, @tempfile.path)
|
18
17
|
end
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'rack/multipart/parser'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# A multipart form data parser, adapted from IOWA.
|
3
5
|
#
|
4
6
|
# Usually, Rack::Request#POST takes care of calling this.
|
5
7
|
module Multipart
|
6
8
|
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
7
|
-
autoload :Parser, 'rack/multipart/parser'
|
8
9
|
autoload :Generator, 'rack/multipart/generator'
|
9
10
|
|
10
11
|
EOL = "\r\n"
|
@@ -12,17 +13,44 @@ module Rack
|
|
12
13
|
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
13
14
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
14
15
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
15
|
-
|
16
|
-
|
17
|
-
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
16
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
+
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
19
18
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
19
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s+name=(#{VALUE})/ni
|
21
20
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
21
|
+
# Updated definitions from RFC 2231
|
22
|
+
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
23
|
+
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
24
|
+
SECTION = /\*[0-9]+/
|
25
|
+
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
26
|
+
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
27
|
+
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
28
|
+
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
29
|
+
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
30
|
+
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
31
|
+
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
32
|
+
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
33
|
+
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
34
|
+
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
35
|
+
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
22
36
|
|
23
37
|
class << self
|
24
|
-
def parse_multipart(env)
|
25
|
-
|
38
|
+
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
39
|
+
extract_multipart Rack::Request.new(env), params
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_multipart(req, params = Rack::Utils.default_query_parser)
|
43
|
+
io = req.get_header(RACK_INPUT)
|
44
|
+
io.rewind
|
45
|
+
content_length = req.content_length
|
46
|
+
content_length = content_length.to_i if content_length
|
47
|
+
|
48
|
+
tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
|
49
|
+
bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
|
50
|
+
|
51
|
+
info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
|
52
|
+
req.set_header(RACK_TEMPFILES, info.tmp_files)
|
53
|
+
info.params
|
26
54
|
end
|
27
55
|
|
28
56
|
def build_multipart(params, first = true)
|