rack 2.0.6 → 2.1.0

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/{HISTORY.md → CHANGELOG.md} +220 -155
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +77 -117
  5. data/Rakefile +25 -18
  6. data/SPEC +3 -4
  7. data/bin/rackup +1 -0
  8. data/example/lobster.ru +2 -0
  9. data/example/protectedlobster.rb +3 -1
  10. data/example/protectedlobster.ru +2 -0
  11. data/lib/rack/auth/abstract/handler.rb +3 -1
  12. data/lib/rack/auth/abstract/request.rb +2 -0
  13. data/lib/rack/auth/basic.rb +4 -1
  14. data/lib/rack/auth/digest/md5.rb +9 -7
  15. data/lib/rack/auth/digest/nonce.rb +6 -3
  16. data/lib/rack/auth/digest/params.rb +4 -2
  17. data/lib/rack/auth/digest/request.rb +2 -0
  18. data/lib/rack/body_proxy.rb +3 -6
  19. data/lib/rack/builder.rb +38 -15
  20. data/lib/rack/cascade.rb +6 -5
  21. data/lib/rack/chunked.rb +29 -6
  22. data/lib/rack/common_logger.rb +9 -8
  23. data/lib/rack/conditional_get.rb +3 -1
  24. data/lib/rack/config.rb +2 -0
  25. data/lib/rack/content_length.rb +3 -1
  26. data/lib/rack/content_type.rb +3 -1
  27. data/lib/rack/core_ext/regexp.rb +14 -0
  28. data/lib/rack/deflater.rb +28 -17
  29. data/lib/rack/directory.rb +17 -14
  30. data/lib/rack/etag.rb +3 -1
  31. data/lib/rack/events.rb +5 -3
  32. data/lib/rack/file.rb +5 -173
  33. data/lib/rack/files.rb +178 -0
  34. data/lib/rack/handler/cgi.rb +3 -1
  35. data/lib/rack/handler/fastcgi.rb +4 -2
  36. data/lib/rack/handler/lsws.rb +3 -1
  37. data/lib/rack/handler/scgi.rb +9 -6
  38. data/lib/rack/handler/thin.rb +3 -1
  39. data/lib/rack/handler/webrick.rb +4 -2
  40. data/lib/rack/handler.rb +7 -2
  41. data/lib/rack/head.rb +2 -0
  42. data/lib/rack/lint.rb +14 -11
  43. data/lib/rack/lobster.rb +7 -5
  44. data/lib/rack/lock.rb +2 -0
  45. data/lib/rack/logger.rb +2 -0
  46. data/lib/rack/media_type.rb +10 -5
  47. data/lib/rack/method_override.rb +4 -2
  48. data/lib/rack/mime.rb +9 -1
  49. data/lib/rack/mock.rb +74 -15
  50. data/lib/rack/multipart/generator.rb +6 -7
  51. data/lib/rack/multipart/parser.rb +55 -52
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +5 -3
  54. data/lib/rack/null_logger.rb +2 -0
  55. data/lib/rack/query_parser.rb +51 -25
  56. data/lib/rack/recursive.rb +7 -5
  57. data/lib/rack/reloader.rb +10 -4
  58. data/lib/rack/request.rb +80 -27
  59. data/lib/rack/response.rb +71 -31
  60. data/lib/rack/rewindable_input.rb +4 -2
  61. data/lib/rack/runtime.rb +4 -2
  62. data/lib/rack/sendfile.rb +15 -8
  63. data/lib/rack/server.rb +88 -16
  64. data/lib/rack/session/abstract/id.rb +104 -21
  65. data/lib/rack/session/cookie.rb +21 -11
  66. data/lib/rack/session/memcache.rb +4 -87
  67. data/lib/rack/session/pool.rb +17 -8
  68. data/lib/rack/show_exceptions.rb +15 -9
  69. data/lib/rack/show_status.rb +4 -2
  70. data/lib/rack/static.rb +15 -10
  71. data/lib/rack/tempfile_reaper.rb +2 -0
  72. data/lib/rack/urlmap.rb +11 -2
  73. data/lib/rack/utils.rb +55 -70
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +30 -171
  77. data/test/builder/an_underscore_app.rb +0 -5
  78. data/test/builder/anything.rb +0 -5
  79. data/test/builder/comment.ru +0 -4
  80. data/test/builder/end.ru +0 -5
  81. data/test/builder/line.ru +0 -1
  82. data/test/builder/options.ru +0 -2
  83. data/test/cgi/assets/folder/test.js +0 -1
  84. data/test/cgi/assets/fonts/font.eot +0 -1
  85. data/test/cgi/assets/images/image.png +0 -1
  86. data/test/cgi/assets/index.html +0 -1
  87. data/test/cgi/assets/javascripts/app.js +0 -1
  88. data/test/cgi/assets/stylesheets/app.css +0 -1
  89. data/test/cgi/lighttpd.conf +0 -26
  90. data/test/cgi/rackup_stub.rb +0 -6
  91. data/test/cgi/sample_rackup.ru +0 -5
  92. data/test/cgi/test +0 -9
  93. data/test/cgi/test+directory/test+file +0 -1
  94. data/test/cgi/test.fcgi +0 -9
  95. data/test/cgi/test.gz +0 -0
  96. data/test/cgi/test.ru +0 -5
  97. data/test/gemloader.rb +0 -10
  98. data/test/helper.rb +0 -34
  99. data/test/multipart/bad_robots +0 -259
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/content_type_and_no_filename +0 -6
  102. data/test/multipart/empty +0 -10
  103. data/test/multipart/fail_16384_nofile +0 -814
  104. data/test/multipart/file1.txt +0 -1
  105. data/test/multipart/filename_and_modification_param +0 -7
  106. data/test/multipart/filename_and_no_name +0 -6
  107. data/test/multipart/filename_with_encoded_words +0 -7
  108. data/test/multipart/filename_with_escaped_quotes +0 -6
  109. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  110. data/test/multipart/filename_with_null_byte +0 -7
  111. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_single_quote +0 -7
  113. data/test/multipart/filename_with_unescaped_percentages +0 -6
  114. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  116. data/test/multipart/filename_with_unescaped_quotes +0 -6
  117. data/test/multipart/ie +0 -6
  118. data/test/multipart/invalid_character +0 -6
  119. data/test/multipart/mixed_files +0 -21
  120. data/test/multipart/nested +0 -10
  121. data/test/multipart/none +0 -9
  122. data/test/multipart/quoted +0 -15
  123. data/test/multipart/rack-logo.png +0 -0
  124. data/test/multipart/semicolon +0 -6
  125. data/test/multipart/text +0 -15
  126. data/test/multipart/three_files_three_fields +0 -31
  127. data/test/multipart/unity3d_wwwform +0 -11
  128. data/test/multipart/webkit +0 -32
  129. data/test/rackup/config.ru +0 -31
  130. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  131. data/test/spec_auth_basic.rb +0 -89
  132. data/test/spec_auth_digest.rb +0 -260
  133. data/test/spec_body_proxy.rb +0 -85
  134. data/test/spec_builder.rb +0 -233
  135. data/test/spec_cascade.rb +0 -63
  136. data/test/spec_cgi.rb +0 -84
  137. data/test/spec_chunked.rb +0 -103
  138. data/test/spec_common_logger.rb +0 -95
  139. data/test/spec_conditional_get.rb +0 -103
  140. data/test/spec_config.rb +0 -23
  141. data/test/spec_content_length.rb +0 -86
  142. data/test/spec_content_type.rb +0 -46
  143. data/test/spec_deflater.rb +0 -375
  144. data/test/spec_directory.rb +0 -148
  145. data/test/spec_etag.rb +0 -108
  146. data/test/spec_events.rb +0 -133
  147. data/test/spec_fastcgi.rb +0 -85
  148. data/test/spec_file.rb +0 -264
  149. data/test/spec_handler.rb +0 -57
  150. data/test/spec_head.rb +0 -46
  151. data/test/spec_lint.rb +0 -515
  152. data/test/spec_lobster.rb +0 -59
  153. data/test/spec_lock.rb +0 -204
  154. data/test/spec_logger.rb +0 -24
  155. data/test/spec_media_type.rb +0 -42
  156. data/test/spec_method_override.rb +0 -110
  157. data/test/spec_mime.rb +0 -51
  158. data/test/spec_mock.rb +0 -359
  159. data/test/spec_multipart.rb +0 -722
  160. data/test/spec_null_logger.rb +0 -21
  161. data/test/spec_recursive.rb +0 -75
  162. data/test/spec_request.rb +0 -1398
  163. data/test/spec_response.rb +0 -510
  164. data/test/spec_rewindable_input.rb +0 -128
  165. data/test/spec_runtime.rb +0 -50
  166. data/test/spec_sendfile.rb +0 -125
  167. data/test/spec_server.rb +0 -193
  168. data/test/spec_session_abstract_id.rb +0 -31
  169. data/test/spec_session_abstract_session_hash.rb +0 -45
  170. data/test/spec_session_cookie.rb +0 -442
  171. data/test/spec_session_memcache.rb +0 -320
  172. data/test/spec_session_pool.rb +0 -210
  173. data/test/spec_show_exceptions.rb +0 -93
  174. data/test/spec_show_status.rb +0 -104
  175. data/test/spec_static.rb +0 -184
  176. data/test/spec_tempfile_reaper.rb +0 -64
  177. data/test/spec_thin.rb +0 -96
  178. data/test/spec_urlmap.rb +0 -237
  179. data/test/spec_utils.rb +0 -742
  180. data/test/spec_version.rb +0 -11
  181. data/test/spec_webrick.rb +0 -206
  182. data/test/static/another/index.html +0 -1
  183. data/test/static/foo.html +0 -1
  184. data/test/static/index.html +0 -1
  185. data/test/testrequest.rb +0 -78
  186. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  187. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MethodOverride
