rack 2.2.17 → 3.0.18

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.

Potentially problematic release.


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

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +292 -74
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +336 -0
  6. data/SPEC.rdoc +174 -126
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +2 -3
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -53
  12. data/lib/rack/auth/digest/params.rb +1 -54
  13. data/lib/rack/auth/digest/request.rb +1 -43
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +21 -3
  16. data/lib/rack/builder.rb +83 -63
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +22 -17
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +64 -0
  22. data/lib/rack/content_length.rb +12 -16
  23. data/lib/rack/content_type.rb +8 -5
  24. data/lib/rack/deflater.rb +40 -26
  25. data/lib/rack/directory.rb +9 -3
  26. data/lib/rack/etag.rb +17 -23
  27. data/lib/rack/events.rb +4 -0
  28. data/lib/rack/file.rb +2 -0
  29. data/lib/rack/files.rb +15 -17
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +758 -646
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +3 -8
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -300
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +155 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +127 -69
  43. data/lib/rack/multipart/uploaded_file.rb +4 -0
  44. data/lib/rack/multipart.rb +20 -40
  45. data/lib/rack/null_logger.rb +9 -0
  46. data/lib/rack/query_parser.rb +78 -46
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +224 -106
  50. data/lib/rack/response.rb +138 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +30 -25
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +8 -8
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +3 -1
  59. data/lib/rack/utils.rb +206 -180
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +13 -87
  62. metadata +15 -35
  63. data/README.rdoc +0 -347
  64. data/Rakefile +0 -130
  65. data/bin/rackup +0 -5
  66. data/contrib/rack.png +0 -0
  67. data/contrib/rack.svg +0 -150
  68. data/contrib/rack_logo.svg +0 -164
  69. data/contrib/rdoc.css +0 -412
  70. data/example/lobster.ru +0 -6
  71. data/example/protectedlobster.rb +0 -16
  72. data/example/protectedlobster.ru +0 -10
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
data/lib/rack/chunked.rb CHANGED
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
7
+ warn "Rack::Chunked is deprecated and will be removed in Rack 3.1", uplevel: 1
4
8
 
5
9
  # Middleware that applies chunked transfer encoding to response bodies
6
- # when the response does not include a Content-Length header.
10
+ # when the response does not include a content-length header.
7
11
  #
8
- # This supports the Trailer response header to allow the use of trailing
12
+ # This supports the trailer response header to allow the use of trailing
9
13
  # headers in the chunked encoding. However, using this requires you manually
10
14
  # specify a response body that supports a +trailers+ method. Example:
11
15
  #
12
- # [200, { 'Trailer' => 'Expires'}, ["Hello", "World"]]
16
+ # [200, { 'trailer' => 'expires'}, ["Hello", "World"]]
13
17
  # # error raised
14
18
  #
15
19
  # body = ["Hello", "World"]
16
20
  # def body.trailers
17
- # { 'Expires' => Time.now.to_s }
21
+ # { 'expires' => Time.now.to_s }
18
22
  # end
19
- # [200, { 'Trailer' => 'Expires'}, body]
23
+ # [200, { 'trailer' => 'expires'}, body]
20
24
  # # No exception raised
21
25
  class Chunked
22
26
  include Rack::Utils
@@ -92,11 +96,10 @@ module Rack
92
96
  end
93
97
 
94
98
  # If the rack app returns a response that should have a body,
95
- # but does not have Content-Length or Transfer-Encoding headers,
96
- # modify the response to use chunked Transfer-Encoding.
99
+ # but does not have content-length or transfer-encoding headers,
100
+ # modify the response to use chunked transfer-encoding.
97
101
  def call(env)
98
- status, headers, body = @app.call(env)
99
- headers = HeaderHash[headers]
102
+ status, headers, body = response = @app.call(env)
100
103
 
101
104
  if chunkable_version?(env[SERVER_PROTOCOL]) &&
102
105
  !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
@@ -104,14 +107,14 @@ module Rack
104
107
  !headers[TRANSFER_ENCODING]
105
108
 
106
109
  headers[TRANSFER_ENCODING] = 'chunked'
107
- if headers['Trailer']
108
- body = TrailerBody.new(body)
110
+ if headers['trailer']
111
+ response[2] = TrailerBody.new(body)
109
112
  else
