rack 2.0.1 → 2.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +795 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +188 -145
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +46 -17
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +6 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +5 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +37 -25
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +60 -70
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +8 -5
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +3 -3
  40. data/lib/rack/handler/webrick.rb +19 -10
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +221 -186
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +14 -4
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +23 -8
  48. data/lib/rack/method_override.rb +13 -4
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +135 -29
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +85 -68
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +6 -5
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +108 -36
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +232 -60
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +14 -10
  64. data/lib/rack/server.rb +97 -25
  65. data/lib/rack/session/abstract/id.rb +113 -25
  66. data/lib/rack/session/cookie.rb +22 -14
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +24 -10
  69. data/lib/rack/show_exceptions.rb +22 -18
  70. data/lib/rack/show_status.rb +9 -9
  71. data/lib/rack/static.rb +25 -12
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +13 -7
  74. data/lib/rack/utils.rb +135 -123
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -29
  78. metadata +25 -184
  79. data/HISTORY.md +0 -505
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  114. data/test/multipart/filename_with_single_quote +0 -7
  115. data/test/multipart/filename_with_unescaped_percentages +0 -6
  116. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  118. data/test/multipart/filename_with_unescaped_quotes +0 -6
  119. data/test/multipart/ie +0 -6
  120. data/test/multipart/invalid_character +0 -6
  121. data/test/multipart/mixed_files +0 -21
  122. data/test/multipart/nested +0 -10
  123. data/test/multipart/none +0 -9
  124. data/test/multipart/quoted +0 -15
  125. data/test/multipart/rack-logo.png +0 -0
  126. data/test/multipart/semicolon +0 -6
  127. data/test/multipart/text +0 -15
  128. data/test/multipart/three_files_three_fields +0 -31
  129. data/test/multipart/unity3d_wwwform +0 -11
  130. data/test/multipart/webkit +0 -32
  131. data/test/rackup/config.ru +0 -31
  132. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  133. data/test/spec_auth_basic.rb +0 -89
  134. data/test/spec_auth_digest.rb +0 -260
  135. data/test/spec_body_proxy.rb +0 -85
  136. data/test/spec_builder.rb +0 -233
  137. data/test/spec_cascade.rb +0 -63
  138. data/test/spec_cgi.rb +0 -84
  139. data/test/spec_chunked.rb +0 -103
  140. data/test/spec_common_logger.rb +0 -95
  141. data/test/spec_conditional_get.rb +0 -103
  142. data/test/spec_config.rb +0 -23
  143. data/test/spec_content_length.rb +0 -86
  144. data/test/spec_content_type.rb +0 -46
  145. data/test/spec_deflater.rb +0 -365
  146. data/test/spec_directory.rb +0 -148
  147. data/test/spec_etag.rb +0 -108
  148. data/test/spec_events.rb +0 -133
  149. data/test/spec_fastcgi.rb +0 -85
  150. data/test/spec_file.rb +0 -251
  151. data/test/spec_handler.rb +0 -57
  152. data/test/spec_head.rb +0 -46
  153. data/test/spec_lint.rb +0 -515
  154. data/test/spec_lobster.rb +0 -59
  155. data/test/spec_lock.rb +0 -194
  156. data/test/spec_logger.rb +0 -24
  157. data/test/spec_media_type.rb +0 -42
  158. data/test/spec_method_override.rb +0 -83
  159. data/test/spec_mime.rb +0 -51
  160. data/test/spec_mock.rb +0 -342
  161. data/test/spec_multipart.rb +0 -716
  162. data/test/spec_null_logger.rb +0 -21
  163. data/test/spec_recursive.rb +0 -75
  164. data/test/spec_request.rb +0 -1393
  165. data/test/spec_response.rb +0 -510
  166. data/test/spec_rewindable_input.rb +0 -128
  167. data/test/spec_runtime.rb +0 -50
  168. data/test/spec_sendfile.rb +0 -125
  169. data/test/spec_server.rb +0 -193
  170. data/test/spec_session_abstract_id.rb +0 -31
  171. data/test/spec_session_abstract_session_hash.rb +0 -28
  172. data/test/spec_session_cookie.rb +0 -442
  173. data/test/spec_session_memcache.rb +0 -320
  174. data/test/spec_session_pool.rb +0 -210
  175. data/test/spec_show_exceptions.rb +0 -80
  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 -208
  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/lobster.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'zlib'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rack/request'