3
5
  HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
4
6
 
5
- METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
6
- HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
7
+ METHOD_OVERRIDE_PARAM_KEY = "_method"
8
+ HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
7
9
  ALLOWED_METHODS = %w[POST]
8
10
 
9
11
  def initialize(app)
data/lib/rack/mime.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Mime
3
5
  # Returns String with mime type if found, otherwise use +fallback+.
@@ -13,7 +15,7 @@ module Rack
13
15
  # This is a shortcut for:
14
16
  # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
15
17
 
16
- def mime_type(ext, fallback='application/octet-stream')
18
+ def mime_type(ext, fallback = 'application/octet-stream')
17
19
  MIME_TYPES.fetch(ext.to_s.downcase, fallback)
18
20
  end
19
21
  module_function :mime_type
@@ -306,6 +308,7 @@ module Rack
306
308
  ".lvp" => "audio/vnd.lucent.voice",
307
309
  ".lwp" => "application/vnd.lotus-wordpro",
308
310
  ".m3u" => "audio/x-mpegurl",
311
+ ".m3u8" => "application/x-mpegurl",
309
312
  ".m4a" => "audio/mp4a-latm",
310
313
  ".m4v" => "video/mp4",
311
314
  ".ma" => "application/mathematica",
@@ -343,6 +346,7 @@ module Rack
343
346
  ".mp4s" => "application/mp4",