110
- body = Body.new(body)
113
+ response[2] = Body.new(body)
111
114
  end
112
115
  end
113
116
 
114
- [status, headers, body]
117
+ response
115
118
  end
116
119
  end
117
120
  end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
6
+ require_relative 'request'
7
+
3
8
  module Rack
4
9
  # Rack::CommonLogger forwards every request to the given +app+, and
5
10
  # logs a line in the
@@ -35,36 +40,36 @@ module Rack
35
40
  # cause the request not to be logged.
36
41
  def call(env)
37
42
  began_at = Utils.clock_time
38
- status, headers, body = @app.call(env)
39
- headers = Utils::HeaderHash[headers]
40
- body = BodyProxy.new(body) { log(env, status, headers, began_at) }
41
- [status, headers, body]
43
+ status, headers, body = response = @app.call(env)
44
+
45
+ response[2] = BodyProxy.new(body) { log(env, status, headers, began_at) }
46
+ response
42
47
  end
43
48
 
44
49
  private
45
50
 
46
51
  # Log the request to the configured logger.
47
- def log(env, status, header, began_at)
48
- length = extract_content_length(header)
52
+ def log(env, status, response_headers, began_at)
53
+ request = Rack::Request.new(env)
54
+ length = extract_content_length(response_headers)
49
55
 
50
- msg = FORMAT % [
51
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
52
- env["REMOTE_USER"] || "-",
56
+ msg = sprintf(FORMAT,
57
+ request.ip || "-",
58
+ request.get_header("REMOTE_USER") || "-",
53
59
  Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
54
- env[REQUEST_METHOD],
55
- env[SCRIPT_NAME],
56
- env[PATH_INFO],
57
- env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
58
- env[SERVER_PROTOCOL],
60
+ request.request_method,
61
+ request.script_name,
62
+ request.path_info,
63
+ request.query_string.empty? ? "" : "?#{request.query_string}",
64
+ request.get_header(SERVER_PROTOCOL),
59
65
  status.to_s[0..3],
60
66
  length,
61
- Utils.clock_time - began_at ]
67
+ Utils.clock_time - began_at)
62
68
 
63
69
  msg.gsub!(/[^[:print:]]/) { |c| sprintf("\\x%x", c.ord) }
64
70
  msg[-1] = "\n"
65
71
 
66
- logger = @logger || env[RACK_ERRORS]
67
-
72
+ logger = @logger || request.get_header(RACK_ERRORS)
68
73
  # Standard library logger doesn't support write but it supports << which actually
69
74
  # calls to write on the log device without formatting
70
75
  if logger.respond_to?(:write)
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
6
+
3
7
  module Rack
4
8
 
5
- # Middleware that enables conditional GET using If-None-Match and
6
- # If-Modified-Since. The application should set either or both of the
7
- # Last-Modified or Etag response headers according to RFC 2616. When
9
+ # Middleware that enables conditional GET using if-none-match and
10
+ # if-modified-since. The application should set either or both of the
11
+ # last-modified or etag response headers according to RFC 2616. When
8
12
  # either of the conditions is met, the response body is set to be zero
9
13
  # length and the response status is set to 304 Not Modified.
10
14
  #
@@ -24,18 +28,17 @@ module Rack
24
28
  def call(env)
25
29
  case env[REQUEST_METHOD]
26
30
  when "GET", "HEAD"
27
- status, headers, body = @app.call(env)
28
- headers = Utils::HeaderHash[headers]
31
+ status, headers, body = response = @app.call(env)
32
+
29
33
  if status == 200 && fresh?(env, headers)
30
- status = 304
34
+ response[0] = 304
31
35
  headers.delete(CONTENT_TYPE)
32
36
  headers.delete(CONTENT_LENGTH)
33
- original_body = body
34
- body = Rack::BodyProxy.new([]) do
35
- original_body.close if original_body.respond_to?(:close)
37
+ response[2] = Rack::BodyProxy.new([]) do
38
+ body.close if body.respond_to?(:close)
36
39
  end
37
40
  end
38
- [status, headers, body]
41
+ response
39
42
  else
40
43
  @app.call(env)
41
44
  end
@@ -46,7 +49,7 @@ module Rack
46
49
  # Return whether the response has not been modified since the
47
50
  # last request.