4
- require 'rack/response'
3
+ require 'zlib'
5
4
 
6
5
  module Rack
7
6
  # Paste has a Pony, Rack has a Lobster!
@@ -25,8 +24,8 @@ module Rack
25
24
  content = ["<title>Lobstericious!</title>",
26
25
  "<pre>", lobster, "</pre>",
27
26
  "<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]
27
+ length = content.inject(0) { |a, e| a + e.size }.to_s
28
+ [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
30
29
  }
31
30
 
32
31
  def call(env)
@@ -37,8 +36,8 @@ module Rack
37
36
  gsub('\\', 'TEMP').
38
37
  gsub('/', '\\').
39
38
  gsub('TEMP', '/').
40
- gsub('{','}').
41
- gsub('(',')')
39
+ gsub('{', '}').
40
+ gsub('(', ')')
42
41
  end.join("\n")
43
42
  href = "?flip=right"
44
43
  elsif req.GET["flip"] == "crash"
@@ -62,9 +61,10 @@ module Rack
62
61
  end
63
62
 
64
63
  if $0 == __FILE__
65
- require 'rack'
66
- require 'rack/show_exceptions'
64
+ # :nocov:
65
+ require_relative '../rack'
67
66
  Rack::Server.start(
68
- :app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292
67
+ app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
69
68
  )
69
+ # :nocov:
70
70
  end
data/lib/rack/lock.rb CHANGED
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
- require 'rack/body_proxy'
3
4
 
4
5
  module Rack
5
6
  # Rack::Lock locks every request inside a mutex, so that every request
@@ -11,12 +12,21 @@ module Rack
11
12
 
12
13
  def call(env)
13
14
  @mutex.lock
15
+ @env = env
16
+ @old_rack_multithread = env[RACK_MULTITHREAD]
14
17
  begin
15
- response = @app.call(env.merge(RACK_MULTITHREAD => false))
16
- returned = response << BodyProxy.new(response.pop) { @mutex.unlock }
18
+ response = @app.call(env.merge!(RACK_MULTITHREAD => false))
19
+ returned = response << BodyProxy.new(response.pop) { unlock }
17
20
  ensure
18
- @mutex.unlock unless returned
21
+ unlock unless returned
19
22
  end
20
23
  end
24
+
25
+ private
26
+
27
+ def unlock
28
+ @mutex.unlock
29
+ @env[RACK_MULTITHREAD] = @old_rack_multithread
30
+ end
21
31
  end
22
32
  end
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,8 +1,10 @@
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
 
4
6
  class MediaType
5
- SPLIT_PATTERN = %r{\s*[;,]\s*}
7
+ SPLIT_PATTERN = /[;,]/
6
8
 
7
9
  class << self
8
10
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -13,7 +15,11 @@ 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
+ if type = content_type.split(SPLIT_PATTERN, 2).first
19
+ type.rstrip!
20
+ type.downcase!
21
+ type
22
+ end
17
23
  end
18
24
 
19
25
  # The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -21,18 +27,27 @@ module Rack
21
27
  # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
22
28
  # this method responds with the following Hash:
23
29
  # { 'charset' => 'utf-8' }
30
+ #
31
+ # This will pass back parameters with empty strings in the hash if they
32
+ # lack a value (e.g., "text/plain;charset=" will return { 'charset' => '' },
33
+ # and "text/plain;charset" will return { 'charset' => '' }, similarly to
34
+ # the query params parser (barring the latter case, which returns nil instead)).
24
35
  def params(content_type)
25
36
  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]
37
+
38
+ content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
39
+ s.strip!
40
+ k, v = s.split('=', 2)
41
+ k.downcase!
42
+ hsh[k] = strip_doublequotes(v)
43
+ end
29
44
  end
30
45
 
31
46
  private
32
47
 
