rack 2.0.9.3 → 2.1.4.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +76 -116
  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 +39 -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 +32 -17
  29. data/lib/rack/directory.rb +19 -16
  30. data/lib/rack/etag.rb +3 -1
  31. data/lib/rack/events.rb +5 -3
  32. data/lib/rack/file.rb +4 -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 +50 -45
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +3 -1
  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 -18
  64. data/lib/rack/session/abstract/id.rb +30 -20
  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 +58 -71
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +28 -170
  77. data/HISTORY.md +0 -520
  78. data/test/builder/an_underscore_app.rb +0 -5
  79. data/test/builder/anything.rb +0 -5
  80. data/test/builder/comment.ru +0 -4
  81. data/test/builder/end.ru +0 -5
  82. data/test/builder/line.ru +0 -1
  83. data/test/builder/options.ru +0 -2
  84. data/test/cgi/assets/folder/test.js +0 -1
  85. data/test/cgi/assets/fonts/font.eot +0 -1
  86. data/test/cgi/assets/images/image.png +0 -1
  87. data/test/cgi/assets/index.html +0 -1
  88. data/test/cgi/assets/javascripts/app.js +0 -1
  89. data/test/cgi/assets/stylesheets/app.css +0 -1
  90. data/test/cgi/lighttpd.conf +0 -26
  91. data/test/cgi/rackup_stub.rb +0 -6
  92. data/test/cgi/sample_rackup.ru +0 -5
  93. data/test/cgi/test +0 -9
  94. data/test/cgi/test+directory/test+file +0 -1
  95. data/test/cgi/test.fcgi +0 -9
  96. data/test/cgi/test.gz +0 -0
  97. data/test/cgi/test.ru +0 -5
  98. data/test/gemloader.rb +0 -10
  99. data/test/helper.rb +0 -34
  100. data/test/multipart/bad_robots +0 -259
  101. data/test/multipart/binary +0 -0
  102. data/test/multipart/content_type_and_no_filename +0 -6
  103. data/test/multipart/empty +0 -10
  104. data/test/multipart/fail_16384_nofile +0 -814
  105. data/test/multipart/file1.txt +0 -1
  106. data/test/multipart/filename_and_modification_param +0 -7
  107. data/test/multipart/filename_and_no_name +0 -6
  108. data/test/multipart/filename_with_encoded_words +0 -7
  109. data/test/multipart/filename_with_escaped_quotes +0 -6
  110. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  111. data/test/multipart/filename_with_null_byte +0 -7
  112. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  113. data/test/multipart/filename_with_single_quote +0 -7
  114. data/test/multipart/filename_with_unescaped_percentages +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  116. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  117. data/test/multipart/filename_with_unescaped_quotes +0 -6
  118. data/test/multipart/ie +0 -6
  119. data/test/multipart/invalid_character +0 -6
  120. data/test/multipart/mixed_files +0 -21
  121. data/test/multipart/nested +0 -10
  122. data/test/multipart/none +0 -9
  123. data/test/multipart/quoted +0 -15
  124. data/test/multipart/rack-logo.png +0 -0
  125. data/test/multipart/semicolon +0 -6
  126. data/test/multipart/text +0 -15
  127. data/test/multipart/three_files_three_fields +0 -31
  128. data/test/multipart/unity3d_wwwform +0 -11
  129. data/test/multipart/webkit +0 -32
  130. data/test/rackup/config.ru +0 -31
  131. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  132. data/test/spec_auth_basic.rb +0 -89
  133. data/test/spec_auth_digest.rb +0 -260
  134. data/test/spec_body_proxy.rb +0 -85
  135. data/test/spec_builder.rb +0 -233
  136. data/test/spec_cascade.rb +0 -63
  137. data/test/spec_cgi.rb +0 -84
  138. data/test/spec_chunked.rb +0 -103
  139. data/test/spec_common_logger.rb +0 -107
  140. data/test/spec_conditional_get.rb +0 -103
  141. data/test/spec_config.rb +0 -23
  142. data/test/spec_content_length.rb +0 -86
  143. data/test/spec_content_type.rb +0 -46
  144. data/test/spec_deflater.rb +0 -375
  145. data/test/spec_directory.rb +0 -148
  146. data/test/spec_etag.rb +0 -108
  147. data/test/spec_events.rb +0 -133
  148. data/test/spec_fastcgi.rb +0 -85
  149. data/test/spec_file.rb +0 -264
  150. data/test/spec_handler.rb +0 -57
  151. data/test/spec_head.rb +0 -46
  152. data/test/spec_lint.rb +0 -520
  153. data/test/spec_lobster.rb +0 -59
  154. data/test/spec_lock.rb +0 -204
  155. data/test/spec_logger.rb +0 -24
  156. data/test/spec_media_type.rb +0 -42
  157. data/test/spec_method_override.rb +0 -110
  158. data/test/spec_mime.rb +0 -51
  159. data/test/spec_mock.rb +0 -359
  160. data/test/spec_multipart.rb +0 -721
  161. data/test/spec_null_logger.rb +0 -21
  162. data/test/spec_recursive.rb +0 -75
  163. data/test/spec_request.rb +0 -1423
  164. data/test/spec_response.rb +0 -528
  165. data/test/spec_rewindable_input.rb +0 -128
  166. data/test/spec_runtime.rb +0 -50
  167. data/test/spec_sendfile.rb +0 -125
  168. data/test/spec_server.rb +0 -193
  169. data/test/spec_session_abstract_id.rb +0 -31
  170. data/test/spec_session_abstract_session_hash.rb +0 -45
  171. data/test/spec_session_cookie.rb +0 -442
  172. data/test/spec_session_memcache.rb +0 -357
  173. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  174. data/test/spec_session_pool.rb +0 -247
  175. data/test/spec_show_exceptions.rb +0 -93
  176. data/test/spec_show_status.rb +0 -104
  177. data/test/spec_static.rb +0 -184
  178. data/test/spec_tempfile_reaper.rb +0 -64
  179. data/test/spec_thin.rb +0 -96
  180. data/test/spec_urlmap.rb +0 -237
  181. data/test/spec_utils.rb +0 -742
  182. data/test/spec_version.rb +0 -11
  183. data/test/spec_webrick.rb +0 -206
  184. data/test/static/another/index.html +0 -1
  185. data/test/static/foo.html +0 -1
  186. data/test/static/index.html +0 -1
  187. data/test/testrequest.rb +0 -78
  188. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  189. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/lint.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
  require 'forwardable'