48
51
  def fresh?(env, headers)
49
- # If-None-Match has priority over If-Modified-Since per RFC 7232
52
+ # if-none-match has priority over if-modified-since per RFC 7232
50
53
  if none_match = env['HTTP_IF_NONE_MATCH']
51
54
  etag_matches?(none_match, headers)
52
55
  elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since))
@@ -54,16 +57,16 @@ module Rack
54
57
  end
55
58
  end
56
59
 
57
- # Whether the ETag response header matches the If-None-Match request header.
60
+ # Whether the etag response header matches the if-none-match request header.
58
61
  # If so, the request has not been modified.
59
62
  def etag_matches?(none_match, headers)
60
- headers['ETag'] == none_match
63
+ headers[ETAG] == none_match
61
64
  end
62
65
 
63
- # Whether the Last-Modified response header matches the If-Modified-Since
66
+ # Whether the last-modified response header matches the if-modified-since
64
67
  # request header. If so, the request has not been modified.
65
68
  def modified_since?(modified_since, headers)
66
- last_modified = to_rfc2822(headers['Last-Modified']) and
69
+ last_modified = to_rfc2822(headers['last-modified']) and
67
70
  modified_since >= last_modified
68
71
  end
69
72
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Request env keys
5
+ HTTP_HOST = 'HTTP_HOST'
6
+ HTTP_PORT = 'HTTP_PORT'
7
+ HTTPS = 'HTTPS'
8
+ PATH_INFO = 'PATH_INFO'
9
+ REQUEST_METHOD = 'REQUEST_METHOD'
10
+ REQUEST_PATH = 'REQUEST_PATH'
11
+ SCRIPT_NAME = 'SCRIPT_NAME'
12
+ QUERY_STRING = 'QUERY_STRING'
13
+ SERVER_PROTOCOL = 'SERVER_PROTOCOL'
14
+ SERVER_NAME = 'SERVER_NAME'
15
+ SERVER_PORT = 'SERVER_PORT'
16
+ HTTP_COOKIE = 'HTTP_COOKIE'
17
+
18
+ # Response Header Keys
19
+ CACHE_CONTROL = 'cache-control'
20
+ CONTENT_LENGTH = 'content-length'
21
+ CONTENT_TYPE = 'content-type'
22
+ ETAG = 'etag'
23
+ EXPIRES = 'expires'
24
+ SET_COOKIE = 'set-cookie'
25
+ TRANSFER_ENCODING = 'transfer-encoding'
26
+
27
+ # HTTP method verbs
28
+ GET = 'GET'
29
+ POST = 'POST'
30
+ PUT = 'PUT'
31
+ PATCH = 'PATCH'
32
+ DELETE = 'DELETE'
33
+ HEAD = 'HEAD'
34
+ OPTIONS = 'OPTIONS'
35
+ LINK = 'LINK'
36
+ UNLINK = 'UNLINK'
37
+ TRACE = 'TRACE'
38
+
39
+ # Rack environment variables
40
+ RACK_VERSION = 'rack.version'
41
+ RACK_TEMPFILES = 'rack.tempfiles'
42
+ RACK_ERRORS = 'rack.errors'
43
+ RACK_LOGGER = 'rack.logger'
44
+ RACK_INPUT = 'rack.input'
45
+ RACK_SESSION = 'rack.session'
46
+ RACK_SESSION_OPTIONS = 'rack.session.options'
47
+ RACK_SHOWSTATUS_DETAIL = 'rack.showstatus.detail'
48
+ RACK_URL_SCHEME = 'rack.url_scheme'
49
+ RACK_HIJACK = 'rack.hijack'
50
+ RACK_IS_HIJACK = 'rack.hijack?'
51
+ RACK_RECURSIVE_INCLUDE = 'rack.recursive.include'
52
+ RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size'
53
+ RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory'
54
+ RACK_RESPONSE_FINISHED = 'rack.response_finished'
55
+ RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
56
+ RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
57
+ RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
58
+ RACK_REQUEST_FORM_ERROR = 'rack.request.form_error'
59
+ RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
60
+ RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string'
61
+ RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
62
+ RACK_REQUEST_QUERY_STRING = 'rack.request.query_string'
63
+ RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
64
+ end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
4
7
 