33
- def strip_doublequotes(str)
34
- (str[0] == ?" && str[-1] == ?") ? str[1..-2] : str
35
- end
48
+ def strip_doublequotes(str)
49
+ (str && str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str || ''
50
+ end
36
51
  end
37
52
  end
38
53
  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)
@@ -26,7 +28,11 @@ module Rack
26
28
  req = Request.new(env)
27
29
  method = method_override_param(req) ||
28
30
  env[HTTP_METHOD_OVERRIDE_HEADER]
29
- method.to_s.upcase
31
+ begin
32
+ method.to_s.upcase
33
+ rescue ArgumentError
34
+ env[RACK_ERRORS].puts "Invalid string for method"
35
+ end
30
36
  end
31
37
 
32
38
  private
@@ -37,7 +43,10 @@ module Rack
37
43
 
38
44
  def method_override_param(req)
39
45
  req.POST[METHOD_OVERRIDE_PARAM_KEY]
40
- rescue Utils::InvalidParameterError, Utils::ParameterTypeError
46
+ rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError
47
+ req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
48
+ rescue EOFError
49
+ req.get_header(RACK_ERRORS).puts "Bad request content body"
41
50
  end
42
51
  end
43
52
  end
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,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'stringio'
3
- require 'rack'
4
- require 'rack/lint'
5
- require 'rack/utils'
6
- require 'rack/response'
5
+ require_relative '../rack'
7
6
 
8
7
  module Rack
9
8
  # Rack::MockRequest helps testing your Rack application without
@@ -53,16 +52,26 @@ module Rack
53
52
  @app = app
54
53
  end
55
54
 
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
63
-
64
- def request(method=GET, uri="", opts={})
65
- env = self.class.env_for(uri, opts.merge(:method => method))
55
+ # Make a GET request and return a MockResponse. See #request.
56
+ def get(uri, opts = {}) request(GET, uri, opts) end
57
+ # Make a POST request and return a MockResponse. See #request.
58
+ def post(uri, opts = {}) request(POST, uri, opts) end
59
+ # Make a PUT request and return a MockResponse. See #request.
60
+ def put(uri, opts = {}) request(PUT, uri, opts) end
61
+ # Make a PATCH request and return a MockResponse. See #request.
62
+ def patch(uri, opts = {}) request(PATCH, uri, opts) end
63
+ # Make a DELETE request and return a MockResponse. See #request.
64
+ def delete(uri, opts = {}) request(DELETE, uri, opts) end
65
+ # Make a HEAD request and return a MockResponse. See #request.
66
+ def head(uri, opts = {}) request(HEAD, uri, opts) end
67
+ # Make an OPTIONS request and return a MockResponse. See #request.
68
+ def options(uri, opts = {}) request(OPTIONS, uri, opts) end
69
+
70
+ # Make a request using the given request method for the given
71
+ # uri to the rack application and return a MockResponse.
72
+ # Options given are passed to MockRequest.env_for.
73
+ def request(method = GET, uri = "", opts = {})
74
+ env = self.class.env_for(uri, opts.merge(method: method))
66
75
 
67
76
  if opts[:lint]
68
77
  app = Rack::Lint.new(@app)
@@ -71,7 +80,7 @@ module Rack
71
80
  end
72
81
 
73
82
  errors = env[RACK_ERRORS]
74
- status, headers, body = app.call(env)
83
+ status, headers, body = app.call(env)
75
84
  MockResponse.new(status, headers, body, errors)
76
85
  ensure
77
86
  body.close if body.respond_to?(:close)
@@ -85,19 +94,26 @@ module Rack
85
94
  end
86
95
 
87
96
  # Return the Rack environment used for a request to +uri+.
88
- def self.env_for(uri="", opts={})
97
+ # All options that are strings are added to the returned environment.
98
+ # Options:
99
+ # :fatal :: Whether to raise an exception if request outputs to rack.errors
100
+ # :input :: The rack.input to set
101
+ # :method :: The HTTP request method to use
102
+ # :params :: The params to use
103
+ # :script_name :: The SCRIPT_NAME to set
104
+ def self.env_for(uri = "", opts = {})
89
105
  uri = parse_uri_rfc2396(uri)
