rack 1.6.11 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +675 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +157 -163
- data/Rakefile +38 -32
- data/{SPEC → SPEC.rdoc} +41 -13
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +6 -2
- 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 +5 -4
- data/lib/rack/auth/digest/request.rb +6 -4
- data/lib/rack/body_proxy.rb +21 -15
- data/lib/rack/builder.rb +119 -26
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +70 -22
- data/lib/rack/common_logger.rb +80 -0
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +9 -8
- 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 +117 -85
- data/lib/rack/etag.rb +9 -7
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +17 -19
- data/lib/rack/handler/fastcgi.rb +17 -18
- data/lib/rack/handler/lsws.rb +14 -14
- data/lib/rack/handler/scgi.rb +22 -21
- data/lib/rack/handler/thin.rb +20 -11
- data/lib/rack/handler/webrick.rb +39 -32
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +16 -18
- data/lib/rack/lint.rb +110 -64
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +17 -11
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +124 -65
- data/lib/rack/multipart/generator.rb +20 -16
- data/lib/rack/multipart/parser.rb +273 -162
- data/lib/rack/multipart/uploaded_file.rb +15 -8
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +217 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +543 -305
- data/lib/rack/response.rb +244 -88
- data/lib/rack/rewindable_input.rb +5 -15
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +17 -15
- data/lib/rack/server.rb +125 -47
- data/lib/rack/session/abstract/id.rb +216 -93
- data/lib/rack/session/cookie.rb +47 -31
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +26 -17
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +8 -8
- data/lib/rack/static.rb +48 -11
- data/lib/rack/tempfile_reaper.rb +3 -3
- data/lib/rack/urlmap.rb +26 -19
- data/lib/rack/utils.rb +208 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- metadata +62 -183
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/commonlogger.rb +0 -72
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- 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 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- 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_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_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/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- 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 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.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,176 +1,103 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'uri'
|
2
5
|
require 'fileutils'
|
3
6
|
require 'set'
|
4
7
|
require 'tempfile'
|
5
|
-
require 'rack/multipart'
|
6
8
|
require 'time'
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
if major == 1 && minor < 9
|
11
|
-
require 'rack/backports/uri/common_18'
|
12
|
-
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
|
13
|
-
require 'rack/backports/uri/common_192'
|
14
|
-
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
|
15
|
-
require 'rack/backports/uri/common_193'
|
16
|
-
else
|
17
|
-
require 'uri/common'
|
18
|
-
end
|
10
|
+
require_relative 'query_parser'
|
19
11
|
|
20
12
|
module Rack
|
21
13
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
22
14
|
# applications adopted from all kinds of Ruby libraries.
|
23
15
|
|
24
16
|
module Utils
|
25
|
-
|
26
|
-
# parameters (parsed by parse_nested_query) contain conflicting types.
|
27
|
-
class ParameterTypeError < TypeError; end
|
17
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
ParameterTypeError = QueryParser::ParameterTypeError
|
20
|
+
InvalidParameterError = QueryParser::InvalidParameterError
|
21
|
+
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
22
|
+
COMMON_SEP = QueryParser::COMMON_SEP
|
23
|
+
KeySpaceConstrainedParams = QueryParser::Params
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :default_query_parser
|
27
|
+
end
|
28
|
+
# The default number of bytes to allow parameter keys to take up.
|
29
|
+
# This helps prevent a rogue client from flooding a Request.
|
30
|
+
self.default_query_parser = QueryParser.make_default(65536, 100)
|
31
|
+
|
32
|
+
module_function
|
33
33
|
|
34
34
|
# URI escapes. (CGI style space to +)
|
35
35
|
def escape(s)
|
36
36
|
URI.encode_www_form_component(s)
|
37
37
|
end
|
38
|
-
module_function :escape
|
39
38
|
|
40
39
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
41
40
|
# true URI escaping.
|
42
41
|
def escape_path(s)
|
43
|
-
escape
|
42
|
+
::URI::DEFAULT_PARSER.escape s
|
43
|
+
end
|
44
|
+
|
45
|
+
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
46
|
+
# unescaping query parameters or form components.
|
47
|
+
def unescape_path(s)
|
48
|
+
::URI::DEFAULT_PARSER.unescape s
|
44
49
|
end
|
45
|
-
module_function :escape_path
|
46
50
|
|
47
51
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
48
52
|
# target encoding of the string returned, and it defaults to UTF-8
|
49
|
-
|
50
|
-
|
51
|
-
URI.decode_www_form_component(s, encoding)
|
52
|
-
end
|
53
|
-
else
|
54
|
-
def unescape(s, encoding = nil)
|
55
|
-
URI.decode_www_form_component(s, encoding)
|
56
|
-
end
|
53
|
+
def unescape(s, encoding = Encoding::UTF_8)
|
54
|
+
URI.decode_www_form_component(s, encoding)
|
57
55
|
end
|
58
|
-
module_function :unescape
|
59
|
-
|
60
|
-
DEFAULT_SEP = /[&;] */n
|
61
56
|
|
62
57
|
class << self
|
63
|
-
attr_accessor :key_space_limit
|
64
|
-
attr_accessor :param_depth_limit
|
65
58
|
attr_accessor :multipart_part_limit
|
66
59
|
end
|
67
60
|
|
68
|
-
# The default number of bytes to allow parameter keys to take up.
|
69
|
-
# This helps prevent a rogue client from flooding a Request.
|
70
|
-
self.key_space_limit = 65536
|
71
|
-
|
72
|
-
# Default depth at which the parameter parser will raise an exception for
|
73
|
-
# being too deep. This helps prevent SystemStackErrors
|
74
|
-
self.param_depth_limit = 100
|
75
|
-
|
76
61
|
# The maximum number of parts a request can contain. Accepting too many part
|
77
62
|
# can lead to the server running out of file handles.
|
78
63
|
# Set to `0` for no limit.
|
79
|
-
|
80
|
-
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
|
81
|
-
|
82
|
-
# Stolen from Mongrel, with some small modifications:
|
83
|
-
# Parses a query string by breaking it up at the '&'
|
84
|
-
# and ';' characters. You can also use this to parse
|
85
|
-
# cookies by changing the characters used in the second
|
86
|
-
# parameter (which defaults to '&;').
|
87
|
-
def parse_query(qs, d = nil, &unescaper)
|
88
|
-
unescaper ||= method(:unescape)
|
89
|
-
|
90
|
-
params = KeySpaceConstrainedParams.new
|
91
|
-
|
92
|
-
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
93
|
-
next if p.empty?
|
94
|
-
k, v = p.split('=', 2).map(&unescaper)
|
64
|
+
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
95
65
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
else
|
100
|
-
params[k] = [cur, v]
|
101
|
-
end
|
102
|
-
else
|
103
|
-
params[k] = v
|
104
|
-
end
|
105
|
-
end
|
66
|
+
def self.param_depth_limit
|
67
|
+
default_query_parser.param_depth_limit
|
68
|
+
end
|
106
69
|
|
107
|
-
|
70
|
+
def self.param_depth_limit=(v)
|
71
|
+
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
108
72
|
end
|
109
|
-
module_function :parse_query
|
110
73
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
115
|
-
# case.
|
116
|
-
def parse_nested_query(qs, d = nil)
|
117
|
-
params = KeySpaceConstrainedParams.new
|
74
|
+
def self.key_space_limit
|
75
|
+
default_query_parser.key_space_limit
|
76
|
+
end
|
118
77
|
|
119
|
-
|
120
|
-
|
78
|
+
def self.key_space_limit=(v)
|
79
|
+
self.default_query_parser = self.default_query_parser.new_space_limit(v)
|
80
|
+
end
|
121
81
|
|
122
|
-
|
82
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
83
|
+
def clock_time
|
84
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
123
85
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
module_function :parse_nested_query
|
130
|
-
|
131
|
-
# normalize_params recursively expands parameters into structural types. If
|
132
|
-
# the structural types represented by two different parameter names are in
|
133
|
-
# conflict, a ParameterTypeError is raised.
|
134
|
-
def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
|
135
|
-
raise RangeError if depth <= 0
|
136
|
-
|
137
|
-
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
138
|
-
k = $1 || ''
|
139
|
-
after = $' || ''
|
140
|
-
|
141
|
-
return if k.empty?
|
142
|
-
|
143
|
-
if after == ""
|
144
|
-
params[k] = v
|
145
|
-
elsif after == "["
|
146
|
-
params[name] = v
|
147
|
-
elsif after == "[]"
|
148
|
-
params[k] ||= []
|
149
|
-
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
150
|
-
params[k] << v
|
151
|
-
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
152
|
-
child_key = $1
|
153
|
-
params[k] ||= []
|
154
|
-
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
155
|
-
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
|
156
|
-
normalize_params(params[k].last, child_key, v, depth - 1)
|
157
|
-
else
|
158
|
-
params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
|
159
|
-
end
|
160
|
-
else
|
161
|
-
params[k] ||= params.class.new
|
162
|
-
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
163
|
-
params[k] = normalize_params(params[k], after, v, depth - 1)
|
86
|
+
else
|
87
|
+
# :nocov:
|
88
|
+
def clock_time
|
89
|
+
Time.now.to_f
|
164
90
|
end
|
91
|
+
# :nocov:
|
92
|
+
end
|
165
93
|
|
166
|
-
|
94
|
+
def parse_query(qs, d = nil, &unescaper)
|
95
|
+
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
167
96
|
end
|
168
|
-
module_function :normalize_params
|
169
97
|
|
170
|
-
def
|
171
|
-
|
98
|
+
def parse_nested_query(qs, d = nil)
|
99
|
+
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
172
100
|
end
|
173
|
-
module_function :params_hash_type?
|
174
101
|
|
175
102
|
def build_query(params)
|
176
103
|
params.map { |k, v|
|
@@ -181,7 +108,6 @@ module Rack
|
|
181
108
|
end
|
182
109
|
}.join("&")
|
183
110
|
end
|
184
|
-
module_function :build_query
|
185
111
|
|
186
112
|
def build_nested_query(value, prefix = nil)
|
187
113
|
case value
|
@@ -192,7 +118,7 @@ module Rack
|
|
192
118
|
when Hash
|
193
119
|
value.map { |k, v|
|
194
120
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
195
|
-
}.
|
121
|
+
}.delete_if(&:empty?).join('&')
|
196
122
|
when nil
|
197
123
|
prefix
|
198
124
|
else
|
@@ -200,20 +126,22 @@ module Rack
|
|
200
126
|
"#{prefix}=#{escape(value)}"
|
201
127
|
end
|
202
128
|
end
|
203
|
-
module_function :build_nested_query
|
204
129
|
|
205
130
|
def q_values(q_value_header)
|
206
131
|
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
207
132
|
value, parameters = part.split(/\s*;\s*/, 2)
|
208
133
|
quality = 1.0
|
209
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
134
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
210
135
|
quality = md[1].to_f
|
211
136
|
end
|
212
137
|
[value, quality]
|
213
138
|
end
|
214
139
|
end
|
215
|
-
module_function :q_values
|
216
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.
|
217
145
|
def best_q_match(q_value_header, available_mimes)
|
218
146
|
values = q_values(q_value_header)
|
219
147
|
|
@@ -226,7 +154,6 @@ module Rack
|
|
226
154
|
end.last
|
227
155
|
matches && matches.first
|
228
156
|
end
|
229
|
-
module_function :best_q_match
|
230
157
|
|
231
158
|
ESCAPE_HTML = {
|
232
159
|
"&" => "&",
|
@@ -236,160 +163,155 @@ module Rack
|
|
236
163
|
'"' => """,
|
237
164
|
"/" => "/"
|
238
165
|
}
|
239
|
-
|
240
|
-
|
241
|
-
else
|
242
|
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
|
243
|
-
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
244
|
-
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
245
|
-
end
|
166
|
+
|
167
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
246
168
|
|
247
169
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
248
170
|
def escape_html(string)
|
249
171
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
250
172
|
end
|
251
|
-
module_function :escape_html
|
252
173
|
|
253
174
|
def select_best_encoding(available_encodings, accept_encoding)
|
254
175
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
255
176
|
|
256
|
-
expanded_accept_encoding =
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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]
|
262
185
|
end
|
263
|
-
|
264
|
-
|
265
|
-
|
186
|
+
else
|
187
|
+
expanded_accept_encoding << [m, q, preference]
|
188
|
+
end
|
189
|
+
end
|
266
190
|
|
267
|
-
encoding_candidates = expanded_accept_encoding
|
191
|
+
encoding_candidates = expanded_accept_encoding
|
192
|
+
.sort_by { |_, q, p| [-q, p] }
|
193
|
+
.map!(&:first)
|
268
194
|
|
269
195
|
unless encoding_candidates.include?("identity")
|
270
196
|
encoding_candidates.push("identity")
|
271
197
|
end
|
272
198
|
|
273
|
-
expanded_accept_encoding.each
|
199
|
+
expanded_accept_encoding.each do |m, q|
|
274
200
|
encoding_candidates.delete(m) if q == 0.0
|
275
|
-
|
201
|
+
end
|
276
202
|
|
277
|
-
|
203
|
+
(encoding_candidates & available_encodings)[0]
|
278
204
|
end
|
279
|
-
module_function :select_best_encoding
|
280
205
|
|
281
|
-
def
|
206
|
+
def parse_cookies(env)
|
207
|
+
parse_cookies_header env[HTTP_COOKIE]
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse_cookies_header(header)
|
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 }
|
217
|
+
end
|
218
|
+
|
219
|
+
def add_cookie_to_header(header, key, value)
|
282
220
|
case value
|
283
221
|
when Hash
|
284
|
-
domain = "; domain
|
285
|
-
path = "; path
|
286
|
-
max_age = "; max-age
|
287
|
-
|
288
|
-
# only are there contradicting RFCs and examples within RFC text, but
|
289
|
-
# there are also numerous conflicting names of fields and partially
|
290
|
-
# cross-applicable specifications.
|
291
|
-
#
|
292
|
-
# These are best described in RFC 2616 3.3.1. This RFC text also
|
293
|
-
# specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
|
294
|
-
# fixed length format with space-date delimeted fields.
|
295
|
-
#
|
296
|
-
# See also RFC 1123 section 5.2.14.
|
297
|
-
#
|
298
|
-
# RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
|
299
|
-
# in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
|
300
|
-
# the space delimited format. These formats are compliant with RFC 2822.
|
301
|
-
#
|
302
|
-
# For reference, all involved RFCs are:
|
303
|
-
# RFC 822
|
304
|
-
# RFC 1123
|
305
|
-
# RFC 2109
|
306
|
-
# RFC 2616
|
307
|
-
# RFC 2822
|
308
|
-
# RFC 2965
|
309
|
-
# RFC 6265
|
310
|
-
expires = "; expires=" +
|
311
|
-
rfc2822(value[:expires].clone.gmtime) if value[:expires]
|
222
|
+
domain = "; domain=#{value[:domain]}" if value[:domain]
|
223
|
+
path = "; path=#{value[:path]}" if value[:path]
|
224
|
+
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
225
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
312
226
|
secure = "; secure" if value[:secure]
|
313
227
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
314
228
|
same_site =
|
315
229
|
case value[:same_site]
|
316
230
|
when false, nil
|
317
231
|
nil
|
232
|
+
when :none, 'None', :None
|
233
|
+
'; SameSite=None'
|
318
234
|
when :lax, 'Lax', :Lax
|
319
|
-
'; SameSite=Lax'
|
235
|
+
'; SameSite=Lax'
|
320
236
|
when true, :strict, 'Strict', :Strict
|
321
|
-
'; SameSite=Strict'
|
237
|
+
'; SameSite=Strict'
|
322
238
|
else
|
323
239
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
324
240
|
end
|
325
241
|
value = value[:value]
|
326
242
|
end
|
327
243
|
value = [value] unless Array === value
|
328
|
-
cookie = escape(key) + "=" +
|
329
|
-
value.map { |v| escape v }.join("&") +
|
330
|
-
"#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
331
244
|
|
332
|
-
|
245
|
+
cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
246
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
247
|
+
|
248
|
+
case header
|
333
249
|
when nil, ''
|
334
|
-
|
250
|
+
cookie
|
335
251
|
when String
|
336
|
-
|
252
|
+
[header, cookie].join("\n")
|
337
253
|
when Array
|
338
|
-
|
254
|
+
(header + [cookie]).join("\n")
|
255
|
+
else
|
256
|
+
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
339
257
|
end
|
258
|
+
end
|
340
259
|
|
260
|
+
def set_cookie_header!(header, key, value)
|
261
|
+
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
341
262
|
nil
|
342
263
|
end
|
343
|
-
module_function :set_cookie_header!
|
344
264
|
|
345
|
-
def
|
346
|
-
case header
|
265
|
+
def make_delete_cookie_header(header, key, value)
|
266
|
+
case header
|
347
267
|
when nil, ''
|
348
268
|
cookies = []
|
349
269
|
when String
|
350
|
-
cookies = header
|
270
|
+
cookies = header.split("\n")
|
351
271
|
when Array
|
352
|
-
cookies = header
|
353
|
-
end
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
272
|
+
cookies = header
|
273
|
+
end
|
274
|
+
|
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 }
|
291
|
+
|
292
|
+
cookies.join("\n")
|
293
|
+
end
|
371
294
|
|
295
|
+
def delete_cookie_header!(header, key, value = {})
|
296
|
+
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
372
297
|
nil
|
373
298
|
end
|
374
|
-
module_function :delete_cookie_header!
|
375
299
|
|
376
|
-
#
|
377
|
-
#
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
300
|
+
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
301
|
+
# strange method name.
|
302
|
+
def add_remove_cookie_to_header(header, key, value = {})
|
303
|
+
new_header = make_delete_cookie_header(header, key, value)
|
304
|
+
|
305
|
+
add_cookie_to_header(new_header, key,
|
306
|
+
{ value: '', path: nil, domain: nil,
|
307
|
+
max_age: '0',
|
308
|
+
expires: Time.at(0) }.merge(value))
|
309
|
+
|
386
310
|
end
|
387
|
-
module_function :bytesize
|
388
311
|
|
389
312
|
def rfc2822(time)
|
390
313
|
time.rfc2822
|
391
314
|
end
|
392
|
-
module_function :rfc2822
|
393
315
|
|
394
316
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
395
317
|
# of '% %b %Y'.
|
@@ -405,19 +327,22 @@ module Rack
|
|
405
327
|
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
406
328
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
407
329
|
end
|
408
|
-
module_function :rfc2109
|
409
330
|
|
410
331
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
411
332
|
# Returns nil if the header is missing or syntactically invalid.
|
412
333
|
# Returns an empty array if none of the ranges are satisfiable.
|
413
334
|
def byte_ranges(env, size)
|
335
|
+
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
336
|
+
get_byte_ranges env['HTTP_RANGE'], size
|
337
|
+
end
|
338
|
+
|
339
|
+
def get_byte_ranges(http_range, size)
|
414
340
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
415
|
-
http_range = env['HTTP_RANGE']
|
416
341
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
417
342
|
ranges = []
|
418
343
|
$1.split(/,\s*/).each do |range_spec|
|
419
344
|
return nil unless range_spec =~ /(\d*)-(\d*)/
|
420
|
-
r0,r1 = $1, $2
|
345
|
+
r0, r1 = $1, $2
|
421
346
|
if r0.empty?
|
422
347
|
return nil if r1.empty?
|
423
348
|
# suffix-byte-range-spec, represents trailing suffix of file
|
@@ -431,14 +356,13 @@ module Rack
|
|
431
356
|
else
|
432
357
|
r1 = r1.to_i
|
433
358
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
434
|
-
r1 = size-1 if r1 >= size
|
359
|
+
r1 = size - 1 if r1 >= size
|
435
360
|
end
|
436
361
|
end
|
437
362
|
ranges << (r0..r1) if r0 <= r1
|
438
363
|
end
|
439
364
|
ranges
|
440
365
|
end
|
441
|
-
module_function :byte_ranges
|
442
366
|
|
443
367
|
# Constant time string comparison.
|
444
368
|
#
|
@@ -447,15 +371,14 @@ module Rack
|
|
447
371
|
# on variable length plaintext strings because it could leak length info
|
448
372
|
# via timing attacks.
|
449
373
|
def secure_compare(a, b)
|
450
|
-
return false unless bytesize
|
374
|
+
return false unless a.bytesize == b.bytesize
|
451
375
|
|
452
376
|
l = a.unpack("C*")
|
453
377
|
|
454
378
|
r, i = 0, -1
|
455
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
379
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
456
380
|
r == 0
|
457
381
|
end
|
458
|
-
module_function :secure_compare
|
459
382
|
|
460
383
|
# Context allows the use of a compatible middleware at different points
|
461
384
|
# in a request handling stack. A compatible middleware must define
|
@@ -478,24 +401,42 @@ module Rack
|
|
478
401
|
self.class.new(@for, app)
|
479
402
|
end
|
480
403
|
|
481
|
-
def context(env, app
|
404
|
+
def context(env, app = @app)
|
482
405
|
recontext(app).call(env)
|
483
406
|
end
|
484
407
|
end
|
485
408
|
|
486
409
|
# A case-insensitive Hash that preserves the original case of a
|
487
410
|
# header when set.
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
491
420
|
end
|
492
421
|
|
493
|
-
def initialize(hash={})
|
422
|
+
def initialize(hash = {})
|
494
423
|
super()
|
495
424
|
@names = {}
|
496
425
|
hash.each { |k, v| self[k] = v }
|
497
426
|
end
|
498
427
|
|
428
|
+
# on dup/clone, we need to duplicate @names hash
|
429
|
+
def initialize_copy(other)
|
430
|
+
super
|
431
|
+
@names = other.names.dup
|
432
|
+
end
|
433
|
+
|
434
|
+
# on clear, we need to clear @names hash
|
435
|
+
def clear
|
436
|
+
super
|
437
|
+
@names.clear
|
438
|
+
end
|
439
|
+
|
499
440
|
def each
|
500
441
|
super do |k, v|
|
501
442
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -504,7 +445,7 @@ module Rack
|
|
504
445
|
|
505
446
|
def to_hash
|
506
447
|
hash = {}
|
507
|
-
each { |k,v| hash[k] = v }
|
448
|
+
each { |k, v| hash[k] = v }
|
508
449
|
hash
|
509
450
|
end
|
510
451
|
|
@@ -513,21 +454,20 @@ module Rack
|
|
513
454
|
end
|
514
455
|
|
515
456
|
def []=(k, v)
|
516
|
-
canonical = k.downcase
|
457
|
+
canonical = k.downcase.freeze
|
517
458
|
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
518
|
-
@names[
|
459
|
+
@names[canonical] = k
|
519
460
|
super k, v
|
520
461
|
end
|
521
462
|
|
522
463
|
def delete(k)
|
523
464
|
canonical = k.downcase
|
524
465
|
result = super @names.delete(canonical)
|
525
|
-
@names.delete_if { |name,| name.downcase == canonical }
|
526
466
|
result
|
527
467
|
end
|
528
468
|
|
529
469
|
def include?(k)
|
530
|
-
|
470
|
+
super || @names.include?(k.downcase)
|
531
471
|
end
|
532
472
|
|
533
473
|
alias_method :has_key?, :include?
|
@@ -549,56 +489,23 @@ module Rack
|
|
549
489
|
other.each { |k, v| self[k] = v }
|
550
490
|
self
|
551
491
|
end
|
552
|
-
end
|
553
|
-
|
554
|
-
class KeySpaceConstrainedParams
|
555
|
-
def initialize(limit = Utils.key_space_limit)
|
556
|
-
@limit = limit
|
557
|
-
@size = 0
|
558
|
-
@params = {}
|
559
|
-
end
|
560
492
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
def []=(key, value)
|
566
|
-
@size += key.size if key && !@params.key?(key)
|
567
|
-
raise RangeError, 'exceeded available parameter key space' if @size > @limit
|
568
|
-
@params[key] = value
|
569
|
-
end
|
570
|
-
|
571
|
-
def key?(key)
|
572
|
-
@params.key?(key)
|
573
|
-
end
|
574
|
-
|
575
|
-
def to_params_hash
|
576
|
-
hash = @params
|
577
|
-
hash.keys.each do |key|
|
578
|
-
value = hash[key]
|
579
|
-
if value.kind_of?(self.class)
|
580
|
-
if value.object_id == self.object_id
|
581
|
-
hash[key] = hash
|
582
|
-
else
|
583
|
-
hash[key] = value.to_params_hash
|
584
|
-
end
|
585
|
-
elsif value.kind_of?(Array)
|
586
|
-
value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
|
587
|
-
end
|
493
|
+
protected
|
494
|
+
def names
|
495
|
+
@names
|
588
496
|
end
|
589
|
-
hash
|
590
|
-
end
|
591
497
|
end
|
592
498
|
|
593
499
|
# Every standard HTTP code mapped to the appropriate message.
|
594
500
|
# Generated with:
|
595
|
-
#
|
596
|
-
#
|
597
|
-
#
|
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,"'
|
598
504
|
HTTP_STATUS_CODES = {
|
599
505
|
100 => 'Continue',
|
600
506
|
101 => 'Switching Protocols',
|
601
507
|
102 => 'Processing',
|
508
|
+
103 => 'Early Hints',
|
602
509
|
200 => 'OK',
|
603
510
|
201 => 'Created',
|
604
511
|
202 => 'Accepted',
|
@@ -615,6 +522,7 @@ module Rack
|
|
615
522
|
303 => 'See Other',
|
616
523
|
304 => 'Not Modified',
|
617
524
|
305 => 'Use Proxy',
|
525
|
+
306 => '(Unused)',
|
618
526
|
307 => 'Temporary Redirect',
|
619
527
|
308 => 'Permanent Redirect',
|
620
528
|
400 => 'Bad Request',
|
@@ -635,13 +543,16 @@ module Rack
|
|
635
543
|
415 => 'Unsupported Media Type',
|
636
544
|
416 => 'Range Not Satisfiable',
|
637
545
|
417 => 'Expectation Failed',
|
546
|
+
421 => 'Misdirected Request',
|
638
547
|
422 => 'Unprocessable Entity',
|
639
548
|
423 => 'Locked',
|
640
549
|
424 => 'Failed Dependency',
|
550
|
+
425 => 'Too Early',
|
641
551
|
426 => 'Upgrade Required',
|
642
552
|
428 => 'Precondition Required',
|
643
553
|
429 => 'Too Many Requests',
|
644
554
|
431 => 'Request Header Fields Too Large',
|
555
|
+
451 => 'Unavailable for Legal Reasons',
|
645
556
|
500 => 'Internal Server Error',
|
646
557
|
501 => 'Not Implemented',
|
647
558
|
502 => 'Bad Gateway',
|
@@ -651,12 +562,13 @@ module Rack
|
|
651
562
|
506 => 'Variant Also Negotiates',
|
652
563
|
507 => 'Insufficient Storage',
|
653
564
|
508 => 'Loop Detected',
|
565
|
+
509 => 'Bandwidth Limit Exceeded',
|
654
566
|
510 => 'Not Extended',
|
655
567
|
511 => 'Network Authentication Required'
|
656
568
|
}
|
657
569
|
|
658
570
|
# Responses with HTTP status codes that should not have an entity body
|
659
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
571
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
660
572
|
|
661
573
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
662
574
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -664,14 +576,11 @@ module Rack
|
|
664
576
|
|
665
577
|
def status_code(status)
|
666
578
|
if status.is_a?(Symbol)
|
667
|
-
SYMBOL_TO_STATUS_CODE
|
579
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
668
580
|
else
|
669
581
|
status.to_i
|
670
582
|
end
|
671
583
|
end
|
672
|
-
module_function :status_code
|
673
|
-
|
674
|
-
Multipart = Rack::Multipart
|
675
584
|
|
676
585
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
677
586
|
|
@@ -685,11 +594,16 @@ module Rack
|
|
685
594
|
part == '..' ? clean.pop : clean << part
|
686
595
|
end
|
687
596
|
|
688
|
-
|
597
|
+
clean_path = clean.join(::File::SEPARATOR)
|
598
|
+
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
599
|
+
clean_path
|
600
|
+
end
|
601
|
+
|
602
|
+
NULL_BYTE = "\0"
|
689
603
|
|
690
|
-
|
604
|
+
def valid_path?(path)
|
605
|
+
path.valid_encoding? && !path.include?(NULL_BYTE)
|
691
606
|
end
|
692
|
-
module_function :clean_path_info
|
693
607
|
|
694
608
|
end
|
695
609
|
end
|