5
- # Sets the Content-Length header on responses that do not specify
6
- # a Content-Length or Transfer-Encoding header. Note that this
7
- # does not fix responses that have an invalid Content-Length
8
+ # Sets the content-length header on responses that do not specify
9
+ # a content-length or transfer-encoding header. Note that this
10
+ # does not fix responses that have an invalid content-length
8
11
  # header specified.
9
12
  class ContentLength
10
13
  include Rack::Utils
@@ -14,25 +17,18 @@ module Rack
14
17
  end
15
18
 
16
19
  def call(env)
17
- status, headers, body = @app.call(env)
18
- headers = HeaderHash[headers]
20
+ status, headers, body = response = @app.call(env)
19
21
 
20
22
  if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
21
23
  !headers[CONTENT_LENGTH] &&
22
- !headers[TRANSFER_ENCODING]
23
-
24
- obody = body
25
- body, length = [], 0
26
- obody.each { |part| body << part; length += part.bytesize }
27
-
28
- body = BodyProxy.new(body) do
29
- obody.close if obody.respond_to?(:close)
30
- end
24
+ !headers[TRANSFER_ENCODING] &&
25
+ body.respond_to?(:to_ary)
31
26
 
32
- headers[CONTENT_LENGTH] = length.to_s
27
+ response[2] = body = body.to_ary
28
+ headers[CONTENT_LENGTH] = body.sum(&:bytesize).to_s
33
29
  end
34
30
 
35
- [status, headers, body]
31
+ response
36
32
  end
37
33
  end
38
34
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
4
7
 
5
- # Sets the Content-Type header on responses which don't have one.
8
+ # Sets the content-type header on responses which don't have one.
6
9
  #
7
10
  # Builder Usage:
8
11
  # use Rack::ContentType, "text/plain"
@@ -13,18 +16,18 @@ module Rack
13
16
  include Rack::Utils
14
17
 
15
18
  def initialize(app, content_type = "text/html")
16
- @app, @content_type = app, content_type
19
+ @app = app
20
+ @content_type = content_type
17
21
  end
18
22
 
19
23
  def call(env)
20
- status, headers, body = @app.call(env)
21
- headers = Utils::HeaderHash[headers]
24
+ status, headers, _ = response = @app.call(env)
22
25
 
23
26
  unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
24
27
  headers[CONTENT_TYPE] ||= @content_type
25
28
  end
26
29
 
27
- [status, headers, body]
30
+ response
28
31
  end
29
32
  end
30
33
  end
data/lib/rack/deflater.rb CHANGED
@@ -3,6 +3,11 @@
3
3
  require "zlib"
4
4
  require "time" # for Time.httpdate
5
5
 
6
+ require_relative 'constants'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'body_proxy'
10
+
6
11
  module Rack
7
12
  # This middleware enables content encoding of http responses,
8
13
  # usually for purposes of compression.
@@ -21,8 +26,6 @@ module Rack
21
26
  # Note that despite the name, Deflater does not support the +deflate+
22
27
  # encoding.
23
28
  class Deflater
24
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
25
-
26
29
  # Creates Rack::Deflater middleware. Options:
27
30
  #
28
31
  # :if :: a lambda enabling / disabling deflation based on returned boolean value
@@ -41,11 +44,10 @@ module Rack
41
44
  end
42
45
 
43
46
  def call(env)
44
- status, headers, body = @app.call(env)
45
- headers = Utils::HeaderHash[headers]
47
+ status, headers, body = response = @app.call(env)
46
48
 
47
49
  unless should_deflate?(env, status, headers, body)
48
- return [status, headers, body]
50
+ return response
49
51
  end
50
52
 
51
53
  request = Request.new(env)
@@ -54,21 +56,23 @@ module Rack
54
56
  request.accept_encoding)
55
57
 
56
58
  # Set the Vary HTTP header.
57
- vary = headers["Vary"].to_s.split(",").map(&:strip)
58
- unless vary.include?("*") || vary.include?("Accept-Encoding")
59
- headers["Vary"] = vary.push("Accept-Encoding").join(",")
59
+ vary = headers["vary"].to_s.split(",").map(&:strip)
60
+ unless vary.include?("*") || vary.any?{|v| v.downcase == 'accept-encoding'}
61
+ headers["vary"] = vary.push("Accept-Encoding").join(",")
60
62
  end
