rack 2.2.10 → 3.0.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +152 -100
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +293 -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 -1
  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 +3 -1
  16. data/lib/rack/builder.rb +60 -42
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +23 -18
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +63 -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 +14 -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 +754 -648
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +4 -9
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -271
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +126 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +123 -85
  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 +76 -44
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +189 -91
  50. data/lib/rack/response.rb +131 -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 +4 -2
  59. data/lib/rack/utils.rb +212 -202
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +5 -76
  62. metadata +15 -35
  63. data/README.rdoc +0 -320
  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 -85
  87. data/rack.gemspec +0 -46
@@ -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,35 +40,35 @@ 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 ]
62
-
63
- msg.gsub!(/[^[:print:]\n]/) { |c| "\\x#{c.ord}" }
67
+ Utils.clock_time - began_at)
64
68
 
65
- logger = @logger || env[RACK_ERRORS]
69
+ msg.gsub!(/[^[:print:]\n]/) { |c| sprintf("\\x%x", c.ord) }
66
70
 
71
+ logger = @logger || request.get_header(RACK_ERRORS)
67
72
  # Standard library logger doesn't support write but it supports << which actually
68
73
  # calls to write on the log device without formatting
69
74
  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,63 @@
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_COOKIE_HASH = 'rack.request.cookie_hash'
59
+ RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string'
60
+ RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
61
+ RACK_REQUEST_QUERY_STRING = 'rack.request.query_string'
62
+ RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
63
+ 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,16 +26,11 @@ 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
38
35
  end
39
36
 
@@ -45,7 +42,7 @@ module Rack
45
42
  end
46
43
  end
47
44
 
48
- [status, headers, body]
45
+ response
49
46
  end
50
47
 
51
48
  private
@@ -54,24 +51,18 @@ module Rack
54
51
  status == 200 || status == 201
55
52
  end
56
53
 
57
- def etag_body?(body)
58
- !body.respond_to?(:to_path)
59
- end
60
-
61
54
  def skip_caching?(headers)
62
- headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
55
+ headers.key?(ETAG_STRING) || headers.key?('last-modified')
63
56
  end
64
57
 
65
58
  def digest_body(body)
66
- parts = []
67
59
  digest = nil
68
60
 
69
61
  body.each do |part|
70
- parts << part
71
62
  (digest ||= Digest::SHA256.new) << part unless part.empty?
72
63
  end
73
64
 
74
- [digest && digest.hexdigest.byteslice(0, 32), parts]
65
+ digest && digest.hexdigest.byteslice(0,32)
75
66
  end
76
67
  end
77
68
  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
data/lib/rack/files.rb CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  require 'time'
4
4
 
5
+ require_relative 'constants'
6
+ require_relative 'head'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'mime'
10
+
5
11
  module Rack
6
12
  # Rack::Files serves files below the +root+ directory given, according to the
7
13
  # path info of the Rack request.
@@ -16,14 +22,6 @@ module Rack
16
22
  ALLOW_HEADER = ALLOWED_VERBS.join(', ')
17
23
  MULTIPART_BOUNDARY = 'AaB03x'
18
24
 
19
- # @todo remove in 3.0
20
- def self.method_added(name)
21
- if name == :response_body
22
- raise "#{self.class}\#response_body is no longer supported."
23
- end
24
- super
25
- end
26
-
27
25
  attr_reader :root
28
26
 
29
27
  def initialize(root, headers = {}, default_mime = 'text/plain')
@@ -41,7 +39,7 @@ module Rack
41
39
  def get(env)
42
40
  request = Rack::Request.new env
43
41
  unless ALLOWED_VERBS.include? request.request_method
44
- return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
42
+ return fail(405, "Method Not Allowed", { 'allow' => ALLOW_HEADER })
45
43
  end
46
44
 
47
45
  path_info = Utils.unescape_path request.path_info
@@ -69,12 +67,12 @@ module Rack
69
67
 
70
68
  def serving(request, path)
71
69
  if request.options?
72
- return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
70
+ return [200, { 'allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
73
71
  end
74
72
  last_modified = ::File.mtime(path).httpdate
75
73
  return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
76
74
 
77
- headers = { "Last-Modified" => last_modified }
75
+ headers = { "last-modified" => last_modified }
78
76
  mime_type = mime_type path, @default_mime
79
77
  headers[CONTENT_TYPE] = mime_type if mime_type
80
78
 
@@ -91,15 +89,15 @@ module Rack
91
89
  elsif ranges.empty?
92
90
  # Unsatisfiable. Return error, and file size:
93
91
  response = fail(416, "Byte range unsatisfiable")
94
- response[1]["Content-Range"] = "bytes */#{size}"
92
+ response[1]["content-range"] = "bytes */#{size}"
95
93
  return response
96
- elsif ranges.size >= 1
94
+ else
97
95
  # Partial content
98
96
  partial_content = true
99
97
 
100
98
  if ranges.size == 1
101
99
  range = ranges[0]
102
- headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
100
+ headers["content-range"] = "bytes #{range.begin}-#{range.end}/#{size}"
103
101
  else
104
102
  headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
105
103
  end
@@ -164,8 +162,8 @@ module Rack
164
162
  <<-EOF
165
163
  \r
166
164
  --#{MULTIPART_BOUNDARY}\r
167
- Content-Type: #{options[:mime_type]}\r
168
- Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
165
+ content-type: #{options[:mime_type]}\r
166
+ content-range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
169
167
  \r
170
168
  EOF
171
169
  end
@@ -197,7 +195,7 @@ EOF
197
195
  {
198
196
  CONTENT_TYPE => "text/plain",
199
197
  CONTENT_LENGTH => body.size.to_s,
200
- "X-Cascade" => "pass"
198
+ "x-cascade" => "pass"
201
199
  }.merge!(headers),
202
200
  [body]
203
201
  ]