344
347
  ".mp4v" => "video/mp4",
345
348
  ".mpc" => "application/vnd.mophun.certificate",
349
+ ".mpd" => "application/dash+xml",
346
350
  ".mpeg" => "video/mpeg",
347
351
  ".mpg" => "video/mpeg",
348
352
  ".mpga" => "audio/mpeg",
@@ -542,6 +546,7 @@ module Rack
542
546
  ".spp" => "application/scvp-vp-response",
543
547
  ".spq" => "application/scvp-vp-request",
544
548
  ".src" => "application/x-wais-source",
549
+ ".srt" => "text/srt",
545
550
  ".srx" => "application/sparql-results+xml",
546
551
  ".sse" => "application/vnd.kodak-descriptor",
547
552
  ".ssf" => "application/vnd.epson.ssf",
@@ -576,6 +581,7 @@ module Rack
576
581
  ".tr" => "text/troff",
577
582
  ".tra" => "application/vnd.trueapp",
578
583
  ".trm" => "application/x-msterminal",
584
+ ".ts" => "video/mp2t",
579
585
  ".tsv" => "text/tab-separated-values",
580
586
  ".ttf" => "application/octet-stream",
581
587
  ".twd" => "application/vnd.simtech-mindmapper",
@@ -600,9 +606,11 @@ module Rack
600
606
  ".vrml" => "model/vrml",