90
106
  uri.path = "/#{uri.path}" unless uri.path[0] == ?/
91
107
 
92
108
  env = DEFAULT_ENV.dup
93
109
 
94
- env[REQUEST_METHOD] = opts[:method] ? opts[:method].to_s.upcase : GET
95
- env[SERVER_NAME] = uri.host || "example.org"
96
- env[SERVER_PORT] = uri.port ? uri.port.to_s : "80"
97
- env[QUERY_STRING] = uri.query.to_s
98
- env[PATH_INFO] = (!uri.path || uri.path.empty?) ? "/" : uri.path
99
- env[RACK_URL_SCHEME] = uri.scheme || "http"
100
- env[HTTPS] = env[RACK_URL_SCHEME] == "https" ? "on" : "off"
110
+ env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
111
+ env[SERVER_NAME] = (uri.host || "example.org").b
112
+ env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
113
+ env[QUERY_STRING] = (uri.query.to_s).b
114
+ env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
115
+ env[RACK_URL_SCHEME] = (uri.scheme || "http").b
116
+ env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
101
117
 
102
118
  env[SCRIPT_NAME] = opts[:script_name] || ""
103
119
 
@@ -128,7 +144,7 @@ module Rack
128
144
  end
129
145
  end
130
146
 
131
- empty_str = String.new.force_encoding(Encoding::ASCII_8BIT)
147
+ empty_str = String.new
132
148
  opts[:input] ||= empty_str
133
149
  if String === opts[:input]
134
150
  rack_input = StringIO.new(opts[:input])
@@ -139,7 +155,7 @@ module Rack
139
155
  rack_input.set_encoding(Encoding::BINARY)
140
156
  env[RACK_INPUT] = rack_input
141
157
 
142
- env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s
158
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
143
159
 
