rack 2.0.9 → 2.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +681 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +152 -148
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +29 -5
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack.rb +67 -73
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +5 -3
- data/lib/rack/body_proxy.rb +15 -14
- data/lib/rack/builder.rb +116 -23
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +68 -20
- data/lib/rack/common_logger.rb +33 -25
- data/lib/rack/conditional_get.rb +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +8 -7
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +59 -34
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +19 -20
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +2 -3
- data/lib/rack/handler/fastcgi.rb +4 -4
- data/lib/rack/handler/lsws.rb +3 -3
- data/lib/rack/handler/scgi.rb +9 -8
- data/lib/rack/handler/thin.rb +17 -11
- data/lib/rack/handler/webrick.rb +15 -6
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +71 -25
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +2 -1
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +97 -20
- data/lib/rack/multipart.rb +6 -4
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +54 -56
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +53 -28
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +220 -61
- data/lib/rack/response.rb +127 -44
- data/lib/rack/rewindable_input.rb +4 -3
- data/lib/rack/runtime.rb +6 -4
- data/lib/rack/sendfile.rb +13 -9
- data/lib/rack/server.rb +95 -24
- data/lib/rack/session/abstract/id.rb +33 -21
- data/lib/rack/session/cookie.rb +12 -12
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +5 -3
- data/lib/rack/show_exceptions.rb +17 -13
- data/lib/rack/show_status.rb +5 -5
- data/lib/rack/static.rb +23 -11
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +12 -6
- data/lib/rack/utils.rb +97 -110
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +40 -28
- metadata +36 -179
- data/HISTORY.md +0 -505
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -528
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/tempfile_reaper.rb
CHANGED
data/lib/rack/urlmap.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
1
5
|
module Rack
|
|
2
6
|
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
|
3
7
|
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
|
@@ -12,17 +16,16 @@ module Rack
|
|
|
12
16
|
# first, since they are most specific.
|
|
13
17
|
|
|
14
18
|
class URLMap
|
|
15
|
-
NEGATIVE_INFINITY = -1.0 / 0.0
|
|
16
|
-
INFINITY = 1.0 / 0.0
|
|
17
|
-
|
|
18
19
|
def initialize(map = {})
|
|
19
20
|
remap(map)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def remap(map)
|
|
24
|
+
@known_hosts = Set[]
|
|
23
25
|
@mapping = map.map { |location, app|
|
|
24
26
|
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
|
25
27
|
host, location = $1, $2
|
|
28
|
+
@known_hosts << host
|
|
26
29
|
else
|
|
27
30
|
host = nil
|
|
28
31
|
end
|
|
@@ -36,7 +39,7 @@ module Rack
|
|
|
36
39
|
|
|
37
40
|
[host, location, match, app]
|
|
38
41
|
}.sort_by do |(host, location, _, _)|
|
|
39
|
-
[host ? -host.size : INFINITY, -location.size]
|
|
42
|
+
[host ? -host.size : Float::INFINITY, -location.size]
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
|
|
@@ -50,10 +53,13 @@ module Rack
|
|
|
50
53
|
is_same_server = casecmp?(http_host, server_name) ||
|
|
51
54
|
casecmp?(http_host, "#{server_name}:#{server_port}")
|
|
52
55
|
|
|
56
|
+
is_host_known = @known_hosts.include? http_host
|
|
57
|
+
|
|
53
58
|
@mapping.each do |host, location, match, app|
|
|
54
59
|
unless casecmp?(http_host, host) \
|
|
55
60
|
|| casecmp?(server_name, host) \
|
|
56
|
-
|| (!host && is_same_server)
|
|
61
|
+
|| (!host && is_same_server) \
|
|
62
|
+
|| (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
|
|
57
63
|
next
|
|
58
64
|
end
|
|
59
65
|
|
|
@@ -68,7 +74,7 @@ module Rack
|
|
|
68
74
|
return app.call(env)
|
|
69
75
|
end
|
|
70
76
|
|
|
71
|
-
[404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
|
|
77
|
+
[404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
|
|
72
78
|
|
|
73
79
|
ensure
|
|
74
80
|
env[PATH_INFO] = path
|
data/lib/rack/utils.rb
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
2
4
|
require 'uri'
|
|
3
5
|
require 'fileutils'
|
|
4
6
|
require 'set'
|
|
5
7
|
require 'tempfile'
|
|
6
|
-
require 'rack/query_parser'
|
|
7
8
|
require 'time'
|
|
8
9
|
|
|
10
|
+
require_relative 'query_parser'
|
|
11
|
+
|
|
9
12
|
module Rack
|
|
10
13
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
|
11
14
|
# applications adopted from all kinds of Ruby libraries.
|
|
12
15
|
|
|
13
16
|
module Utils
|
|
17
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
|
18
|
+
|
|
14
19
|
ParameterTypeError = QueryParser::ParameterTypeError
|
|
15
20
|
InvalidParameterError = QueryParser::InvalidParameterError
|
|
16
21
|
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
|
@@ -24,33 +29,30 @@ module Rack
|
|
|
24
29
|
# This helps prevent a rogue client from flooding a Request.
|
|
25
30
|
self.default_query_parser = QueryParser.make_default(65536, 100)
|
|
26
31
|
|
|
32
|
+
module_function
|
|
33
|
+
|
|
27
34
|
# URI escapes. (CGI style space to +)
|
|
28
35
|
def escape(s)
|
|
29
36
|
URI.encode_www_form_component(s)
|
|
30
37
|
end
|
|
31
|
-
module_function :escape
|
|
32
38
|
|
|
33
39
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
|
34
40
|
# true URI escaping.
|
|
35
41
|
def escape_path(s)
|
|
36
42
|
::URI::DEFAULT_PARSER.escape s
|
|
37
43
|
end
|
|
38
|
-
module_function :escape_path
|
|
39
44
|
|
|
40
45
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
|
41
46
|
# unescaping query parameters or form components.
|
|
42
47
|
def unescape_path(s)
|
|
43
48
|
::URI::DEFAULT_PARSER.unescape s
|
|
44
49
|
end
|
|
45
|
-
module_function :unescape_path
|
|
46
|
-
|
|
47
50
|
|
|
48
51
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
|
49
52
|
# target encoding of the string returned, and it defaults to UTF-8
|
|
50
53
|
def unescape(s, encoding = Encoding::UTF_8)
|
|
51
54
|
URI.decode_www_form_component(s, encoding)
|
|
52
55
|
end
|
|
53
|
-
module_function :unescape
|
|
54
56
|
|
|
55
57
|
class << self
|
|
56
58
|
attr_accessor :multipart_part_limit
|
|
@@ -82,21 +84,20 @@ module Rack
|
|
|
82
84
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
83
85
|
end
|
|
84
86
|
else
|
|
87
|
+
# :nocov:
|
|
85
88
|
def clock_time
|
|
86
89
|
Time.now.to_f
|
|
87
90
|
end
|
|
91
|
+
# :nocov:
|
|
88
92
|
end
|
|
89
|
-
module_function :clock_time
|
|
90
93
|
|
|
91
94
|
def parse_query(qs, d = nil, &unescaper)
|
|
92
95
|
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
|
93
96
|
end
|
|
94
|
-
module_function :parse_query
|
|
95
97
|
|
|
96
98
|
def parse_nested_query(qs, d = nil)
|
|
97
99
|
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
|
98
100
|
end
|
|
99
|
-
module_function :parse_nested_query
|
|
100
101
|
|
|
101
102
|
def build_query(params)
|
|
102
103
|
params.map { |k, v|
|
|
@@ -107,7 +108,6 @@ module Rack
|
|
|
107
108
|
end
|
|
108
109
|
}.join("&")
|
|
109
110
|
end
|
|
110
|
-
module_function :build_query
|
|
111
111
|
|
|
112
112
|
def build_nested_query(value, prefix = nil)
|
|
113
113
|
case value
|
|
@@ -118,7 +118,7 @@ module Rack
|
|
|
118
118
|
when Hash
|
|
119
119
|
value.map { |k, v|
|
|
120
120
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
|
121
|
-
}.
|
|
121
|
+
}.delete_if(&:empty?).join('&')
|
|
122
122
|
when nil
|
|
123
123
|
prefix
|
|
124
124
|
else
|
|
@@ -126,20 +126,22 @@ module Rack
|
|
|
126
126
|
"#{prefix}=#{escape(value)}"
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
|
-
module_function :build_nested_query
|
|
130
129
|
|
|
131
130
|
def q_values(q_value_header)
|
|
132
131
|
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
|
133
132
|
value, parameters = part.split(/\s*;\s*/, 2)
|
|
134
133
|
quality = 1.0
|
|
135
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
|
134
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
|
136
135
|
quality = md[1].to_f
|
|
137
136
|
end
|
|
138
137
|
[value, quality]
|
|
139
138
|
end
|
|
140
139
|
end
|
|
141
|
-
module_function :q_values
|
|
142
140
|
|
|
141
|
+
# Return best accept value to use, based on the algorithm
|
|
142
|
+
# in RFC 2616 Section 14. If there are multiple best
|
|
143
|
+
# matches (same specificity and quality), the value returned
|
|
144
|
+
# is arbitrary.
|
|
143
145
|
def best_q_match(q_value_header, available_mimes)
|
|
144
146
|
values = q_values(q_value_header)
|
|
145
147
|
|
|
@@ -152,7 +154,6 @@ module Rack
|
|
|
152
154
|
end.last
|
|
153
155
|
matches && matches.first
|
|
154
156
|
end
|
|
155
|
-
module_function :best_q_match
|
|
156
157
|
|
|
157
158
|
ESCAPE_HTML = {
|
|
158
159
|
"&" => "&",
|
|
@@ -169,51 +170,51 @@ module Rack
|
|
|
169
170
|
def escape_html(string)
|
|
170
171
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
|
171
172
|
end
|
|
172
|
-
module_function :escape_html
|
|
173
173
|
|
|
174
174
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
175
175
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
176
176
|
|
|
177
|
-
expanded_accept_encoding =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
expanded_accept_encoding = []
|
|
178
|
+
|
|
179
|
+
accept_encoding.each do |m, q|
|
|
180
|
+
preference = available_encodings.index(m) || available_encodings.size
|
|
181
|
+
|
|
182
|
+
if m == "*"
|
|
183
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
|
184
|
+
expanded_accept_encoding << [m2, q, preference]
|
|
183
185
|
end
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
else
|
|
187
|
+
expanded_accept_encoding << [m, q, preference]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
187
190
|
|
|
188
|
-
encoding_candidates = expanded_accept_encoding
|
|
191
|
+
encoding_candidates = expanded_accept_encoding
|
|
192
|
+
.sort_by { |_, q, p| [-q, p] }
|
|
193
|
+
.map!(&:first)
|
|
189
194
|
|
|
190
195
|
unless encoding_candidates.include?("identity")
|
|
191
196
|
encoding_candidates.push("identity")
|
|
192
197
|
end
|
|
193
198
|
|
|
194
|
-
expanded_accept_encoding.each
|
|
199
|
+
expanded_accept_encoding.each do |m, q|
|
|
195
200
|
encoding_candidates.delete(m) if q == 0.0
|
|
196
|
-
|
|
201
|
+
end
|
|
197
202
|
|
|
198
|
-
|
|
203
|
+
(encoding_candidates & available_encodings)[0]
|
|
199
204
|
end
|
|
200
|
-
module_function :select_best_encoding
|
|
201
205
|
|
|
202
206
|
def parse_cookies(env)
|
|
203
207
|
parse_cookies_header env[HTTP_COOKIE]
|
|
204
208
|
end
|
|
205
|
-
module_function :parse_cookies
|
|
206
209
|
|
|
207
210
|
def parse_cookies_header(header)
|
|
208
|
-
# According to RFC
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
cookies
|
|
214
|
-
cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
|
|
211
|
+
# According to RFC 6265:
|
|
212
|
+
# The syntax for cookie headers only supports semicolons
|
|
213
|
+
# User Agent -> Server ==
|
|
214
|
+
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
|
215
|
+
cookies = parse_query(header, ';') { |s| unescape(s) rescue s }
|
|
216
|
+
cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
|
|
215
217
|
end
|
|
216
|
-
module_function :parse_cookies_header
|
|
217
218
|
|
|
218
219
|
def add_cookie_to_header(header, key, value)
|
|
219
220
|
case value
|
|
@@ -221,31 +222,7 @@ module Rack
|
|
|
221
222
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
|
222
223
|
path = "; path=#{value[:path]}" if value[:path]
|
|
223
224
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
|
224
|
-
|
|
225
|
-
# only are there contradicting RFCs and examples within RFC text, but
|
|
226
|
-
# there are also numerous conflicting names of fields and partially
|
|
227
|
-
# cross-applicable specifications.
|
|
228
|
-
#
|
|
229
|
-
# These are best described in RFC 2616 3.3.1. This RFC text also
|
|
230
|
-
# specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
|
|
231
|
-
# fixed length format with space-date delimited fields.
|
|
232
|
-
#
|
|
233
|
-
# See also RFC 1123 section 5.2.14.
|
|
234
|
-
#
|
|
235
|
-
# RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
|
|
236
|
-
# in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
|
|
237
|
-
# the space delimited format. These formats are compliant with RFC 2822.
|
|
238
|
-
#
|
|
239
|
-
# For reference, all involved RFCs are:
|
|
240
|
-
# RFC 822
|
|
241
|
-
# RFC 1123
|
|
242
|
-
# RFC 2109
|
|
243
|
-
# RFC 2616
|
|
244
|
-
# RFC 2822
|
|
245
|
-
# RFC 2965
|
|
246
|
-
# RFC 6265
|
|
247
|
-
expires = "; expires=" +
|
|
248
|
-
rfc2822(value[:expires].clone.gmtime) if value[:expires]
|
|
225
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
|
249
226
|
secure = "; secure" if value[:secure]
|
|
250
227
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
|
251
228
|
same_site =
|
|
@@ -253,11 +230,11 @@ module Rack
|
|
|
253
230
|
when false, nil
|
|
254
231
|
nil
|
|
255
232
|
when :none, 'None', :None
|
|
256
|
-
'; SameSite=None'
|
|
233
|
+
'; SameSite=None'
|
|
257
234
|
when :lax, 'Lax', :Lax
|
|
258
|
-
'; SameSite=Lax'
|
|
235
|
+
'; SameSite=Lax'
|
|
259
236
|
when true, :strict, 'Strict', :Strict
|
|
260
|
-
'; SameSite=Strict'
|
|
237
|
+
'; SameSite=Strict'
|
|
261
238
|
else
|
|
262
239
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
|
263
240
|
end
|
|
@@ -279,13 +256,11 @@ module Rack
|
|
|
279
256
|
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
|
280
257
|
end
|
|
281
258
|
end
|
|
282
|
-
module_function :add_cookie_to_header
|
|
283
259
|
|
|
284
260
|
def set_cookie_header!(header, key, value)
|
|
285
261
|
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
|
286
262
|
nil
|
|
287
263
|
end
|
|
288
|
-
module_function :set_cookie_header!
|
|
289
264
|
|
|
290
265
|
def make_delete_cookie_header(header, key, value)
|
|
291
266
|
case header
|
|
@@ -297,25 +272,30 @@ module Rack
|
|
|
297
272
|
cookies = header
|
|
298
273
|
end
|
|
299
274
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
275
|
+
key = escape(key)
|
|
276
|
+
domain = value[:domain]
|
|
277
|
+
path = value[:path]
|
|
278
|
+
regexp = if domain
|
|
279
|
+
if path
|
|
280
|
+
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
|
281
|
+
else
|
|
282
|
+
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
|
283
|
+
end
|
|
284
|
+
elsif path
|
|
285
|
+
/\A#{key}=.*path=#{path}(?:;|$)/
|
|
286
|
+
else
|
|
287
|
+
/\A#{key}=/
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
|
309
291
|
|
|
310
292
|
cookies.join("\n")
|
|
311
293
|
end
|
|
312
|
-
module_function :make_delete_cookie_header
|
|
313
294
|
|
|
314
295
|
def delete_cookie_header!(header, key, value = {})
|
|
315
296
|
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
|
316
297
|
nil
|
|
317
298
|
end
|
|
318
|
-
module_function :delete_cookie_header!
|
|
319
299
|
|
|
320
300
|
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
|
321
301
|
# strange method name.
|
|
@@ -323,17 +303,15 @@ module Rack
|
|
|
323
303
|
new_header = make_delete_cookie_header(header, key, value)
|
|
324
304
|
|
|
325
305
|
add_cookie_to_header(new_header, key,
|
|
326
|
-
{:
|
|
327
|
-
:
|
|
328
|
-
:
|
|
306
|
+
{ value: '', path: nil, domain: nil,
|
|
307
|
+
max_age: '0',
|
|
308
|
+
expires: Time.at(0) }.merge(value))
|
|
329
309
|
|
|
330
310
|
end
|
|
331
|
-
module_function :add_remove_cookie_to_header
|
|
332
311
|
|
|
333
312
|
def rfc2822(time)
|
|
334
313
|
time.rfc2822
|
|
335
314
|
end
|
|
336
|
-
module_function :rfc2822
|
|
337
315
|
|
|
338
316
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
|
339
317
|
# of '% %b %Y'.
|
|
@@ -349,7 +327,6 @@ module Rack
|
|
|
349
327
|
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
|
350
328
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
|
351
329
|
end
|
|
352
|
-
module_function :rfc2109
|
|
353
330
|
|
|
354
331
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
355
332
|
# Returns nil if the header is missing or syntactically invalid.
|
|
@@ -358,7 +335,6 @@ module Rack
|
|
|
358
335
|
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
|
359
336
|
get_byte_ranges env['HTTP_RANGE'], size
|
|
360
337
|
end
|
|
361
|
-
module_function :byte_ranges
|
|
362
338
|
|
|
363
339
|
def get_byte_ranges(http_range, size)
|
|
364
340
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
@@ -366,7 +342,7 @@ module Rack
|
|
|
366
342
|
ranges = []
|
|
367
343
|
$1.split(/,\s*/).each do |range_spec|
|
|
368
344
|
return nil unless range_spec =~ /(\d*)-(\d*)/
|
|
369
|
-
r0,r1 = $1, $2
|
|
345
|
+
r0, r1 = $1, $2
|
|
370
346
|
if r0.empty?
|
|
371
347
|
return nil if r1.empty?
|
|
372
348
|
# suffix-byte-range-spec, represents trailing suffix of file
|
|
@@ -380,14 +356,13 @@ module Rack
|
|
|
380
356
|
else
|
|
381
357
|
r1 = r1.to_i
|
|
382
358
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
|
383
|
-
r1 = size-1 if r1 >= size
|
|
359
|
+
r1 = size - 1 if r1 >= size
|
|
384
360
|
end
|
|
385
361
|
end
|
|
386
362
|
ranges << (r0..r1) if r0 <= r1
|
|
387
363
|
end
|
|
388
364
|
ranges
|
|
389
365
|
end
|
|
390
|
-
module_function :get_byte_ranges
|
|
391
366
|
|
|
392
367
|
# Constant time string comparison.
|
|
393
368
|
#
|
|
@@ -401,10 +376,9 @@ module Rack
|
|
|
401
376
|
l = a.unpack("C*")
|
|
402
377
|
|
|
403
378
|
r, i = 0, -1
|
|
404
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
|
379
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
|
405
380
|
r == 0
|
|
406
381
|
end
|
|
407
|
-
module_function :secure_compare
|
|
408
382
|
|
|
409
383
|
# Context allows the use of a compatible middleware at different points
|
|
410
384
|
# in a request handling stack. A compatible middleware must define
|
|
@@ -427,19 +401,25 @@ module Rack
|
|
|
427
401
|
self.class.new(@for, app)
|
|
428
402
|
end
|
|
429
403
|
|
|
430
|
-
def context(env, app
|
|
404
|
+
def context(env, app = @app)
|
|
431
405
|
recontext(app).call(env)
|
|
432
406
|
end
|
|
433
407
|
end
|
|
434
408
|
|
|
435
409
|
# A case-insensitive Hash that preserves the original case of a
|
|
436
410
|
# header when set.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
411
|
+
#
|
|
412
|
+
# @api private
|
|
413
|
+
class HeaderHash < Hash # :nodoc:
|
|
414
|
+
def self.[](headers)
|
|
415
|
+
if headers.is_a?(HeaderHash) && !headers.frozen?
|
|
416
|
+
return headers
|
|
417
|
+
else
|
|
418
|
+
return self.new(headers)
|
|
419
|
+
end
|
|
440
420
|
end
|
|
441
421
|
|
|
442
|
-
def initialize(hash={})
|
|
422
|
+
def initialize(hash = {})
|
|
443
423
|
super()
|
|
444
424
|
@names = {}
|
|
445
425
|
hash.each { |k, v| self[k] = v }
|
|
@@ -451,6 +431,12 @@ module Rack
|
|
|
451
431
|
@names = other.names.dup
|
|
452
432
|
end
|
|
453
433
|
|
|
434
|
+
# on clear, we need to clear @names hash
|
|
435
|
+
def clear
|
|
436
|
+
super
|
|
437
|
+
@names.clear
|
|
438
|
+
end
|
|
439
|
+
|
|
454
440
|
def each
|
|
455
441
|
super do |k, v|
|
|
456
442
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
|
@@ -459,7 +445,7 @@ module Rack
|
|
|
459
445
|
|
|
460
446
|
def to_hash
|
|
461
447
|
hash = {}
|
|
462
|
-
each { |k,v| hash[k] = v }
|
|
448
|
+
each { |k, v| hash[k] = v }
|
|
463
449
|
hash
|
|
464
450
|
end
|
|
465
451
|
|
|
@@ -512,13 +498,14 @@ module Rack
|
|
|
512
498
|
|
|
513
499
|
# Every standard HTTP code mapped to the appropriate message.
|
|
514
500
|
# Generated with:
|
|
515
|
-
#
|
|
516
|
-
#
|
|
517
|
-
#
|
|
501
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
|
502
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
|
503
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
|
518
504
|
HTTP_STATUS_CODES = {
|
|
519
505
|
100 => 'Continue',
|
|
520
506
|
101 => 'Switching Protocols',
|
|
521
507
|
102 => 'Processing',
|
|
508
|
+
103 => 'Early Hints',
|
|
522
509
|
200 => 'OK',
|
|
523
510
|
201 => 'Created',
|
|
524
511
|
202 => 'Accepted',
|
|
@@ -535,6 +522,7 @@ module Rack
|
|
|
535
522
|
303 => 'See Other',
|
|
536
523
|
304 => 'Not Modified',
|
|
537
524
|
305 => 'Use Proxy',
|
|
525
|
+
306 => '(Unused)',
|
|
538
526
|
307 => 'Temporary Redirect',
|
|
539
527
|
308 => 'Permanent Redirect',
|
|
540
528
|
400 => 'Bad Request',
|
|
@@ -559,6 +547,7 @@ module Rack
|
|
|
559
547
|
422 => 'Unprocessable Entity',
|
|
560
548
|
423 => 'Locked',
|
|
561
549
|
424 => 'Failed Dependency',
|
|
550
|
+
425 => 'Too Early',
|
|
562
551
|
426 => 'Upgrade Required',
|
|
563
552
|
428 => 'Precondition Required',
|
|
564
553
|
429 => 'Too Many Requests',
|
|
@@ -573,12 +562,13 @@ module Rack
|
|
|
573
562
|
506 => 'Variant Also Negotiates',
|
|
574
563
|
507 => 'Insufficient Storage',
|
|
575
564
|
508 => 'Loop Detected',
|
|
565
|
+
509 => 'Bandwidth Limit Exceeded',
|
|
576
566
|
510 => 'Not Extended',
|
|
577
567
|
511 => 'Network Authentication Required'
|
|
578
568
|
}
|
|
579
569
|
|
|
580
570
|
# Responses with HTTP status codes that should not have an entity body
|
|
581
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
|
571
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
|
582
572
|
|
|
583
573
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
|
584
574
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
|
@@ -586,12 +576,11 @@ module Rack
|
|
|
586
576
|
|
|
587
577
|
def status_code(status)
|
|
588
578
|
if status.is_a?(Symbol)
|
|
589
|
-
SYMBOL_TO_STATUS_CODE
|
|
579
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
|
590
580
|
else
|
|
591
581
|
status.to_i
|
|
592
582
|
end
|
|
593
583
|
end
|
|
594
|
-
module_function :status_code
|
|
595
584
|
|
|
596
585
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
|
597
586
|
|
|
@@ -605,18 +594,16 @@ module Rack
|
|
|
605
594
|
part == '..' ? clean.pop : clean << part
|
|
606
595
|
end
|
|
607
596
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
597
|
+
clean_path = clean.join(::File::SEPARATOR)
|
|
598
|
+
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
|
599
|
+
clean_path
|
|
611
600
|
end
|
|
612
|
-
module_function :clean_path_info
|
|
613
601
|
|
|
614
|
-
NULL_BYTE = "\0"
|
|
602
|
+
NULL_BYTE = "\0"
|
|
615
603
|
|
|
616
604
|
def valid_path?(path)
|
|
617
605
|
path.valid_encoding? && !path.include?(NULL_BYTE)
|
|
618
606
|
end
|
|
619
|
-
module_function :valid_path?
|
|
620
607
|
|
|
621
608
|
end
|
|
622
609
|
end
|