rack 1.6.13 → 2.0.1

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.

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