61
63
 
62
64
  case encoding
63
65
  when "gzip"
64
- headers['Content-Encoding'] = "gzip"
66
+ headers['content-encoding'] = "gzip"
65
67
  headers.delete(CONTENT_LENGTH)
66
- mtime = headers["Last-Modified"]
68
+ mtime = headers["last-modified"]
67
69
  mtime = Time.httpdate(mtime).to_i if mtime
68
- [status, headers, GzipStream.new(body, mtime, @sync)]
70
+ response[2] = GzipStream.new(body, mtime, @sync)
71
+ response
69
72
  when "identity"
70
- [status, headers, body]
71
- when nil
73
+ response
74
+ else # when nil
75
+ # Only possible encoding values here are 'gzip', 'identity', and nil
72
76
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
73
77
  bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
74
78
  [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp]
@@ -77,6 +81,9 @@ module Rack
77
81
 
78
82
  # Body class used for gzip encoded responses.
79
83
  class GzipStream
84
+
85
+ BUFFER_LENGTH = 128 * 1_024
86
+
80
87
  # Initialize the gzip stream. Arguments:
81
88
  # body :: Response body to compress with gzip
82
89
  # mtime :: The modification time of the body, used to set the
@@ -93,19 +100,26 @@ module Rack
93
100
  @writer = block
94
101
  gzip = ::Zlib::GzipWriter.new(self)
95
102
  gzip.mtime = @mtime if @mtime
96
- @body.each { |part|
97
- # Skip empty strings, as they would result in no output,
98
- # and flushing empty parts would raise Zlib::BufError.
99
- next if part.empty?
100
-
101
- gzip.write(part)
102
- gzip.flush if @sync
103
- }
103
+ # @body.each is equivalent to @body.gets (slow)
104
+ if @body.is_a? ::File # XXX: Should probably be ::IO
105
+ while part = @body.read(BUFFER_LENGTH)
106
+ gzip.write(part)
107
+ gzip.flush if @sync
108
+ end
109
+ else
110
+ @body.each { |part|
111
+ # Skip empty strings, as they would result in no output,
112
+ # and flushing empty parts would raise Zlib::BufError.
113
+ next if part.empty?
114
+ gzip.write(part)
115
+ gzip.flush if @sync
116
+ }
117
+ end
104
118
  ensure
105
- gzip.close
119
+ gzip.finish
106
120
  end
107
121
 
108
- # Call the block passed to #each with the the gzipped data.
122
+ # Call the block passed to #each with the gzipped data.
109
123
  def write(data)
110
124
  @writer.call(data)
111
125
  end
@@ -123,13 +137,13 @@ module Rack
123
137
  # Skip compressing empty entity body responses and responses with
124
138
  # no-transform set.
125
139
  if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
126
- /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
127
- headers['Content-Encoding']&.!~(/\bidentity\b/)
140
+ /\bno-transform\b/.match?(headers[CACHE_CONTROL].to_s) ||
141
+ headers['content-encoding']&.!~(/\bidentity\b/)
128
142
  return false
129
143
  end
130
144
 
131
145
  # Skip if @compressible_types are given and does not include request's content type
132
- return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
146
+ return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/]))
133
147
 
134
148
  # Skip if @condition lambda is given and evaluates to false
135
149
  return false if @condition && !@condition.call(env, status, headers, body)
@@ -2,6 +2,12 @@
2
2
 
3
3
  require 'time'
4
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+ require_relative 'head'
8
+ require_relative 'mime'
9
+ require_relative 'files'
10
+
5
11
  module Rack
6
12
  # Rack::Directory serves entries below the +root+ given, according to the
7
13
  # path info of the Rack request. If a directory is found, the file's contents
@@ -106,7 +112,7 @@ table { width:100%%; }
106
112
  body = "Bad Request\n"
107
113
  [400, { CONTENT_TYPE => "text/plain",
108
114
  CONTENT_LENGTH => body.bytesize.to_s,
109
- "X-Cascade" => "pass" }, [body]]
115
+ "x-cascade" => "pass" }, [body]]
110
116
  end
111
117
 
112
118
  # Rack response to use for requests with paths outside the root, or nil if path is inside the root.