144
160
  opts.each { |field, value|
145
161
  env[field] = value if String === field
@@ -154,17 +170,54 @@ module Rack
154
170
  # MockRequest.
155
171
 
156
172
  class MockResponse < Rack::Response
173
+ begin
174
+ # Recent versions of the CGI gem may not provide `CGI::Cookie`.
175
+ require 'cgi/cookie'
176
+ Cookie = CGI::Cookie
177
+ rescue LoadError
178
+ class Cookie
179
+ attr_reader :name, :value, :path, :domain, :expires, :secure
180
+
181
+ def initialize(args)
182
+ @name = args["name"]
183
+ @value = args["value"]
184
+ @path = args["path"]
185
+ @domain = args["domain"]
186
+ @expires = args["expires"]
187
+ @secure = args["secure"]
188
+ end
189
+
190
+ def method_missing(method_name, *args, &block)
191
+ @value.send(method_name, *args, &block)
192
+ end
193
+ # :nocov:
194
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
195
+ # :nocov:
196
+
197
+ def respond_to_missing?(method_name, include_all = false)
198
+ @value.respond_to?(method_name, include_all) || super
199
+ end
200
+ end
201
+ end
202
+
203
+ class << self
204
+ alias [] new
205
+ end
206
+
157
207
  # Headers
158
- attr_reader :original_headers
208
+ attr_reader :original_headers, :cookies
159
209
 
160
210
  # Errors
161
211
  attr_accessor :errors
162
212
 
163
- def initialize(status, headers, body, errors=StringIO.new(""))
213
+ def initialize(status, headers, body, errors = StringIO.new(""))
164
214
  @original_headers = headers
165
215
  @errors = errors.string if errors.respond_to?(:string)
216
+ @cookies = parse_cookies_from_header
166
217
 
167
218
  super(body, status, headers)
219
+
220
+ buffered_body!
168
221
  end
169
222
 
170
223
  def =~(other)
@@ -186,11 +239,64 @@ module Rack
186
239
  # ...
187
240
  # res.body.should == "foo!"
188
241
  # end
189
- super.join
242
+ buffer = String.new
243
+
244
+ super.each do |chunk|
245
+ buffer << chunk
246
+ end
247
+
248
+ return buffer
190
249
  end
191
250
 
192
251
  def empty?
193
- [201, 204, 205, 304].include? status
252
+ [201, 204, 304].include? status
253
+ end
254
+
255
+ def cookie(name)
256
+ cookies.fetch(name, nil)
257
+ end
258
+
259
+ private
260
+
261
+ def parse_cookies_from_header
262
+ cookies = Hash.new
263
+ if original_headers.has_key? 'Set-Cookie'
264
+ set_cookie_header = original_headers.fetch('Set-Cookie')
265
+ set_cookie_header.split("\n").each do |cookie|
266
+ cookie_name, cookie_filling = cookie.split('=', 2)
267
+ cookie_attributes = identify_cookie_attributes cookie_filling
268
+ parsed_cookie = Cookie.new(
269
+ 'name' => cookie_name.strip,
270
+ 'value' => cookie_attributes.fetch('value'),
271
+ 'path' => cookie_attributes.fetch('path', nil),
272
+ 'domain' => cookie_attributes.fetch('domain', nil),
273
+ 'expires' => cookie_attributes.fetch('expires', nil),
274
+ 'secure' => cookie_attributes.fetch('secure', false)
275
+ )
276
+ cookies.store(cookie_name, parsed_cookie)
277
+ end
278
+ end
279
+ cookies
194
280
  end
281
+
282
+ def identify_cookie_attributes(cookie_filling)
283
+ cookie_bits = cookie_filling.split(';')
284
+ cookie_attributes = Hash.new
285
+ cookie_attributes.store('value', Array(cookie_bits[0].strip))
286
+ cookie_bits.each do |bit|
287
+ if bit.include? '='
288
+ cookie_attribute, attribute_value = bit.split('=')
289
+ cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
290
+ if cookie_attribute.include? 'max-age'
291
+ cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
292
+ end
293
+ end
294
+ if bit.include? 'secure'
295
+ cookie_attributes.store('secure', true)
296
+ end
297
+ end
298
+ cookie_attributes
299
+ end
300
+
195
301
  end
196
302
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class Generator
@@ -15,9 +17,13 @@ module Rack
15
17
 
16
18
  flattened_params.map do |name, file|
17
19
  if file.respond_to?(:original_filename)
18
- ::File.open(file.path, 'rb') do |f|
19
- f.set_encoding(Encoding::BINARY)
20
- content_for_tempfile(f, file, name)
20
+ if file.path
21
+ ::File.open(file.path, 'rb') do |f|
22
+ f.set_encoding(Encoding::BINARY)
23
+ content_for_tempfile(f, file, name)
24
+ end
25
+ else
26
+ content_for_tempfile(file, file, name)
21
27
  end
22
28
  else
23
29
  content_for_other(file, name)
@@ -27,21 +33,18 @@ module Rack
27
33
 
28
34
  private
29
35
  def multipart?
30
- multipart = false
31
-
32
36
  query = lambda { |value|
33
37
  case value
34
38
  when Array
35
- value.each(&query)
39
+ value.any?(&query)
36
40
  when Hash
37
- value.values.each(&query)
41
+ value.values.any?(&query)
38
42
  when Rack::Multipart::UploadedFile
39
- multipart = true
43
+ true
40
44
  end
41
45
  }
42
- @params.values.each(&query)
43
46
 
44
- multipart
47
+ @params.values.any?(&query)
45
48
  end
46
49
 
47
50
  def flattened_params
@@ -70,12 +73,13 @@ module Rack
70
73
  end
71
74
 
72
75
  def content_for_tempfile(io, file, name)
76
+ length = ::File.stat(file.path).size if file.path
77
+ filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
73
78
  <<-EOF
74
79
  --#{MULTIPART_BOUNDARY}\r
75
- Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
80
+ Content-Disposition: form-data; name="#{name}"#{filename}\r
76
81
  Content-Type: #{file.content_type}\r
77
- Content-Length: #{::File.stat(file.path).size}\r
78
- \r
82
+ #{"Content-Length: #{length}\r\n" if length}\r
79
83
  #{io.read}\r
80
84
  EOF
81
85
  end