rack 2.2.10 → 3.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -105
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +287 -0
- data/Rakefile +40 -7
- data/SPEC.rdoc +166 -125
- data/contrib/LICENSE.md +7 -0
- data/contrib/logo.webp +0 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +2 -1
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -53
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +3 -1
- data/lib/rack/builder.rb +60 -42
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +62 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +14 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +740 -649
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +4 -9
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +123 -85
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +76 -44
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +189 -91
- data/lib/rack/response.rb +131 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +4 -2
- data/lib/rack/utils.rb +212 -202
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- data/rack.gemspec +6 -6
- metadata +19 -31
- data/README.rdoc +0 -320
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
data/lib/rack/common_logger.rb
CHANGED
@@ -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
|
-
|
40
|
-
|
41
|
-
|
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,
|
48
|
-
|
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
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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)
|
data/lib/rack/conditional_get.rb
CHANGED
@@ -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
|
6
|
-
#
|
7
|
-
#
|
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
|
-
|
31
|
+
status, headers, body = response = @app.call(env)
|
32
|
+
|
29
33
|
if status == 200 && fresh?(env, headers)
|
30
|
-
|
34
|
+
response[0] = 304
|
31
35
|
headers.delete(CONTENT_TYPE)
|
32
36
|
headers.delete(CONTENT_LENGTH)
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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[
|
63
|
+
headers[ETAG] == none_match
|
61
64
|
end
|
62
65
|
|
63
|
-
# Whether the
|
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['
|
69
|
+
last_modified = to_rfc2822(headers['last-modified']) and
|
67
70
|
modified_since >= last_modified
|
68
71
|
end
|
69
72
|
|
@@ -0,0 +1,62 @@
|
|
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_REQUEST_FORM_INPUT = 'rack.request.form_input'
|
55
|
+
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
|
56
|
+
RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
|
57
|
+
RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
|
58
|
+
RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string'
|
59
|
+
RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
|
60
|
+
RACK_REQUEST_QUERY_STRING = 'rack.request.query_string'
|
61
|
+
RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
|
62
|
+
end
|
data/lib/rack/content_length.rb
CHANGED
@@ -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
|
6
|
-
# a
|
7
|
-
# does not fix responses that have an invalid
|
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
|
-
|
27
|
+
response[2] = body = body.to_ary
|
28
|
+
headers[CONTENT_LENGTH] = body.sum(&:bytesize).to_s
|
33
29
|
end
|
34
30
|
|
35
|
-
|
31
|
+
response
|
36
32
|
end
|
37
33
|
end
|
38
34
|
end
|
data/lib/rack/content_type.rb
CHANGED
@@ -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
|
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
|
19
|
+
@app = app
|
20
|
+
@content_type = content_type
|
17
21
|
end
|
18
22
|
|
19
23
|
def call(env)
|
20
|
-
status, headers,
|
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
|
-
|
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
|
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["
|
58
|
-
unless vary.include?("*") || vary.
|
59
|
-
headers["
|
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['
|
66
|
+
headers['content-encoding'] = "gzip"
|
65
67
|
headers.delete(CONTENT_LENGTH)
|
66
|
-
mtime = headers["
|
68
|
+
mtime = headers["last-modified"]
|
67
69
|
mtime = Time.httpdate(mtime).to_i if mtime
|
68
|
-
[
|
70
|
+
response[2] = GzipStream.new(body, mtime, @sync)
|
71
|
+
response
|
69
72
|
when "identity"
|
70
|
-
|
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
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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.
|
119
|
+
gzip.finish
|
106
120
|
end
|
107
121
|
|
108
|
-
# Call the block passed to #each with the
|
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[
|
127
|
-
headers['
|
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?(
|
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)
|
data/lib/rack/directory.rb
CHANGED
@@ -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
|
-
"
|
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
|
-
"
|
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
|
-
"
|
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
|
9
|
+
# Automatically sets the etag header on all String bodies.
|
8
10
|
#
|
9
|
-
# The
|
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
|
14
|
-
# used when
|
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) &&
|
32
|
-
|
33
|
-
digest
|
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
|
-
|
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?('
|
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
|
-
|
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
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", { '
|
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, { '
|
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 = { "
|
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]["
|
92
|
+
response[1]["content-range"] = "bytes */#{size}"
|
95
93
|
return response
|
96
|
-
|
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["
|
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
|
-
|
168
|
-
|
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
|
-
"
|
198
|
+
"x-cascade" => "pass"
|
201
199
|
}.merge!(headers),
|
202
200
|
[body]
|
203
201
|
]
|