rack 2.0.1 → 2.2.17
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 +5 -5
- data/CHANGELOG.md +795 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +188 -145
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +46 -17
- 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/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +6 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +5 -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 +37 -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 +60 -70
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +8 -5
- 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/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 +3 -3
- data/lib/rack/handler/webrick.rb +19 -10
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +221 -186
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +14 -4
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +23 -8
- data/lib/rack/method_override.rb +13 -4
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +135 -29
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +85 -68
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/multipart.rb +6 -5
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +108 -36
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +232 -60
- 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 +14 -10
- data/lib/rack/server.rb +97 -25
- data/lib/rack/session/abstract/id.rb +113 -25
- data/lib/rack/session/cookie.rb +22 -14
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +24 -10
- data/lib/rack/show_exceptions.rb +22 -18
- data/lib/rack/show_status.rb +9 -9
- data/lib/rack/static.rb +25 -12
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +13 -7
- data/lib/rack/utils.rb +135 -123
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +67 -73
- data/rack.gemspec +40 -29
- metadata +25 -184
- 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_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 -365
- 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 -251
- 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 -194
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -83
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -342
- data/test/spec_multipart.rb +0 -716
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1393
- data/test/spec_response.rb +0 -510
- 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 -28
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -320
- data/test/spec_session_pool.rb +0 -210
- data/test/spec_show_exceptions.rb +0 -80
- 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 -208
- 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/utils.rb
CHANGED
@@ -1,22 +1,31 @@
|
|
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
|
17
22
|
COMMON_SEP = QueryParser::COMMON_SEP
|
18
23
|
KeySpaceConstrainedParams = QueryParser::Params
|
19
24
|
|
25
|
+
RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
|
26
|
+
RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
|
27
|
+
RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
|
28
|
+
|
20
29
|
class << self
|
21
30
|
attr_accessor :default_query_parser
|
22
31
|
end
|
@@ -24,42 +33,50 @@ module Rack
|
|
24
33
|
# This helps prevent a rogue client from flooding a Request.
|
25
34
|
self.default_query_parser = QueryParser.make_default(65536, 100)
|
26
35
|
|
36
|
+
module_function
|
37
|
+
|
27
38
|
# URI escapes. (CGI style space to +)
|
28
39
|
def escape(s)
|
29
40
|
URI.encode_www_form_component(s)
|
30
41
|
end
|
31
|
-
module_function :escape
|
32
42
|
|
33
43
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
34
44
|
# true URI escaping.
|
35
45
|
def escape_path(s)
|
36
|
-
|
46
|
+
RFC2396_PARSER.escape s
|
37
47
|
end
|
38
|
-
module_function :escape_path
|
39
48
|
|
40
49
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
41
50
|
# unescaping query parameters or form components.
|
42
51
|
def unescape_path(s)
|
43
|
-
|
52
|
+
RFC2396_PARSER.unescape s
|
44
53
|
end
|
45
|
-
module_function :unescape_path
|
46
|
-
|
47
54
|
|
48
55
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
49
56
|
# target encoding of the string returned, and it defaults to UTF-8
|
50
57
|
def unescape(s, encoding = Encoding::UTF_8)
|
51
58
|
URI.decode_www_form_component(s, encoding)
|
52
59
|
end
|
53
|
-
module_function :unescape
|
54
60
|
|
55
61
|
class << self
|
56
|
-
attr_accessor :
|
62
|
+
attr_accessor :multipart_total_part_limit
|
63
|
+
|
64
|
+
attr_accessor :multipart_file_limit
|
65
|
+
|
66
|
+
# multipart_part_limit is the original name of multipart_file_limit, but
|
67
|
+
# the limit only counts parts with filenames.
|
68
|
+
alias multipart_part_limit multipart_file_limit
|
69
|
+
alias multipart_part_limit= multipart_file_limit=
|
57
70
|
end
|
58
71
|
|
59
|
-
# The maximum number of parts a request can contain. Accepting too
|
60
|
-
# can lead to the server running out of file handles.
|
72
|
+
# The maximum number of file parts a request can contain. Accepting too
|
73
|
+
# many parts can lead to the server running out of file handles.
|
61
74
|
# Set to `0` for no limit.
|
62
|
-
self.
|
75
|
+
self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
|
76
|
+
|
77
|
+
# The maximum total number of parts a request can contain. Accepting too
|
78
|
+
# many can lead to excessive memory use and parsing time.
|
79
|
+
self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
|
63
80
|
|
64
81
|
def self.param_depth_limit
|
65
82
|
default_query_parser.param_depth_limit
|
@@ -82,21 +99,20 @@ module Rack
|
|
82
99
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
83
100
|
end
|
84
101
|
else
|
102
|
+
# :nocov:
|
85
103
|
def clock_time
|
86
104
|
Time.now.to_f
|
87
105
|
end
|
106
|
+
# :nocov:
|
88
107
|
end
|
89
|
-
module_function :clock_time
|
90
108
|
|
91
109
|
def parse_query(qs, d = nil, &unescaper)
|
92
110
|
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
93
111
|
end
|
94
|
-
module_function :parse_query
|
95
112
|
|
96
113
|
def parse_nested_query(qs, d = nil)
|
97
114
|
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
98
115
|
end
|
99
|
-
module_function :parse_nested_query
|
100
116
|
|
101
117
|
def build_query(params)
|
102
118
|
params.map { |k, v|
|
@@ -107,7 +123,6 @@ module Rack
|
|
107
123
|
end
|
108
124
|
}.join("&")
|
109
125
|
end
|
110
|
-
module_function :build_query
|
111
126
|
|
112
127
|
def build_nested_query(value, prefix = nil)
|
113
128
|
case value
|
@@ -118,7 +133,7 @@ module Rack
|
|
118
133
|
when Hash
|
119
134
|
value.map { |k, v|
|
120
135
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
121
|
-
}.
|
136
|
+
}.delete_if(&:empty?).join('&')
|
122
137
|
when nil
|
123
138
|
prefix
|
124
139
|
else
|
@@ -126,20 +141,22 @@ module Rack
|
|
126
141
|
"#{prefix}=#{escape(value)}"
|
127
142
|
end
|
128
143
|
end
|
129
|
-
module_function :build_nested_query
|
130
144
|
|
131
145
|
def q_values(q_value_header)
|
132
|
-
q_value_header.to_s.split(
|
133
|
-
value, parameters = part.split(
|
146
|
+
q_value_header.to_s.split(',').map do |part|
|
147
|
+
value, parameters = part.split(';', 2).map(&:strip)
|
134
148
|
quality = 1.0
|
135
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
149
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
136
150
|
quality = md[1].to_f
|
137
151
|
end
|
138
152
|
[value, quality]
|
139
153
|
end
|
140
154
|
end
|
141
|
-
module_function :q_values
|
142
155
|
|
156
|
+
# Return best accept value to use, based on the algorithm
|
157
|
+
# in RFC 2616 Section 14. If there are multiple best
|
158
|
+
# matches (same specificity and quality), the value returned
|
159
|
+
# is arbitrary.
|
143
160
|
def best_q_match(q_value_header, available_mimes)
|
144
161
|
values = q_values(q_value_header)
|
145
162
|
|
@@ -152,7 +169,6 @@ module Rack
|
|
152
169
|
end.last
|
153
170
|
matches && matches.first
|
154
171
|
end
|
155
|
-
module_function :best_q_match
|
156
172
|
|
157
173
|
ESCAPE_HTML = {
|
158
174
|
"&" => "&",
|
@@ -169,51 +185,55 @@ module Rack
|
|
169
185
|
def escape_html(string)
|
170
186
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
171
187
|
end
|
172
|
-
module_function :escape_html
|
173
188
|
|
174
189
|
def select_best_encoding(available_encodings, accept_encoding)
|
175
190
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
176
191
|
|
177
|
-
expanded_accept_encoding =
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
192
|
+
expanded_accept_encoding = []
|
193
|
+
|
194
|
+
accept_encoding.each do |m, q|
|
195
|
+
preference = available_encodings.index(m) || available_encodings.size
|
196
|
+
|
197
|
+
if m == "*"
|
198
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
199
|
+
expanded_accept_encoding << [m2, q, preference]
|
183
200
|
end
|
184
|
-
|
185
|
-
|
186
|
-
|
201
|
+
else
|
202
|
+
expanded_accept_encoding << [m, q, preference]
|
203
|
+
end
|
204
|
+
end
|
187
205
|
|
188
|
-
encoding_candidates = expanded_accept_encoding
|
206
|
+
encoding_candidates = expanded_accept_encoding
|
207
|
+
.sort_by { |_, q, p| [-q, p] }
|
208
|
+
.map!(&:first)
|
189
209
|
|
190
210
|
unless encoding_candidates.include?("identity")
|
191
211
|
encoding_candidates.push("identity")
|
192
212
|
end
|
193
213
|
|
194
|
-
expanded_accept_encoding.each
|
214
|
+
expanded_accept_encoding.each do |m, q|
|
195
215
|
encoding_candidates.delete(m) if q == 0.0
|
196
|
-
|
216
|
+
end
|
197
217
|
|
198
|
-
|
218
|
+
(encoding_candidates & available_encodings)[0]
|
199
219
|
end
|
200
|
-
module_function :select_best_encoding
|
201
220
|
|
202
221
|
def parse_cookies(env)
|
203
222
|
parse_cookies_header env[HTTP_COOKIE]
|
204
223
|
end
|
205
|
-
module_function :parse_cookies
|
206
224
|
|
207
225
|
def parse_cookies_header(header)
|
208
|
-
# According to RFC
|
209
|
-
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
|
213
|
-
|
214
|
-
|
226
|
+
# According to RFC 6265:
|
227
|
+
# The syntax for cookie headers only supports semicolons
|
228
|
+
# User Agent -> Server ==
|
229
|
+
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
230
|
+
return {} unless header
|
231
|
+
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
232
|
+
next if cookie.empty?
|
233
|
+
key, value = cookie.split('=', 2)
|
234
|
+
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
235
|
+
end
|
215
236
|
end
|
216
|
-
module_function :parse_cookies_header
|
217
237
|
|
218
238
|
def add_cookie_to_header(header, key, value)
|
219
239
|
case value
|
@@ -221,41 +241,19 @@ module Rack
|
|
221
241
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
222
242
|
path = "; path=#{value[:path]}" if value[:path]
|
223
243
|
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]
|
244
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
249
245
|
secure = "; secure" if value[:secure]
|
250
246
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
251
247
|
same_site =
|
252
248
|
case value[:same_site]
|
253
249
|
when false, nil
|
254
250
|
nil
|
251
|
+
when :none, 'None', :None
|
252
|
+
'; SameSite=None'
|
255
253
|
when :lax, 'Lax', :Lax
|
256
|
-
'; SameSite=Lax'
|
254
|
+
'; SameSite=Lax'
|
257
255
|
when true, :strict, 'Strict', :Strict
|
258
|
-
'; SameSite=Strict'
|
256
|
+
'; SameSite=Strict'
|
259
257
|
else
|
260
258
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
261
259
|
end
|
@@ -277,13 +275,11 @@ module Rack
|
|
277
275
|
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
278
276
|
end
|
279
277
|
end
|
280
|
-
module_function :add_cookie_to_header
|
281
278
|
|
282
279
|
def set_cookie_header!(header, key, value)
|
283
280
|
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
284
281
|
nil
|
285
282
|
end
|
286
|
-
module_function :set_cookie_header!
|
287
283
|
|
288
284
|
def make_delete_cookie_header(header, key, value)
|
289
285
|
case header
|
@@ -295,25 +291,30 @@ module Rack
|
|
295
291
|
cookies = header
|
296
292
|
end
|
297
293
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
294
|
+
key = escape(key)
|
295
|
+
domain = value[:domain]
|
296
|
+
path = value[:path]
|
297
|
+
regexp = if domain
|
298
|
+
if path
|
299
|
+
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
300
|
+
else
|
301
|
+
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
302
|
+
end
|
303
|
+
elsif path
|
304
|
+
/\A#{key}=.*path=#{path}(?:;|$)/
|
305
|
+
else
|
306
|
+
/\A#{key}=/
|
307
|
+
end
|
308
|
+
|
309
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
307
310
|
|
308
311
|
cookies.join("\n")
|
309
312
|
end
|
310
|
-
module_function :make_delete_cookie_header
|
311
313
|
|
312
314
|
def delete_cookie_header!(header, key, value = {})
|
313
315
|
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
314
316
|
nil
|
315
317
|
end
|
316
|
-
module_function :delete_cookie_header!
|
317
318
|
|
318
319
|
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
319
320
|
# strange method name.
|
@@ -321,17 +322,15 @@ module Rack
|
|
321
322
|
new_header = make_delete_cookie_header(header, key, value)
|
322
323
|
|
323
324
|
add_cookie_to_header(new_header, key,
|
324
|
-
{:
|
325
|
-
:
|
326
|
-
:
|
325
|
+
{ value: '', path: nil, domain: nil,
|
326
|
+
max_age: '0',
|
327
|
+
expires: Time.at(0) }.merge(value))
|
327
328
|
|
328
329
|
end
|
329
|
-
module_function :add_remove_cookie_to_header
|
330
330
|
|
331
331
|
def rfc2822(time)
|
332
332
|
time.rfc2822
|
333
333
|
end
|
334
|
-
module_function :rfc2822
|
335
334
|
|
336
335
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
337
336
|
# of '% %b %Y'.
|
@@ -343,11 +342,10 @@ module Rack
|
|
343
342
|
# weekday and month.
|
344
343
|
#
|
345
344
|
def rfc2109(time)
|
346
|
-
wday =
|
347
|
-
mon =
|
345
|
+
wday = RFC2822_DAY_NAME[time.wday]
|
346
|
+
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
348
347
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
349
348
|
end
|
350
|
-
module_function :rfc2109
|
351
349
|
|
352
350
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
353
351
|
# Returns nil if the header is missing or syntactically invalid.
|
@@ -356,36 +354,38 @@ module Rack
|
|
356
354
|
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
357
355
|
get_byte_ranges env['HTTP_RANGE'], size
|
358
356
|
end
|
359
|
-
module_function :byte_ranges
|
360
357
|
|
361
358
|
def get_byte_ranges(http_range, size)
|
362
359
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
363
360
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
364
361
|
ranges = []
|
365
362
|
$1.split(/,\s*/).each do |range_spec|
|
366
|
-
return nil
|
367
|
-
|
368
|
-
|
369
|
-
|
363
|
+
return nil unless range_spec.include?('-')
|
364
|
+
range = range_spec.split('-')
|
365
|
+
r0, r1 = range[0], range[1]
|
366
|
+
if r0.nil? || r0.empty?
|
367
|
+
return nil if r1.nil?
|
370
368
|
# suffix-byte-range-spec, represents trailing suffix of file
|
371
369
|
r0 = size - r1.to_i
|
372
370
|
r0 = 0 if r0 < 0
|
373
371
|
r1 = size - 1
|
374
372
|
else
|
375
373
|
r0 = r0.to_i
|
376
|
-
if r1.
|
374
|
+
if r1.nil?
|
377
375
|
r1 = size - 1
|
378
376
|
else
|
379
377
|
r1 = r1.to_i
|
380
378
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
381
|
-
r1 = size-1 if r1 >= size
|
379
|
+
r1 = size - 1 if r1 >= size
|
382
380
|
end
|
383
381
|
end
|
384
382
|
ranges << (r0..r1) if r0 <= r1
|
385
383
|
end
|
384
|
+
|
385
|
+
return [] if ranges.map(&:size).inject(0, :+) > size
|
386
|
+
|
386
387
|
ranges
|
387
388
|
end
|
388
|
-
module_function :get_byte_ranges
|
389
389
|
|
390
390
|
# Constant time string comparison.
|
391
391
|
#
|
@@ -399,10 +399,9 @@ module Rack
|
|
399
399
|
l = a.unpack("C*")
|
400
400
|
|
401
401
|
r, i = 0, -1
|
402
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
402
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
403
403
|
r == 0
|
404
404
|
end
|
405
|
-
module_function :secure_compare
|
406
405
|
|
407
406
|
# Context allows the use of a compatible middleware at different points
|
408
407
|
# in a request handling stack. A compatible middleware must define
|
@@ -425,19 +424,25 @@ module Rack
|
|
425
424
|
self.class.new(@for, app)
|
426
425
|
end
|
427
426
|
|
428
|
-
def context(env, app
|
427
|
+
def context(env, app = @app)
|
429
428
|
recontext(app).call(env)
|
430
429
|
end
|
431
430
|
end
|
432
431
|
|
433
432
|
# A case-insensitive Hash that preserves the original case of a
|
434
433
|
# header when set.
|
435
|
-
|
436
|
-
|
437
|
-
|
434
|
+
#
|
435
|
+
# @api private
|
436
|
+
class HeaderHash < Hash # :nodoc:
|
437
|
+
def self.[](headers)
|
438
|
+
if headers.is_a?(HeaderHash) && !headers.frozen?
|
439
|
+
return headers
|
440
|
+
else
|
441
|
+
return self.new(headers)
|
442
|
+
end
|
438
443
|
end
|
439
444
|
|
440
|
-
def initialize(hash={})
|
445
|
+
def initialize(hash = {})
|
441
446
|
super()
|
442
447
|
@names = {}
|
443
448
|
hash.each { |k, v| self[k] = v }
|
@@ -449,6 +454,12 @@ module Rack
|
|
449
454
|
@names = other.names.dup
|
450
455
|
end
|
451
456
|
|
457
|
+
# on clear, we need to clear @names hash
|
458
|
+
def clear
|
459
|
+
super
|
460
|
+
@names.clear
|
461
|
+
end
|
462
|
+
|
452
463
|
def each
|
453
464
|
super do |k, v|
|
454
465
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -457,7 +468,7 @@ module Rack
|
|
457
468
|
|
458
469
|
def to_hash
|
459
470
|
hash = {}
|
460
|
-
each { |k,v| hash[k] = v }
|
471
|
+
each { |k, v| hash[k] = v }
|
461
472
|
hash
|
462
473
|
end
|
463
474
|
|
@@ -510,13 +521,14 @@ module Rack
|
|
510
521
|
|
511
522
|
# Every standard HTTP code mapped to the appropriate message.
|
512
523
|
# Generated with:
|
513
|
-
#
|
514
|
-
#
|
515
|
-
#
|
524
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
525
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
526
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
516
527
|
HTTP_STATUS_CODES = {
|
517
528
|
100 => 'Continue',
|
518
529
|
101 => 'Switching Protocols',
|
519
530
|
102 => 'Processing',
|
531
|
+
103 => 'Early Hints',
|
520
532
|
200 => 'OK',
|
521
533
|
201 => 'Created',
|
522
534
|
202 => 'Accepted',
|
@@ -533,6 +545,7 @@ module Rack
|
|
533
545
|
303 => 'See Other',
|
534
546
|
304 => 'Not Modified',
|
535
547
|
305 => 'Use Proxy',
|
548
|
+
306 => '(Unused)',
|
536
549
|
307 => 'Temporary Redirect',
|
537
550
|
308 => 'Permanent Redirect',
|
538
551
|
400 => 'Bad Request',
|
@@ -557,6 +570,7 @@ module Rack
|
|
557
570
|
422 => 'Unprocessable Entity',
|
558
571
|
423 => 'Locked',
|
559
572
|
424 => 'Failed Dependency',
|
573
|
+
425 => 'Too Early',
|
560
574
|
426 => 'Upgrade Required',
|
561
575
|
428 => 'Precondition Required',
|
562
576
|
429 => 'Too Many Requests',
|
@@ -571,12 +585,13 @@ module Rack
|
|
571
585
|
506 => 'Variant Also Negotiates',
|
572
586
|
507 => 'Insufficient Storage',
|
573
587
|
508 => 'Loop Detected',
|
588
|
+
509 => 'Bandwidth Limit Exceeded',
|
574
589
|
510 => 'Not Extended',
|
575
590
|
511 => 'Network Authentication Required'
|
576
591
|
}
|
577
592
|
|
578
593
|
# Responses with HTTP status codes that should not have an entity body
|
579
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
594
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
580
595
|
|
581
596
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
582
597
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -584,12 +599,11 @@ module Rack
|
|
584
599
|
|
585
600
|
def status_code(status)
|
586
601
|
if status.is_a?(Symbol)
|
587
|
-
SYMBOL_TO_STATUS_CODE
|
602
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
588
603
|
else
|
589
604
|
status.to_i
|
590
605
|
end
|
591
606
|
end
|
592
|
-
module_function :status_code
|
593
607
|
|
594
608
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
595
609
|
|
@@ -603,18 +617,16 @@ module Rack
|
|
603
617
|
part == '..' ? clean.pop : clean << part
|
604
618
|
end
|
605
619
|
|
606
|
-
|
607
|
-
|
608
|
-
|
620
|
+
clean_path = clean.join(::File::SEPARATOR)
|
621
|
+
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
622
|
+
clean_path
|
609
623
|
end
|
610
|
-
module_function :clean_path_info
|
611
624
|
|
612
|
-
NULL_BYTE = "\0"
|
625
|
+
NULL_BYTE = "\0"
|
613
626
|
|
614
627
|
def valid_path?(path)
|
615
628
|
path.valid_encoding? && !path.include?(NULL_BYTE)
|
616
629
|
end
|
617
|
-
module_function :valid_path?
|
618
630
|
|
619
631
|
end
|
620
632
|
end
|
data/lib/rack/version.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
|
4
|
+
#
|
5
|
+
# Rack is freely distributable under the terms of an MIT-style license.
|
6
|
+
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
7
|
+
|
8
|
+
# The Rack main module, serving as a namespace for all core Rack
|
9
|
+
# modules and classes.
|
10
|
+
#
|
11
|
+
# All modules meant for use in your application are <tt>autoload</tt>ed here,
|
12
|
+
# so it should be enough just to <tt>require 'rack'</tt> in your code.
|
13
|
+
|
14
|
+
module Rack
|
15
|
+
# The Rack protocol version number implemented.
|
16
|
+
VERSION = [1, 3]
|
17
|
+
|
18
|
+
# Return the Rack protocol version as a dotted string.
|
19
|
+
def self.version
|
20
|
+
VERSION.join(".")
|
21
|
+
end
|
22
|
+
|
23
|
+
RELEASE = "2.2.17"
|
24
|
+
|
25
|
+
# Return the Rack release as a dotted string.
|
26
|
+
def self.release
|
27
|
+
RELEASE
|
28
|
+
end
|
29
|
+
end
|