rack 2.0.9.4 → 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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/{HISTORY.md → CHANGELOG.md} +214 -164
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +79 -133
  5. data/Rakefile +25 -18
  6. data/SPEC +9 -9
  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 -11
  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 +15 -12
  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 -62
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +6 -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 +79 -26
  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 +40 -22
  65. data/lib/rack/session/cookie.rb +10 -9
  66. data/lib/rack/session/memcache.rb +4 -93
  67. data/lib/rack/session/pool.rb +4 -2
  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 +64 -93
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +33 -175
  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 -107
  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 -520
  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 -721
  160. data/test/spec_null_logger.rb +0 -21
  161. data/test/spec_recursive.rb +0 -75
  162. data/test/spec_request.rb +0 -1423
  163. data/test/spec_response.rb +0 -528
  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 -357
  172. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  173. data/test/spec_session_pool.rb +0 -247
  174. data/test/spec_show_exceptions.rb +0 -93
  175. data/test/spec_show_status.rb +0 -104
  176. data/test/spec_static.rb +0 -184
  177. data/test/spec_tempfile_reaper.rb +0 -64
  178. data/test/spec_thin.rb +0 -96
  179. data/test/spec_urlmap.rb +0 -237
  180. data/test/spec_utils.rb +0 -742
  181. data/test/spec_version.rb +0 -11
  182. data/test/spec_webrick.rb +0 -206
  183. data/test/static/another/index.html +0 -1
  184. data/test/static/foo.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::MediaType parse media type and parameters out of content_type string
3
5
 
@@ -13,7 +15,7 @@ module Rack
13
15
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
14
16
  def type(content_type)
15
17
  return nil unless content_type
16
- content_type.split(SPLIT_PATTERN, 2).first.downcase
18
+ content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
17
19
  end
18
20
 
19
21
  # The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -23,15 +25,18 @@ module Rack
23
25
  # { 'charset' => 'utf-8' }
24
26
  def params(content_type)
25
27
  return {} if content_type.nil?