@@ -117,7 +123,7 @@ table { width:100%%; }
117
123
  body = "Forbidden\n"
118
124
  [403, { CONTENT_TYPE => "text/plain",
119
125
  CONTENT_LENGTH => body.bytesize.to_s,
120
- "X-Cascade" => "pass" }, [body]]
126
+ "x-cascade" => "pass" }, [body]]
121
127
  end
122
128
 
123
129
  # Rack response to use for directories under the root.
@@ -176,7 +182,7 @@ table { width:100%%; }
176
182
  body = "Entity not found: #{path_info}\n"
177
183
  [404, { CONTENT_TYPE => "text/plain",
178
184
  CONTENT_LENGTH => body.bytesize.to_s,
179
- "X-Cascade" => "pass" }, [body]]
185
+ "x-cascade" => "pass" }, [body]]
180
186
  end
181
187
 
182
188
  # Stolen from Ramaze
data/lib/rack/etag.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../rack'
4
3
  require 'digest/sha2'
5
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+
6
8
  module Rack
7
- # Automatically sets the ETag header on all String bodies.
9
+ # Automatically sets the etag header on all String bodies.
8
10
  #
9
- # The ETag header is skipped if ETag or Last-Modified headers are sent or if
11
+ # The etag header is skipped if etag or last-modified headers are sent or if
10
12
  # a sendfile body (body.responds_to :to_path) is given (since such cases
11
13
  # should be handled by apache/nginx).
12
14
  #
13
- # On initialization, you can pass two parameters: a Cache-Control directive
14
- # used when Etag is absent and a directive when it is present. The first
15
+ # On initialization, you can pass two parameters: a cache-control directive
16
+ # used when etag is absent and a directive when it is present. The first
15
17
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
16
18
  class ETag
17
19
  ETAG_STRING = Rack::ETAG
@@ -24,17 +26,15 @@ module Rack
24
26
  end
25
27
 
26
28
  def call(env)
27
- status, headers, body = @app.call(env)
28
-
29
- headers = Utils::HeaderHash[headers]
29
+ status, headers, body = response = @app.call(env)
30
30
 
31
- if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
32
- original_body = body
33
- digest, new_body = digest_body(body)
34
- body = Rack::BodyProxy.new(new_body) do
35
- original_body.close if original_body.respond_to?(:close)
36
- end
31
+ if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
32
+ body = body.to_ary
33
+ digest = digest_body(body)
37
34
  headers[ETAG_STRING] = %(W/"#{digest}") if digest
35
+
36
+ # Body was modified, so we need to re-assign it:
37
+ response[2] = body
38
38
  end
39
39
 
40
40
  unless headers[CACHE_CONTROL]
@@ -45,7 +45,7 @@ module Rack
45
45
  end
46
46
  end
47
47
 
48
- [status, headers, body]
48
+ response
49
49
  end
50
50
 
51
51
  private
@@ -54,24 +54,18 @@ module Rack
54
54
  status == 200 || status == 201
55
55
  end
56
56
 
57
- def etag_body?(body)
58
- !body.respond_to?(:to_path)
59
- end
60
-
61
57
  def skip_caching?(headers)
62
- headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
58
+ headers.key?(ETAG_STRING) || headers.key?('last-modified')
63
59
  end
64
60
 
65
61
  def digest_body(body)
66
- parts = []
67
62
  digest = nil
68
63
 
69
64
  body.each do |part|
70
- parts << part
71
65
  (digest ||= Digest::SHA256.new) << part unless part.empty?
72
66
  end
73
67
 
74
- [digest && digest.hexdigest.byteslice(0, 32), parts]
68
+ digest && digest.hexdigest.byteslice(0,32)
75
69
  end
76
70
  end
77
71
  end
data/lib/rack/events.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'body_proxy'
4
+ require_relative 'request'
5
+ require_relative 'response'
6
+
3
7
  module Rack
4
8
  ### This middleware provides hooks to certain places in the request /
5
9
  # response lifecycle. This is so that middleware that don't need to filter
data/lib/rack/file.rb CHANGED
@@ -3,5 +3,7 @@
3
3
  require_relative 'files'
4
4
 
5
5
  module Rack
6
+ warn "Rack::File is deprecated and will be removed in Rack 3.1", uplevel: 1
7
+
6
8
  File = Files
7
9
  end