rack 2.2.8 → 3.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -83
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +309 -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 +0 -2
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -54
  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 +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 +23 -18
  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 +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 +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 +9 -4
  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 +120 -64
  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 +208 -178
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +6 -76
  62. metadata +14 -34
  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 -204
  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,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,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
  ]