26
- Hash[*content_type.split(SPLIT_PATTERN)[1..-1].
27
- collect { |s| s.split('=', 2) }.
28
- map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
28
+
29
+ content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
30
+ k, v = s.split('=', 2)
31
+
32
+ hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
33
+ end
29
34
  end
30
35
 
31
36
  private
32
37
 
33
38
  def strip_doublequotes(str)
34
- (str[0] == ?" && str[-1] == ?") ? str[1..-2] : str
39
+ (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
35
40
  end
36
41
  end
37
42
  end
@@ -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,17 +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
- class MultipartTotalPartLimitError < StandardError; end
7
10
 
8
11
  class Parser
9
- BUFSIZE = 16384
12
+ using ::Rack::RegexpExtensions
13
+
14
+ BUFSIZE = 1_048_576
10
15
  TEXT_PLAIN = "text/plain"
11
16
  TEMPFILE_FACTORY = lambda { |filename, content_type|
12
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
17
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
13
18
  }
14
19
 
20
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
21
+
15
22
  class BoundedIO # :nodoc:
16
23
  def initialize(io, content_length)
17
24
  @io = io
@@ -19,15 +26,15 @@ module Rack
19
26
  @cursor = 0
20
27
  end
21
28
 
22
- def read(size)
29
+ def read(size, outbuf = nil)
23
30
  return if @cursor >= @content_length
24
31
 
25
32
  left = @content_length - @cursor
26
33
 
27
34
  str = if left < size
28
- @io.read left
35
+ @io.read left, outbuf
29
36
  else
30
- @io.read size
37
+ @io.read size, outbuf
31
38
  end
32
39
 
33
40
  if str
@@ -62,13 +69,14 @@ module Rack
62
69
  return EMPTY unless boundary
63
70
 
64
71
  io = BoundedIO.new(io, content_length) if content_length
72
+ outbuf = String.new
65
73
 
66
74
  parser = new(boundary, tmpfile, bufsize, qp)
67
- parser.on_read io.read(bufsize)
75
+ parser.on_read io.read(bufsize, outbuf)
68
76
 
69
77
  loop do
70
78
  break if parser.state == :DONE
71
- parser.on_read io.read(bufsize)
79
+ parser.on_read io.read(bufsize, outbuf)
72
80
  end
73
81
 
74
82
  io.rewind
@@ -91,14 +99,14 @@ module Rack
91
99
  # those which give the lone filename.
92
100
  fn = filename.split(/[\/\\]/).last
93
101
 
94
- data = {:filename => fn, :type => content_type,
95
- :name => name, :tempfile => body, :head => head}
102
+ data = { filename: fn, type: content_type,
103
+ name: name, tempfile: body, head: head }
96
104
  elsif !filename && content_type && body.is_a?(IO)
97
105
  body.rewind
98
106
 
99
107
  # Generic multipart cases, not coming from a form
100
- data = {:type => content_type,
101
- :name => name, :tempfile => body, :head => head}
108
+ data = { type: content_type,
109
+ name: name, tempfile: body, head: head }
102
110
  end
103
111
 
104
112
  yield data
@@ -140,7 +148,7 @@ module Rack
140
148
 
141
149
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
142
150
 
143
- check_part_limits
151
+ check_open_files
144
152
  end
145
153
 
146
154
  def on_mime_body mime_index, content
@@ -152,48 +160,39 @@ module Rack
152
160
 
153
161
  private
154
162
 
155
- def check_part_limits
156
- file_limit = Utils.multipart_file_limit
157
- part_limit = Utils.multipart_total_part_limit
158
-
159
- if file_limit && file_limit > 0
160
- if @open_files >= file_limit
163
+ def check_open_files
164
+ if Utils.multipart_part_limit > 0
165
+ if @open_files >= Utils.multipart_part_limit
161
166
  @mime_parts.each(&:close)
162
167
  raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
163
168
  end
164
169
  end
165
-
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
172
170
  end
173
171
  end
174
172
 
175
173
  attr_reader :state
176
174
 
177
175
  def initialize(boundary, tempfile, bufsize, query_parser)
178
- @buf = String.new
179
-
180
176
  @query_parser = query_parser
181
177
  @params = query_parser.make_params
182
178
  @boundary = "--#{boundary}"
183
179
  @bufsize = bufsize
184
180
 
185
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
186
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
187
181
  @full_boundary = @boundary
188
182
  @end_boundary = @boundary + '--'
189
183
  @state = :FAST_FORWARD
190
184
  @mime_index = 0
191
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
192
191
  end
193
192
 
194
193
  def on_read content
195
194
  handle_empty_content!(content)
196
- @buf << content
195
+ @sbuf.concat content
197
196
  run_parser
198
197
  end
199
198
 
@@ -204,7 +203,6 @@ module Rack
204
203
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
205
204
  end
206
205
  end
207
-
208
206
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
209
207
  end
210
208
 
@@ -231,7 +229,7 @@ module Rack
231
229
  if consume_boundary
232
230
  @state = :MIME_HEAD
233
231
  else
234
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
232
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
235
233
  :want_read
236
234
  end
237
235
  end
@@ -239,19 +237,16 @@ module Rack
239
237
  def handle_consume_token
240
238
  tok = consume_boundary
241
239
  # 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
240
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
241
+ :DONE
244
242
  else
245
- @state = :MIME_HEAD
243
+ :MIME_HEAD
246
244
  end
247
245
  end
248
246
 
249
247
  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
-
248
+ if @sbuf.scan_until(@head_regex)
249
+ head = @sbuf[1]
255
250
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
256
251
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
257
252
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -262,7 +257,7 @@ module Rack
262
257
  filename = get_filename(head)
263
258
 
264
259
  if name.nil? || name.empty?
265
- name = filename || "#{content_type || TEXT_PLAIN}[]"
260
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
266
261
  end
267
262
 
268
263
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -273,16 +268,19 @@ module Rack
273
268
  end
274
269
 
275
270
  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
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
280
275
  @state = :CONSUME_TOKEN
281
276
  @mime_index += 1
282
277
  else
283
- # Save the read body part.
284
- if @rx_max_size < @buf.size
285
- @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
286
284
  end
287
285
  :want_read
288
286
  end
@@ -290,16 +288,13 @@ module Rack
290
288
 
291
289
  def full_boundary; @full_boundary; end
292
290
 
293
- def rx; @rx; end
294
-
295
291
  def consume_boundary
296
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
297
- read_buffer = $1
292
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
293
  case read_buffer.strip
299
294
  when full_boundary then return :BOUNDARY
300
295
  when @end_boundary then return :END_BOUNDARY
301
296
  end
302
- return if @buf.empty?
297
+ return if @sbuf.eos?
303
298
  end
304
299
  end
305
300
 
@@ -314,15 +309,14 @@ module Rack
314
309
  elsif filename = params['filename*']
315
310
  encoding, _, filename = filename.split("'", 3)
316
311
  end
317
- when BROKEN
312
+ when BROKEN_QUOTED, BROKEN_UNQUOTED
318
313
  filename = $1
319
- filename = $1 if filename =~ /^"(.*)"$/
320
314
  end
321
315
 
322
316
  return unless filename
323
317
 
324
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
325
- filename = Utils.unescape(filename)
318
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
319
+ filename = Utils.unescape_path(filename)
326
320
  end
327
321
 
328
322
  filename.scrub!
@@ -338,7 +332,7 @@ module Rack
338
332
  filename
339
333
  end
340
334
 
341
- CHARSET = "charset"
335
+ CHARSET = "charset"
342
336
 
343
337
  def tag_multipart_encoding(filename, content_type, name, body)
344
338
  name = name.to_s
@@ -355,10 +349,10 @@ module Rack
355
349
  if TEXT_PLAIN == type_subtype
356
350
  rest = list.drop 1
357
351
  rest.each do |param|
358
- k,v = param.split('=', 2)
352
+ k, v = param.split('=', 2)
359
353
  k.strip!
360
354
  v.strip!
361
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
355
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
362
356
  encoding = Encoding.find v if k == CHARSET
363
357
  end
364
358
  end
@@ -368,7 +362,6 @@ module Rack
368
362
  body.force_encoding(encoding)
369
363
  end
370
364
 
371
-
372
365
  def handle_empty_content!(content)
373
366
  if content.nil? || content.empty?
374
367
  raise EOFError
@@ -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,12 +16,13 @@ module Rack
14
16
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
15
17
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
- BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
19
+ BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
20
+ BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
18
21
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
19
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s+name=(#{VALUE})/ni
22
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
20
23
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
21
24
  # Updated definitions from RFC 2231
22
- ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
+ ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
23
26
  ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
24
27
  SECTION = /\*[0-9]+/
25
28
  REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class NullLogger
3
5
  def initialize(app)