601
607
  ".vsd" => "application/vnd.visio",
602
608
  ".vsf" => "application/vnd.vsf",
609
+ ".vtt" => "text/vtt",
603
610
  ".vtu" => "model/vnd.vtu",
604
611
  ".vxml" => "application/voicexml+xml",
605
612
  ".war" => "application/java-archive",
613
+ ".wasm" => "application/wasm",
606
614
  ".wav" => "audio/x-wav",
607
615
  ".wax" => "audio/x-ms-wax",
608
616
  ".wbmp" => "image/vnd.wap.wbmp",
data/lib/rack/mock.rb CHANGED
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'stringio'
3
5
  require 'rack'
4
6
  require 'rack/lint'
5
7
  require 'rack/utils'
6
8
  require 'rack/response'
9
+ require 'cgi/cookie'
7
10
 
8
11
  module Rack
9
12
  # Rack::MockRequest helps testing your Rack application without
@@ -53,16 +56,16 @@ module Rack
53
56
  @app = app
54
57
  end
55
58
 
56
- def get(uri, opts={}) request(GET, uri, opts) end
57
- def post(uri, opts={}) request(POST, uri, opts) end
58
- def put(uri, opts={}) request(PUT, uri, opts) end
59
- def patch(uri, opts={}) request(PATCH, uri, opts) end
60
- def delete(uri, opts={}) request(DELETE, uri, opts) end
61
- def head(uri, opts={}) request(HEAD, uri, opts) end
62
- def options(uri, opts={}) request(OPTIONS, uri, opts) end
59
+ def get(uri, opts = {}) request(GET, uri, opts) end
60
+ def post(uri, opts = {}) request(POST, uri, opts) end
61
+ def put(uri, opts = {}) request(PUT, uri, opts) end
62
+ def patch(uri, opts = {}) request(PATCH, uri, opts) end
63
+ def delete(uri, opts = {}) request(DELETE, uri, opts) end
64
+ def head(uri, opts = {}) request(HEAD, uri, opts) end
65
+ def options(uri, opts = {}) request(OPTIONS, uri, opts) end
63
66
 
64
- def request(method=GET, uri="", opts={})
65
- env = self.class.env_for(uri, opts.merge(:method => method))
67
+ def request(method = GET, uri = "", opts = {})
68
+ env = self.class.env_for(uri, opts.merge(method: method))
66
69
 
67
70
  if opts[:lint]
68
71
  app = Rack::Lint.new(@app)
@@ -71,7 +74,7 @@ module Rack
71
74
  end
72
75
 
73
76
  errors = env[RACK_ERRORS]
74
- status, headers, body = app.call(env)
77
+ status, headers, body = app.call(env)
75
78
  MockResponse.new(status, headers, body, errors)
76
79
  ensure
77
80
  body.close if body.respond_to?(:close)
@@ -85,7 +88,7 @@ module Rack
85
88
  end
86
89
 
87
90
  # Return the Rack environment used for a request to +uri+.
88
- def self.env_for(uri="", opts={})
91
+ def self.env_for(uri = "", opts = {})
89
92
  uri = parse_uri_rfc2396(uri)
90
93
  uri.path = "/#{uri.path}" unless uri.path[0] == ?/
91
94
 
@@ -139,7 +142,7 @@ module Rack
139
142
  rack_input.set_encoding(Encoding::BINARY)
140
143
  env[RACK_INPUT] = rack_input
141
144
 
