rack 1.6.13 → 2.0.9

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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +1 -1
  3. data/HISTORY.md +138 -8
  4. data/README.rdoc +18 -28
  5. data/Rakefile +6 -14
  6. data/SPEC +3 -3
  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/content_length.rb +2 -2
  18. data/lib/rack/deflater.rb +4 -39
  19. data/lib/rack/directory.rb +66 -54
  20. data/lib/rack/etag.rb +5 -4
  21. data/lib/rack/events.rb +154 -0
  22. data/lib/rack/file.rb +64 -40
  23. data/lib/rack/handler/cgi.rb +15 -16
  24. data/lib/rack/handler/fastcgi.rb +13 -14
  25. data/lib/rack/handler/lsws.rb +11 -11
  26. data/lib/rack/handler/scgi.rb +15 -15
  27. data/lib/rack/handler/thin.rb +3 -0
  28. data/lib/rack/handler/webrick.rb +24 -26
  29. data/lib/rack/handler.rb +3 -25
  30. data/lib/rack/head.rb +15 -17
  31. data/lib/rack/lint.rb +40 -40
  32. data/lib/rack/lobster.rb +1 -1
  33. data/lib/rack/lock.rb +15 -10
  34. data/lib/rack/logger.rb +2 -2
  35. data/lib/rack/media_type.rb +38 -0
  36. data/lib/rack/{methodoverride.rb → method_override.rb} +6 -6
  37. data/lib/rack/mime.rb +18 -5
  38. data/lib/rack/mock.rb +36 -54
  39. data/lib/rack/multipart/generator.rb +5 -5
  40. data/lib/rack/multipart/parser.rb +270 -157
  41. data/lib/rack/multipart/uploaded_file.rb +1 -2
  42. data/lib/rack/multipart.rb +35 -6
  43. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  44. data/lib/rack/query_parser.rb +192 -0
  45. data/lib/rack/recursive.rb +8 -8
  46. data/lib/rack/request.rb +394 -305
  47. data/lib/rack/response.rb +130 -57
  48. data/lib/rack/rewindable_input.rb +1 -12
  49. data/lib/rack/runtime.rb +10 -18
  50. data/lib/rack/sendfile.rb +5 -7
  51. data/lib/rack/server.rb +30 -23
  52. data/lib/rack/session/abstract/id.rb +110 -75
  53. data/lib/rack/session/cookie.rb +24 -17
  54. data/lib/rack/session/memcache.rb +9 -9
  55. data/lib/rack/session/pool.rb +8 -8
  56. data/lib/rack/show_exceptions.rb +386 -0
  57. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  58. data/lib/rack/static.rb +30 -5
  59. data/lib/rack/tempfile_reaper.rb +2 -2
  60. data/lib/rack/urlmap.rb +15 -14
  61. data/lib/rack/utils.rb +138 -211
  62. data/lib/rack.rb +70 -21
  63. data/rack.gemspec +10 -9
  64. data/test/builder/an_underscore_app.rb +5 -0
  65. data/test/builder/options.ru +1 -1
  66. data/test/cgi/test.fcgi +1 -0
  67. data/test/cgi/test.gz +0 -0
  68. data/test/helper.rb +34 -0
  69. data/test/multipart/filename_with_encoded_words +7 -0
  70. data/test/multipart/filename_with_single_quote +7 -0
  71. data/test/multipart/quoted +15 -0
  72. data/test/multipart/rack-logo.png +0 -0
  73. data/test/multipart/unity3d_wwwform +11 -0
  74. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  75. data/test/spec_auth_basic.rb +27 -19
  76. data/test/spec_auth_digest.rb +47 -46
  77. data/test/spec_body_proxy.rb +27 -27
  78. data/test/spec_builder.rb +51 -41
  79. data/test/spec_cascade.rb +24 -22
  80. data/test/spec_cgi.rb +49 -67
  81. data/test/spec_chunked.rb +37 -35
  82. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  83. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  84. data/test/spec_config.rb +3 -2
  85. data/test/spec_content_length.rb +18 -17
  86. data/test/spec_content_type.rb +13 -12
  87. data/test/spec_deflater.rb +85 -49
  88. data/test/spec_directory.rb +87 -27
  89. data/test/spec_etag.rb +32 -31
  90. data/test/spec_events.rb +133 -0
  91. data/test/spec_fastcgi.rb +50 -72
  92. data/test/spec_file.rb +120 -77
  93. data/test/spec_handler.rb +19 -34
  94. data/test/spec_head.rb +15 -14
  95. data/test/spec_lint.rb +164 -199
  96. data/test/spec_lobster.rb +24 -23
  97. data/test/spec_lock.rb +79 -39
  98. data/test/spec_logger.rb +4 -3
  99. data/test/spec_media_type.rb +42 -0
  100. data/test/{spec_methodoverride.rb → spec_method_override.rb} +34 -35
  101. data/test/spec_mime.rb +19 -19
  102. data/test/spec_mock.rb +206 -144
  103. data/test/spec_multipart.rb +322 -200
  104. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  105. data/test/spec_recursive.rb +17 -14
  106. data/test/spec_request.rb +780 -605
  107. data/test/spec_response.rb +233 -112
  108. data/test/spec_rewindable_input.rb +50 -40
  109. data/test/spec_runtime.rb +11 -10
  110. data/test/spec_sendfile.rb +30 -35
  111. data/test/spec_server.rb +78 -52
  112. data/test/spec_session_abstract_id.rb +11 -33
  113. data/test/spec_session_abstract_session_hash.rb +45 -0
  114. data/test/spec_session_cookie.rb +99 -67
  115. data/test/spec_session_memcache.rb +67 -68
  116. data/test/spec_session_pool.rb +52 -51
  117. data/test/{spec_showexceptions.rb → spec_show_exceptions.rb} +23 -28
  118. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  119. data/test/spec_static.rb +71 -32
  120. data/test/spec_tempfile_reaper.rb +11 -10
  121. data/test/spec_thin.rb +55 -50
  122. data/test/spec_urlmap.rb +79 -78
  123. data/test/spec_utils.rb +441 -346
  124. data/test/spec_version.rb +2 -8
  125. data/test/spec_webrick.rb +93 -71
  126. data/test/static/foo.html +1 -0
  127. data/test/testrequest.rb +1 -1
  128. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  129. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  130. metadata +57 -36
  131. data/KNOWN-ISSUES +0 -44
  132. data/lib/rack/backports/uri/common_18.rb +0 -56
  133. data/lib/rack/backports/uri/common_192.rb +0 -52
  134. data/lib/rack/backports/uri/common_193.rb +0 -29
  135. data/lib/rack/handler/evented_mongrel.rb +0 -8
  136. data/lib/rack/handler/mongrel.rb +0 -106
  137. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  138. data/lib/rack/showexceptions.rb +0 -387
  139. data/lib/rack/utils/okjson.rb +0 -600
  140. data/test/spec_mongrel.rb +0 -182
  141. /data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