3
5
 
@@ -33,7 +35,7 @@ module Rack
33
35
 
34
36
  ## A Rack application is a Ruby object (not a class) that
35
37
  ## responds to +call+.
36
- def call(env=nil)
38
+ def call(env = nil)
37
39
  dup._call(env)
38
40
  end
39
41
 
@@ -123,9 +125,8 @@ module Rack
123
125
  ## the presence or absence of the
124
126
  ## appropriate HTTP header in the
125
127
  ## request. See
126
- ## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
127
- ## RFC3875 section 4.1.18</a> for
128
- ## specific behavior.
128
+ ## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
129
+ ## for specific behavior.
129
130
 
130
131
  ## In addition to this, the Rack environment must include these
131
132
  ## Rack-specific variables:
@@ -263,7 +264,7 @@ module Rack
263
264
  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
264
265
  ## (use the versions without <tt>HTTP_</tt>).
265
266
  %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
266
- assert("env contains #{header}, must use #{header[5,-1]}") {
267
+ assert("env contains #{header}, must use #{header[5, -1]}") {
267
268
  not env.include? header
268
269
  }
269
270
  }
@@ -626,15 +627,17 @@ module Rack
626
627
  assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
627
628
  header.respond_to? :each
628
629
  }
629
- header.each { |key, value|
630
- ## Special headers starting "rack." are for communicating with the
631
- ## server, and must not be sent back to the client.
632
- next if key =~ /^rack\..+$/
633
630
 
631
+ header.each { |key, value|
634
632
  ## The header keys must be Strings.
635
633
  assert("header key must be a string, was #{key.class}") {
636
634
  key.kind_of? String
637
635
  }
636
+
637
+ ## Special headers starting "rack." are for communicating with the
638
+ ## server, and must not be sent back to the client.
639
+ next if key =~ /^rack\..+$/
640
+
638
641
  ## The header must not contain a +Status+ key.
639
642
  assert("header must not contain Status") { key.downcase != "status" }
640
643
  ## The header must conform to RFC7230 token specification, i.e. cannot
@@ -662,7 +665,7 @@ module Rack
662
665
  ## 204 or 304.
663
666
  if key.downcase == "content-type"
664
667
  assert("Content-Type header found in #{status} response, not allowed") {
665
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
668
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
666
669
  }
667
670
  return
668
671
  end
@@ -676,7 +679,7 @@ module Rack
676
679
  ## There must not be a <tt>Content-Length</tt> header when the
677
680
  ## +Status+ is 1xx, 204 or 304.
678
681
  assert("Content-Length header found in #{status} response, not allowed") {
679
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
682
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
680
683
  }
681
684
  @content_length = value
682
685
  end
data/lib/rack/lobster.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  require 'rack/request'
@@ -25,8 +27,8 @@ module Rack
25
27
  content = ["<title>Lobstericious!</title>",
26
28
  "<pre>", lobster, "</pre>",
27
29
  "<a href='#{href}'>flip!</a>"]
28
- length = content.inject(0) { |a,e| a+e.size }.to_s
29
- [200, {CONTENT_TYPE => "text/html", CONTENT_LENGTH => length}, content]
30
+ length = content.inject(0) { |a, e| a + e.size }.to_s
31
+ [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
30
32
  }
31
33
 
32
34
  def call(env)
@@ -37,8 +39,8 @@ module Rack
37
39
  gsub('\\', 'TEMP').
38
40
  gsub('/', '\\').
39
41
  gsub('TEMP', '/').
40
- gsub('{','}').
41
- gsub('(',')')
42
+ gsub('{', '}').
43
+ gsub('(', ')')
42
44
  end.join("\n")
43
45
  href = "?flip=right"
44
46
  elsif req.GET["flip"] == "crash"
@@ -65,6 +67,6 @@ if $0 == __FILE__
65
67
  require 'rack'
66
68
  require 'rack/show_exceptions'
67
69
  Rack::Server.start(
68
- :app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292
70
+ app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
69
71
  )
70
72
  end
data/lib/rack/lock.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
  require 'rack/body_proxy'
3
5
 
data/lib/rack/logger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
 
3
5
  module Rack
@@ -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,4 +1,8 @@
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
@@ -6,12 +10,16 @@ module Rack
6
10
  class MultipartTotalPartLimitError < StandardError; end
7
11
 
8
12
  class Parser
9
- BUFSIZE = 16384
13
+ using ::Rack::RegexpExtensions
14
+
15
+ BUFSIZE = 1_048_576
10
16
  TEXT_PLAIN = "text/plain"
11
17
  TEMPFILE_FACTORY = lambda { |filename, content_type|
12
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
18
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
13
19
  }
14
20
 
21
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
+
15
23
  class BoundedIO # :nodoc:
16
24
  def initialize(io, content_length)
17
25
  @io = io
@@ -19,15 +27,15 @@ module Rack
19
27
  @cursor = 0
20
28
  end
21
29
 
22
- def read(size)
30
+ def read(size, outbuf = nil)
23
31
  return if @cursor >= @content_length
24
32
 
25
33
  left = @content_length - @cursor
26
34
 
27
35
  str = if left < size
28
- @io.read left
36
+ @io.read left, outbuf
29
37
  else
30
- @io.read size
38
+ @io.read size, outbuf
31
39
  end
32
40
 
33
41
  if str
@@ -62,13 +70,14 @@ module Rack
62
70
  return EMPTY unless boundary
63
71
 
64
72
  io = BoundedIO.new(io, content_length) if content_length
73
+ outbuf = String.new
65
74
 
66
75
  parser = new(boundary, tmpfile, bufsize, qp)
67
- parser.on_read io.read(bufsize)
76
+ parser.on_read io.read(bufsize, outbuf)
68
77
 
69
78
  loop do
70
79
  break if parser.state == :DONE
71
- parser.on_read io.read(bufsize)
80
+ parser.on_read io.read(bufsize, outbuf)
72
81
  end
73
82
 
74
83
  io.rewind
@@ -91,14 +100,14 @@ module Rack
91
100
  # those which give the lone filename.
92
101
  fn = filename.split(/[\/\\]/).last
93
102
 
94
- data = {:filename => fn, :type => content_type,
95
- :name => name, :tempfile => body, :head => head}
103
+ data = { filename: fn, type: content_type,
104
+ name: name, tempfile: body, head: head }
96
105
  elsif !filename && content_type && body.is_a?(IO)
97
106
  body.rewind
98
107
 
99
108
  # Generic multipart cases, not coming from a form
100
- data = {:type => content_type,
101
- :name => name, :tempfile => body, :head => head}
109
+ data = { type: content_type,
110
+ name: name, tempfile: body, head: head }
102
111
  end
103
112
 
104
113
  yield data
@@ -175,25 +184,26 @@ module Rack
175
184
  attr_reader :state
176
185
 
177
186
  def initialize(boundary, tempfile, bufsize, query_parser)
178
- @buf = String.new
179
-
180
187
  @query_parser = query_parser
181
188
  @params = query_parser.make_params
182
189
  @boundary = "--#{boundary}"
183
190
  @bufsize = bufsize
184
191
 
185
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
186
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
187
192
  @full_boundary = @boundary
188
193
  @end_boundary = @boundary + '--'
189
194
  @state = :FAST_FORWARD
190
195
  @mime_index = 0
191
196
  @collector = Collector.new tempfile
197
+
198
+ @sbuf = StringScanner.new("".dup)
199
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
200
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
201
+ @head_regex = /(.*?#{EOL})#{EOL}/m
192
202
  end
193
203
 
194
204
  def on_read content
195
205
  handle_empty_content!(content)
196
- @buf << content
206
+ @sbuf.concat content
197
207
  run_parser
198
208
  end
199
209
 
@@ -204,7 +214,6 @@ module Rack
204
214
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
205
215
  end
206
216
  end
207
-
208
217
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
209
218
  end
210
219
 
@@ -231,7 +240,7 @@ module Rack
231
240
  if consume_boundary
232
241
  @state = :MIME_HEAD
233
242
  else
234
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
243
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
235
244
  :want_read
236
245
  end
237
246
  end
@@ -239,19 +248,16 @@ module Rack
239
248
  def handle_consume_token
240
249
  tok = consume_boundary
241
250
  # 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
251
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
252
+ :DONE
244
253
  else
245
- @state = :MIME_HEAD
254
+ :MIME_HEAD
246
255
  end
247
256
  end
248
257
 
249
258
  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
-
259
+ if @sbuf.scan_until(@head_regex)
260
+ head = @sbuf[1]
255
261
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
256
262
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
257
263
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -262,7 +268,7 @@ module Rack
262
268
  filename = get_filename(head)
263
269
 
264
270
  if name.nil? || name.empty?
265
- name = filename || "#{content_type || TEXT_PLAIN}[]"
271
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
266
272
  end
267
273
 
268
274
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -273,16 +279,19 @@ module Rack
273
279
  end
274
280
 
275
281
  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
282
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
283
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
284
+ @collector.on_mime_body @mime_index, body
285
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
280
286
  @state = :CONSUME_TOKEN
281
287
  @mime_index += 1
282
288
  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)
289
+ # Save what we have so far
290
+ if @rx_max_size < @sbuf.rest_size
291
+ delta = @sbuf.rest_size - @rx_max_size
292
+ @collector.on_mime_body @mime_index, @sbuf.peek(delta)
293
+ @sbuf.pos += delta
294
+ @sbuf.string = @sbuf.rest
286
295
  end
287
296
  :want_read
288
297
  end
@@ -290,16 +299,13 @@ module Rack
290
299
 
291
300
  def full_boundary; @full_boundary; end
292
301
 
293
- def rx; @rx; end
294
-
295
302
  def consume_boundary
296
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
297
- read_buffer = $1
303
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
298
304
  case read_buffer.strip
299
305
  when full_boundary then return :BOUNDARY
300
306
  when @end_boundary then return :END_BOUNDARY
301
307
  end
302
- return if @buf.empty?
308
+ return if @sbuf.eos?
303
309
  end
304
310
  end
305
311
 
@@ -321,8 +327,8 @@ module Rack
321
327
 
322
328
  return unless filename
323
329
 
324
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
325
- filename = Utils.unescape(filename)
330
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
331
+ filename = Utils.unescape_path(filename)
326
332
  end
327
333
 
328
334
  filename.scrub!
@@ -338,7 +344,7 @@ module Rack
338
344
  filename
339
345
  end
340
346
 
341
- CHARSET = "charset"
347
+ CHARSET = "charset"
342
348
 
343
349
  def tag_multipart_encoding(filename, content_type, name, body)
344
350
  name = name.to_s
@@ -355,10 +361,10 @@ module Rack
355
361
  if TEXT_PLAIN == type_subtype
356
362
  rest = list.drop 1
357
363
  rest.each do |param|
358
- k,v = param.split('=', 2)
364
+ k, v = param.split('=', 2)
359
365
  k.strip!
360
366
  v.strip!
361
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
367
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
362
368
  encoding = Encoding.find v if k == CHARSET
363
369
  end
364
370
  end
@@ -368,7 +374,6 @@ module Rack
368
374
  body.force_encoding(encoding)
369
375
  end
370
376
 
371
-
372
377
  def handle_empty_content!(content)
373
378
  if content.nil? || content.empty?
374
379
  raise EOFError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class UploadedFile