142
- env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s
145
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
143
146
 
144
147
  opts.each { |field, value|
145
148
  env[field] = value if String === field
@@ -155,16 +158,19 @@ module Rack
155
158
 
156
159
  class MockResponse < Rack::Response
157
160
  # Headers
158
- attr_reader :original_headers
161
+ attr_reader :original_headers, :cookies
159
162
 
160
163
  # Errors
161
164
  attr_accessor :errors
162
165
 
163
- def initialize(status, headers, body, errors=StringIO.new(""))
166
+ def initialize(status, headers, body, errors = StringIO.new(""))
164
167
  @original_headers = headers
165
168
  @errors = errors.string if errors.respond_to?(:string)
169
+ @cookies = parse_cookies_from_header
166
170
 
167
171
  super(body, status, headers)
172
+
173
+ buffered_body!
168
174
  end
169
175
 
170
176
  def =~(other)
@@ -186,11 +192,64 @@ module Rack
186
192
  # ...
187
193
  # res.body.should == "foo!"
188
194
  # end
189
- super.join
195
+ buffer = String.new
196
+
197
+ super.each do |chunk|
198
+ buffer << chunk
199
+ end
200
+
201
+ return buffer
190
202
  end
191
203
 
192
204
  def empty?
193
205
  [201, 204, 304].include? status
194
206
  end
207
+
208
+ def cookie(name)
209
+ cookies.fetch(name, nil)
210
+ end
211
+
212
+ private
213
+
214
+ def parse_cookies_from_header
215
+ cookies = Hash.new
216
+ if original_headers.has_key? 'Set-Cookie'
217
+ set_cookie_header = original_headers.fetch('Set-Cookie')
218
+ set_cookie_header.split("\n").each do |cookie|
219
+ cookie_name, cookie_filling = cookie.split('=', 2)
220
+ cookie_attributes = identify_cookie_attributes cookie_filling
221
+ parsed_cookie = CGI::Cookie.new(
222
+ 'name' => cookie_name.strip,
223
+ 'value' => cookie_attributes.fetch('value'),
224
+ 'path' => cookie_attributes.fetch('path', nil),
225
+ 'domain' => cookie_attributes.fetch('domain', nil),
226
+ 'expires' => cookie_attributes.fetch('expires', nil),
227
+ 'secure' => cookie_attributes.fetch('secure', false)
228
+ )
229
+ cookies.store(cookie_name, parsed_cookie)
230
+ end
231
+ end
232
+ cookies
233
+ end
234
+
235
+ def identify_cookie_attributes(cookie_filling)
236
+ cookie_bits = cookie_filling.split(';')
237
+ cookie_attributes = Hash.new
238
+ cookie_attributes.store('value', cookie_bits[0].strip)
239
+ cookie_bits.each do |bit|
240
+ if bit.include? '='
241
+ cookie_attribute, attribute_value = bit.split('=')
242
+ cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
243
+ if cookie_attribute.include? 'max-age'
244
+ cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
245
+ end
246
+ end
247
+ if bit.include? 'secure'
248
+ cookie_attributes.store('secure', true)
249
+ end
250
+ end
251
+ cookie_attributes
252
+ end
253
+
195
254
  end
196
255
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class Generator
@@ -27,21 +29,18 @@ module Rack
27
29
 
28
30
  private
29
31
  def multipart?
30
- multipart = false
31
-
32
32
  query = lambda { |value|
33
33
  case value
34
34
  when Array
35
- value.each(&query)
35
+ value.any?(&query)
36
36
  when Hash
37
- value.values.each(&query)
37
+ value.values.any?(&query)
38
38
  when Rack::Multipart::UploadedFile
39
- multipart = true
39
+ true
40
40
  end
41
41
  }
42
- @params.values.each(&query)
43
42
 
44
- multipart
43
+ @params.values.any?(&query)
45
44
  end
46
45
 
47
46
  def flattened_params
@@ -1,16 +1,24 @@
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
9
  class MultipartPartLimitError < Errno::EMFILE; end
6
10
 
7
11
  class Parser
8
- BUFSIZE = 16384
12
+ using ::Rack::RegexpExtensions
13
+
14
+ BUFSIZE = 1_048_576
9
15
  TEXT_PLAIN = "text/plain"
10
16
  TEMPFILE_FACTORY = lambda { |filename, content_type|
11
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
17
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
12
18
  }
13
19
 
20
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
21
+
14
22
  class BoundedIO # :nodoc:
15
23
  def initialize(io, content_length)
16
24
  @io = io
@@ -18,15 +26,15 @@ module Rack
18
26
  @cursor = 0
19
27
  end
20
28
 
21
- def read(size)
29
+ def read(size, outbuf = nil)
22
30
  return if @cursor >= @content_length
23
31
 
24
32
  left = @content_length - @cursor
25
33
 
26
34
  str = if left < size
27
- @io.read left
35
+ @io.read left, outbuf
28
36
  else
29
- @io.read size
37
+ @io.read size, outbuf
30
38
  end
31
39
 
32
40
  if str
@@ -39,8 +47,6 @@ module Rack
39
47
  str
40
48
  end
41
49
 
42
- def eof?; @content_length == @cursor; end
43
-
44
50
  def rewind
45
51
  @io.rewind
46
52
  end
@@ -63,13 +69,14 @@ module Rack
63
69
  return EMPTY unless boundary
64
70
 
65
71
  io = BoundedIO.new(io, content_length) if content_length
72
+ outbuf = String.new
66
73
 
67
74
  parser = new(boundary, tmpfile, bufsize, qp)
68
- parser.on_read io.read(bufsize), io.eof?
75
+ parser.on_read io.read(bufsize, outbuf)
69
76
 
70
77
  loop do
71
78
  break if parser.state == :DONE
72
- parser.on_read io.read(bufsize), io.eof?
79
+ parser.on_read io.read(bufsize, outbuf)
73
80
  end
74
81
 
75
82
  io.rewind
@@ -92,14 +99,14 @@ module Rack
92
99
  # those which give the lone filename.
93
100
  fn = filename.split(/[\/\\]/).last
94
101
 
95
- data = {:filename => fn, :type => content_type,
96
- :name => name, :tempfile => body, :head => head}
102
+ data = { filename: fn, type: content_type,
103
+ name: name, tempfile: body, head: head }
97
104
  elsif !filename && content_type && body.is_a?(IO)
98
105
  body.rewind
99
106
 
100
107
  # Generic multipart cases, not coming from a form
101
- data = {:type => content_type,
102
- :name => name, :tempfile => body, :head => head}
108
+ data = { type: content_type,
109
+ name: name, tempfile: body, head: head }
103
110
  end
104
111
 
105
112
  yield data
@@ -140,6 +147,7 @@ module Rack
140
147
  end
141
148
 
142
149
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
150
+
143
151
  check_open_files
144
152
  end
145
153
 
@@ -165,25 +173,26 @@ module Rack
165
173
  attr_reader :state
166
174
 
167
175
  def initialize(boundary, tempfile, bufsize, query_parser)
168
- @buf = String.new
169
-
170
176
  @query_parser = query_parser
171
177
  @params = query_parser.make_params
172
178
  @boundary = "--#{boundary}"
173
179
  @bufsize = bufsize
174
180
 
175
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
176
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
177
181
  @full_boundary = @boundary
178
182
  @end_boundary = @boundary + '--'
179
183
  @state = :FAST_FORWARD
180
184
  @mime_index = 0
181
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
182
191
  end
183
192
 
184
- def on_read content, eof
185
- handle_empty_content!(content, eof)
186
- @buf << content
193
+ def on_read content
194
+ handle_empty_content!(content)
195
+ @sbuf.concat content
187
196
  run_parser
188
197
  end
189
198
 
@@ -194,7 +203,6 @@ module Rack
194
203
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
195
204
  end
196
205
  end
197
-
198
206
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
199
207
  end
200
208
 
@@ -221,7 +229,7 @@ module Rack
221
229
  if consume_boundary
222
230
  @state = :MIME_HEAD
223
231
  else
224
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
232
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
225
233
  :want_read
226
234
  end
227
235
  end
@@ -229,19 +237,16 @@ module Rack
229
237
  def handle_consume_token
230
238
  tok = consume_boundary
231
239
  # 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
240
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
241
+ :DONE
234
242
  else
235
- @state = :MIME_HEAD
243
+ :MIME_HEAD
236
244
  end
237
245
  end
238
246
 
239
247
  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
-
248
+ if @sbuf.scan_until(@head_regex)
249
+ head = @sbuf[1]
245
250
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
246
251
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
247
252
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -252,7 +257,7 @@ module Rack
252
257
  filename = get_filename(head)
253
258
 
254
259
  if name.nil? || name.empty?
255
- name = filename || "#{content_type || TEXT_PLAIN}[]"
260
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
256
261
  end
257
262
 
258
263
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -263,16 +268,19 @@ module Rack
263
268
  end
264
269
 
265
270
  def handle_mime_body
266
- if i = @buf.index(rx)
267
- # Save the rest.
268
- @collector.on_mime_body @mime_index, @buf.slice!(0, i)
269
- @buf.slice!(0, 2) # Remove \r\n after the content
271
+ if @sbuf.check_until(@body_regex) # check but do not advance the pointer yet
272
+ body = @sbuf[1]
273
+ @collector.on_mime_body @mime_index, body
274
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
270
275
  @state = :CONSUME_TOKEN
271
276
  @mime_index += 1
272
277
  else
273
- # Save the read body part.
274
- if @rx_max_size < @buf.size
275
- @collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
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
276
284
  end
277
285
  :want_read
278
286
  end
@@ -280,16 +288,13 @@ module Rack
280
288
 
281
289
  def full_boundary; @full_boundary; end
282
290
 
283
- def rx; @rx; end
284
-
285
291
  def consume_boundary
286
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
287
- read_buffer = $1
292
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
288
293
  case read_buffer.strip
289
294
  when full_boundary then return :BOUNDARY
290
295
  when @end_boundary then return :END_BOUNDARY
291
296
  end
292
- return if @buf.empty?
297
+ return if @sbuf.eos?
293
298
  end
294
299
  end
295
300
 
@@ -310,8 +315,8 @@ module Rack
310
315
 
311
316
  return unless filename
312
317
 
313
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
314
- filename = Utils.unescape(filename)
318
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
319
+ filename = Utils.unescape_path(filename)
315
320
  end
316
321
 
317
322
  filename.scrub!
@@ -327,7 +332,7 @@ module Rack
327
332
  filename
328
333
  end
329
334
 
330
- CHARSET = "charset"
335
+ CHARSET = "charset"
331
336
 
332
337
  def tag_multipart_encoding(filename, content_type, name, body)
333
338
  name = name.to_s
@@ -344,10 +349,10 @@ module Rack
344
349
  if TEXT_PLAIN == type_subtype
345
350
  rest = list.drop 1
346
351
  rest.each do |param|
347
- k,v = param.split('=', 2)
352
+ k, v = param.split('=', 2)
348
353
  k.strip!
349
354
  v.strip!
350
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
355
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
351
356
  encoding = Encoding.find v if k == CHARSET
352
357
  end
353
358
  end
@@ -357,11 +362,9 @@ module Rack
357
362
  body.force_encoding(encoding)
358
363
  end
359
364
 
360
-
361
- def handle_empty_content!(content, eof)
365
+ def handle_empty_content!(content)
362
366
  if content.nil? || content.empty?
363
- raise EOFError if eof
364
- return true
367
+ raise EOFError
365
368
  end
366
369
  end
367
370
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class UploadedFile
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/multipart/parser'
2
4
 
3
5
  module Rack
@@ -14,10 +16,10 @@ module Rack
14
16
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
15
17
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
- BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
18
- BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
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:.*\s+name=(#{VALUE})/ni
22
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
21
23
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
22
24
  # Updated definitions from RFC 2231
23
25
  ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class NullLogger
3
5
  def initialize(app)