@@ -6,159 +6,302 @@ module Rack
6
6
 
7
7
  class Parser
8
8
  BUFSIZE = 16384
9
- DUMMY = Struct.new(:parse).new
9
+ TEXT_PLAIN = "text/plain"
10
+ TEMPFILE_FACTORY = lambda { |filename, content_type|
11
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
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
10
20
 
11
- def self.create(env)
12
- return DUMMY unless env['CONTENT_TYPE'] =~ MULTIPART
21
+ def read(size)
22
+ return if @cursor >= @content_length
13
23
 
14
- io = env['rack.input']
15
- io.rewind
24
+ left = @content_length - @cursor
25
+
26
+ str = if left < size
27
+ @io.read left
28
+ else
29
+ @io.read size
30
+ end
16
31
 
17
- content_length = env['CONTENT_LENGTH']
18
- content_length = content_length.to_i if content_length
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
19
38
 
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
39
+ str
40
+ end
23
41
 
24
- new($1, io, content_length, env, tempfile, bufsize)
42
+ def rewind
43
+ @io.rewind
44
+ end
25
45
  end
26
46
 
27
- def initialize(boundary, io, content_length, env, tempfile, bufsize)
28
- @buf = ""
47
+ MultipartInfo = Struct.new :params, :tmp_files
48
+ EMPTY = MultipartInfo.new(nil, [])
29
49
 
30
- if @buf.respond_to? :force_encoding
31
- @buf.force_encoding Encoding::ASCII_8BIT
32
- end
50
+ def self.parse_boundary(content_type)
51
+ return unless content_type
52
+ data = content_type.match(MULTIPART)
53
+ return unless data
54
+ data[1]
55
+ end
33
56
 
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
57
+ def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
58
+ return EMPTY if 0 == content_length
42
59
 
43
- if @content_length
44
- @content_length -= @boundary_size
45
- end
60
+ boundary = parse_boundary content_type
61
+ return EMPTY unless boundary
46
62
 
47
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
48
- @full_boundary = @boundary + EOL
49
- end
63
+ io = BoundedIO.new(io, content_length) if content_length
50
64
 
51
- def parse
52
- fast_forward_to_first_boundary
65
+ parser = new(boundary, tmpfile, bufsize, qp)
66
+ parser.on_read io.read(bufsize)
53
67
 
54
- opened_files = 0
55
68
  loop do
69
+ break if parser.state == :DONE
70
+ parser.on_read io.read(bufsize)
71
+ end
56
72
 
57
- head, filename, content_type, name, body =
58
- get_current_head_and_filename_and_content_type_and_name_and_body
73
+ io.rewind
74
+ parser.result
75
+ end
59
76
 
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
77
+ class Collector
78
+ class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
79
+ def get_data
80
+ data = body
81
+ if filename == ""
82
+ # filename is blank which means no file has been selected
83
+ return
84
+ elsif filename
85
+ body.rewind if body.respond_to?(:rewind)
86
+
87
+ # Take the basename of the upload's original filename.
88
+ # This handles the full Windows paths given by Internet Explorer
89
+ # (and perhaps other broken user agents) without affecting
90
+ # those which give the lone filename.
91
+ fn = filename.split(/[\/\\]/).last
92
+
93
+ data = {:filename => fn, :type => content_type,
94
+ :name => name, :tempfile => body, :head => head}
95
+ elsif !filename && content_type && body.is_a?(IO)
96
+ body.rewind
97
+
98
+ # Generic multipart cases, not coming from a form
99
+ data = {:type => content_type,
100
+ :name => name, :tempfile => body, :head => head}
101
+ end
102
+
103
+ yield data
63
104
  end
105
+ end
64
106
 
65
- # Save the rest.
66
- if i = @buf.index(rx)
67
- body << @buf.slice!(0, i)
68
- @buf.slice!(0, @boundary_size+2)
107
+ class BufferPart < MimePart
108
+ def file?; false; end
109
+ def close; end
110
+ end
69
111
 
70
- @content_length = -1 if $1 == "--"
71
- end
112
+ class TempfilePart < MimePart
113
+ def file?; true; end
114
+ def close; body.close; end
115
+ end
72
116
 
73
- get_data(filename, body, content_type, name, head) do |data|
74
- tag_multipart_encoding(filename, content_type, name, data)
117
+ include Enumerable
75
118
 
76
- Utils.normalize_params(@params, name, data)
77
- end
119
+ def initialize tempfile
120
+ @tempfile = tempfile
121
+ @mime_parts = []
122
+ @open_files = 0
123
+ end
78
124
 
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
125
+ def each
126
+ @mime_parts.each { |part| yield part }
81
127
  end
82
128
 
83
- @io.rewind
129
+ def on_mime_head mime_index, head, filename, content_type, name
130
+ if filename
131
+ body = @tempfile.call(filename, content_type)
132
+ body.binmode if body.respond_to?(:binmode)
133
+ klass = TempfilePart
134
+ @open_files += 1
135
+ else
136
+ body = String.new
137
+ klass = BufferPart
138
+ end
84
139
 
85
- @params.to_params_hash
86
- end
140
+ @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
141
+ check_open_files
142
+ end
87
143
 
88
- private
89
- def full_boundary; @full_boundary; end
144
+ def on_mime_body mime_index, content
145
+ @mime_parts[mime_index].body << content
146
+ end
90
147
 
91
- def rx; @rx; end
148
+ def on_mime_finish mime_index
149
+ end
92
150
 
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
151
+ private
98
152
 
99
- while @buf.gsub!(/\A([^\n]*\n)/, '')
100
- read_buffer = $1
101
- return if read_buffer == full_boundary
153
+ def check_open_files
154
+ if Utils.multipart_part_limit > 0
155
+ if @open_files >= Utils.multipart_part_limit
156
+ @mime_parts.each(&:close)
157
+ raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
158
+ end
102
159
  end
103
-
104
- raise EOFError, "bad content body" if Utils.bytesize(@buf) >= @bufsize
105
160
  end
106
161
  end
107
162
 
108
- def get_current_head_and_filename_and_content_type_and_name_and_body
109
- head = nil
110
- body = ''
163
+ attr_reader :state
164
+
165
+ def initialize(boundary, tempfile, bufsize, query_parser)
166
+ @buf = String.new
111
167
 
112
- if body.respond_to? :force_encoding
113
- body.force_encoding Encoding::ASCII_8BIT
168
+ @query_parser = query_parser
169
+ @params = query_parser.make_params
170
+ @boundary = "--#{boundary}"
171
+ @bufsize = bufsize
172
+
173
+ @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
174
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
175
+ @full_boundary = @boundary
176
+ @end_boundary = @boundary + '--'
177
+ @state = :FAST_FORWARD
178
+ @mime_index = 0
179
+ @collector = Collector.new tempfile
180
+ end
181
+
182
+ def on_read content
183
+ handle_empty_content!(content)
184
+ @buf << content
185
+ run_parser
186
+ end
187
+
188
+ def result
189
+ @collector.each do |part|
190
+ part.get_data do |data|
191
+ tag_multipart_encoding(part.filename, part.content_type, part.name, data)
192
+ @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
193
+ end
114
194
  end
115
195
 
116
- filename = content_type = name = nil
196
+ MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
197
+ end
117
198
 
118
- until head && @buf =~ rx
119
- if !head && i = @buf.index(EOL+EOL)
120
- head = @buf.slice!(0, i+2) # First \r\n
199
+ private
121
200
 
122
- @buf.slice!(0, 2) # Second \r\n
201
+ def run_parser
202
+ loop do
203
+ case @state
204
+ when :FAST_FORWARD
205
+ break if handle_fast_forward == :want_read
206
+ when :CONSUME_TOKEN
207
+ break if handle_consume_token == :want_read
208
+ when :MIME_HEAD
209
+ break if handle_mime_head == :want_read
210
+ when :MIME_BODY
211
+ break if handle_mime_body == :want_read
212
+ when :DONE
213
+ break
214
+ end
215
+ end
216
+ end
123
217
 
124
- content_type = head[MULTIPART_CONTENT_TYPE, 1]
125
- name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
218
+ def handle_fast_forward
219
+ if consume_boundary
220
+ @state = :MIME_HEAD
221
+ else
222
+ raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
223
+ :want_read
224
+ end
225
+ end
126
226
 
127
- filename = get_filename(head)
227
+ def handle_consume_token
228
+ tok = consume_boundary
229
+ # break if we're at the end of a buffer, but not if it is the end of a field
230
+ if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
231
+ @state = :DONE
232
+ else
233
+ @state = :MIME_HEAD
234
+ end
235
+ end
128
236
 
129
- if name.nil? || name.empty? && filename
130
- name = filename
131
- end
237
+ def handle_mime_head
238
+ if @buf.index(EOL + EOL)
239
+ i = @buf.index(EOL+EOL)
240
+ head = @buf.slice!(0, i+2) # First \r\n
241
+ @buf.slice!(0, 2) # Second \r\n
242
+
243
+ content_type = head[MULTIPART_CONTENT_TYPE, 1]
244
+ if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
245
+ name = Rack::Auth::Digest::Params::dequote(name)
246
+ else
247
+ name = head[MULTIPART_CONTENT_ID, 1]
248
+ end
132
249
 
133
- if filename
134
- (@env['rack.tempfiles'] ||= []) << body = @tempfile.call(filename, content_type)
135
- body.binmode if body.respond_to?(:binmode)
136
- end
250
+ filename = get_filename(head)
137
251
 
138
- next
252
+ if name.nil? || name.empty?
253
+ name = filename || "#{content_type || TEXT_PLAIN}[]"
139
254
  end
140
255
 
256
+ @collector.on_mime_head @mime_index, head, filename, content_type, name
257
+ @state = :MIME_BODY
258
+ else
259
+ :want_read
260
+ end
261
+ end
262
+
263
+ def handle_mime_body
264
+ if i = @buf.index(rx)
265
+ # Save the rest.
266
+ @collector.on_mime_body @mime_index, @buf.slice!(0, i)
267
+ @buf.slice!(0, 2) # Remove \r\n after the content
268
+ @state = :CONSUME_TOKEN
269
+ @mime_index += 1
270
+ else
141
271
  # Save the read body part.
142
- if head && (@boundary_size+4 < @buf.size)
143
- body << @buf.slice!(0, @buf.size - (@boundary_size+4))
272
+ if @rx_max_size < @buf.size
273
+ @collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
144
274
  end
275
+ :want_read
276
+ end
277
+ end
145
278
 
146
- content = @io.read(@content_length && @bufsize >= @content_length ? @content_length : @bufsize)
147
- raise EOFError, "bad content body" if content.nil? || content.empty?
279
+ def full_boundary; @full_boundary; end
148
280
 
149
- @buf << content
150
- @content_length -= content.size if @content_length
151
- end
281
+ def rx; @rx; end
152
282
 
153
- [head, filename, content_type, name, body]
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,54 @@ 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)
360
+ if content.nil? || content.empty?
361
+ raise EOFError
247
362
  end
248
-
249
- yield data
250
363
  end
251
364
  end
252
365
  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