rack 1.6.13 → 2.2.3
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 +694 -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 +6 -3
- 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 +553 -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 +141 -93
- data/lib/rack/session/cookie.rb +35 -29
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +13 -11
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +12 -12
- 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 +212 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- metadata +65 -187
- 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 -358
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -246
- 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,159 @@ 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
|
+
return {} unless header
|
216
|
+
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
217
|
+
next if cookie.empty?
|
218
|
+
key, value = cookie.split('=', 2)
|
219
|
+
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def add_cookie_to_header(header, key, value)
|
282
224
|
case value
|
283
225
|
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]
|
226
|
+
domain = "; domain=#{value[:domain]}" if value[:domain]
|
227
|
+
path = "; path=#{value[:path]}" if value[:path]
|
228
|
+
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
229
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
312
230
|
secure = "; secure" if value[:secure]
|
313
231
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
314
232
|
same_site =
|
315
233
|
case value[:same_site]
|
316
234
|
when false, nil
|
317
235
|
nil
|
236
|
+
when :none, 'None', :None
|
237
|
+
'; SameSite=None'
|
318
238
|
when :lax, 'Lax', :Lax
|
319
|
-
'; SameSite=Lax'
|
239
|
+
'; SameSite=Lax'
|
320
240
|
when true, :strict, 'Strict', :Strict
|
321
|
-
'; SameSite=Strict'
|
241
|
+
'; SameSite=Strict'
|
322
242
|
else
|
323
243
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
324
244
|
end
|
325
245
|
value = value[:value]
|
326
246
|
end
|
327
247
|
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
248
|
|
332
|
-
|
249
|
+
cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
250
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
251
|
+
|
252
|
+
case header
|
333
253
|
when nil, ''
|
334
|
-
|
254
|
+
cookie
|
335
255
|
when String
|
336
|
-
|
256
|
+
[header, cookie].join("\n")
|
337
257
|
when Array
|
338
|
-
|
258
|
+
(header + [cookie]).join("\n")
|
259
|
+
else
|
260
|
+
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
339
261
|
end
|
262
|
+
end
|
340
263
|
|
264
|
+
def set_cookie_header!(header, key, value)
|
265
|
+
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
341
266
|
nil
|
342
267
|
end
|
343
|
-
module_function :set_cookie_header!
|
344
268
|
|
345
|
-
def
|
346
|
-
case header
|
269
|
+
def make_delete_cookie_header(header, key, value)
|
270
|
+
case header
|
347
271
|
when nil, ''
|
348
272
|
cookies = []
|
349
273
|
when String
|
350
|
-
cookies = header
|
274
|
+
cookies = header.split("\n")
|
351
275
|
when Array
|
352
|
-
cookies = header
|
353
|
-
end
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
276
|
+
cookies = header
|
277
|
+
end
|
278
|
+
|
279
|
+
key = escape(key)
|
280
|
+
domain = value[:domain]
|
281
|
+
path = value[:path]
|
282
|
+
regexp = if domain
|
283
|
+
if path
|
284
|
+
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
285
|
+
else
|
286
|
+
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
287
|
+
end
|
288
|
+
elsif path
|
289
|
+
/\A#{key}=.*path=#{path}(?:;|$)/
|
290
|
+
else
|
291
|
+
/\A#{key}=/
|
292
|
+
end
|
293
|
+
|
294
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
295
|
+
|
296
|
+
cookies.join("\n")
|
297
|
+
end
|
371
298
|
|
299
|
+
def delete_cookie_header!(header, key, value = {})
|
300
|
+
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
372
301
|
nil
|
373
302
|
end
|
374
|
-
module_function :delete_cookie_header!
|
375
303
|
|
376
|
-
#
|
377
|
-
#
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
304
|
+
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
305
|
+
# strange method name.
|
306
|
+
def add_remove_cookie_to_header(header, key, value = {})
|
307
|
+
new_header = make_delete_cookie_header(header, key, value)
|
308
|
+
|
309
|
+
add_cookie_to_header(new_header, key,
|
310
|
+
{ value: '', path: nil, domain: nil,
|
311
|
+
max_age: '0',
|
312
|
+
expires: Time.at(0) }.merge(value))
|
313
|
+
|
386
314
|
end
|
387
|
-
module_function :bytesize
|
388
315
|
|
389
316
|
def rfc2822(time)
|
390
317
|
time.rfc2822
|
391
318
|
end
|
392
|
-
module_function :rfc2822
|
393
319
|
|
394
320
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
395
321
|
# of '% %b %Y'.
|
@@ -405,19 +331,22 @@ module Rack
|
|
405
331
|
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
406
332
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
407
333
|
end
|
408
|
-
module_function :rfc2109
|
409
334
|
|
410
335
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
411
336
|
# Returns nil if the header is missing or syntactically invalid.
|
412
337
|
# Returns an empty array if none of the ranges are satisfiable.
|
413
338
|
def byte_ranges(env, size)
|
339
|
+
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
340
|
+
get_byte_ranges env['HTTP_RANGE'], size
|
341
|
+
end
|
342
|
+
|
343
|
+
def get_byte_ranges(http_range, size)
|
414
344
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
415
|
-
http_range = env['HTTP_RANGE']
|
416
345
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
417
346
|
ranges = []
|
418
347
|
$1.split(/,\s*/).each do |range_spec|
|
419
348
|
return nil unless range_spec =~ /(\d*)-(\d*)/
|
420
|
-
r0,r1 = $1, $2
|
349
|
+
r0, r1 = $1, $2
|
421
350
|
if r0.empty?
|
422
351
|
return nil if r1.empty?
|
423
352
|
# suffix-byte-range-spec, represents trailing suffix of file
|
@@ -431,14 +360,13 @@ module Rack
|
|
431
360
|
else
|
432
361
|
r1 = r1.to_i
|
433
362
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
434
|
-
r1 = size-1 if r1 >= size
|
363
|
+
r1 = size - 1 if r1 >= size
|
435
364
|
end
|
436
365
|
end
|
437
366
|
ranges << (r0..r1) if r0 <= r1
|
438
367
|
end
|
439
368
|
ranges
|
440
369
|
end
|
441
|
-
module_function :byte_ranges
|
442
370
|
|
443
371
|
# Constant time string comparison.
|
444
372
|
#
|
@@ -447,15 +375,14 @@ module Rack
|
|
447
375
|
# on variable length plaintext strings because it could leak length info
|
448
376
|
# via timing attacks.
|
449
377
|
def secure_compare(a, b)
|
450
|
-
return false unless bytesize
|
378
|
+
return false unless a.bytesize == b.bytesize
|
451
379
|
|
452
380
|
l = a.unpack("C*")
|
453
381
|
|
454
382
|
r, i = 0, -1
|
455
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
383
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
456
384
|
r == 0
|
457
385
|
end
|
458
|
-
module_function :secure_compare
|
459
386
|
|
460
387
|
# Context allows the use of a compatible middleware at different points
|
461
388
|
# in a request handling stack. A compatible middleware must define
|
@@ -478,24 +405,42 @@ module Rack
|
|
478
405
|
self.class.new(@for, app)
|
479
406
|
end
|
480
407
|
|
481
|
-
def context(env, app
|
408
|
+
def context(env, app = @app)
|
482
409
|
recontext(app).call(env)
|
483
410
|
end
|
484
411
|
end
|
485
412
|
|
486
413
|
# A case-insensitive Hash that preserves the original case of a
|
487
414
|
# header when set.
|
488
|
-
|
489
|
-
|
490
|
-
|
415
|
+
#
|
416
|
+
# @api private
|
417
|
+
class HeaderHash < Hash # :nodoc:
|
418
|
+
def self.[](headers)
|
419
|
+
if headers.is_a?(HeaderHash) && !headers.frozen?
|
420
|
+
return headers
|
421
|
+
else
|
422
|
+
return self.new(headers)
|
423
|
+
end
|
491
424
|
end
|
492
425
|
|
493
|
-
def initialize(hash={})
|
426
|
+
def initialize(hash = {})
|
494
427
|
super()
|
495
428
|
@names = {}
|
496
429
|
hash.each { |k, v| self[k] = v }
|
497
430
|
end
|
498
431
|
|
432
|
+
# on dup/clone, we need to duplicate @names hash
|
433
|
+
def initialize_copy(other)
|
434
|
+
super
|
435
|
+
@names = other.names.dup
|
436
|
+
end
|
437
|
+
|
438
|
+
# on clear, we need to clear @names hash
|
439
|
+
def clear
|
440
|
+
super
|
441
|
+
@names.clear
|
442
|
+
end
|
443
|
+
|
499
444
|
def each
|
500
445
|
super do |k, v|
|
501
446
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -504,7 +449,7 @@ module Rack
|
|
504
449
|
|
505
450
|
def to_hash
|
506
451
|
hash = {}
|
507
|
-
each { |k,v| hash[k] = v }
|
452
|
+
each { |k, v| hash[k] = v }
|
508
453
|
hash
|
509
454
|
end
|
510
455
|
|
@@ -513,21 +458,20 @@ module Rack
|
|
513
458
|
end
|
514
459
|
|
515
460
|
def []=(k, v)
|
516
|
-
canonical = k.downcase
|
461
|
+
canonical = k.downcase.freeze
|
517
462
|
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
518
|
-
@names[
|
463
|
+
@names[canonical] = k
|
519
464
|
super k, v
|
520
465
|
end
|
521
466
|
|
522
467
|
def delete(k)
|
523
468
|
canonical = k.downcase
|
524
469
|
result = super @names.delete(canonical)
|
525
|
-
@names.delete_if { |name,| name.downcase == canonical }
|
526
470
|
result
|
527
471
|
end
|
528
472
|
|
529
473
|
def include?(k)
|
530
|
-
|
474
|
+
super || @names.include?(k.downcase)
|
531
475
|
end
|
532
476
|
|
533
477
|
alias_method :has_key?, :include?
|
@@ -549,56 +493,23 @@ module Rack
|
|
549
493
|
other.each { |k, v| self[k] = v }
|
550
494
|
self
|
551
495
|
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
496
|
|
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
|
497
|
+
protected
|
498
|
+
def names
|
499
|
+
@names
|
588
500
|
end
|
589
|
-
hash
|
590
|
-
end
|
591
501
|
end
|
592
502
|
|
593
503
|
# Every standard HTTP code mapped to the appropriate message.
|
594
504
|
# Generated with:
|
595
|
-
#
|
596
|
-
#
|
597
|
-
#
|
505
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
506
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
507
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
598
508
|
HTTP_STATUS_CODES = {
|
599
509
|
100 => 'Continue',
|
600
510
|
101 => 'Switching Protocols',
|
601
511
|
102 => 'Processing',
|
512
|
+
103 => 'Early Hints',
|
602
513
|
200 => 'OK',
|
603
514
|
201 => 'Created',
|
604
515
|
202 => 'Accepted',
|
@@ -615,6 +526,7 @@ module Rack
|
|
615
526
|
303 => 'See Other',
|
616
527
|
304 => 'Not Modified',
|
617
528
|
305 => 'Use Proxy',
|
529
|
+
306 => '(Unused)',
|
618
530
|
307 => 'Temporary Redirect',
|
619
531
|
308 => 'Permanent Redirect',
|
620
532
|
400 => 'Bad Request',
|
@@ -635,13 +547,16 @@ module Rack
|
|
635
547
|
415 => 'Unsupported Media Type',
|
636
548
|
416 => 'Range Not Satisfiable',
|
637
549
|
417 => 'Expectation Failed',
|
550
|
+
421 => 'Misdirected Request',
|
638
551
|
422 => 'Unprocessable Entity',
|
639
552
|
423 => 'Locked',
|
640
553
|
424 => 'Failed Dependency',
|
554
|
+
425 => 'Too Early',
|
641
555
|
426 => 'Upgrade Required',
|
642
556
|
428 => 'Precondition Required',
|
643
557
|
429 => 'Too Many Requests',
|
644
558
|
431 => 'Request Header Fields Too Large',
|
559
|
+
451 => 'Unavailable for Legal Reasons',
|
645
560
|
500 => 'Internal Server Error',
|
646
561
|
501 => 'Not Implemented',
|
647
562
|
502 => 'Bad Gateway',
|
@@ -651,12 +566,13 @@ module Rack
|
|
651
566
|
506 => 'Variant Also Negotiates',
|
652
567
|
507 => 'Insufficient Storage',
|
653
568
|
508 => 'Loop Detected',
|
569
|
+
509 => 'Bandwidth Limit Exceeded',
|
654
570
|
510 => 'Not Extended',
|
655
571
|
511 => 'Network Authentication Required'
|
656
572
|
}
|
657
573
|
|
658
574
|
# Responses with HTTP status codes that should not have an entity body
|
659
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
575
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
660
576
|
|
661
577
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
662
578
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -664,14 +580,11 @@ module Rack
|
|
664
580
|
|
665
581
|
def status_code(status)
|
666
582
|
if status.is_a?(Symbol)
|
667
|
-
SYMBOL_TO_STATUS_CODE
|
583
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
668
584
|
else
|
669
585
|
status.to_i
|
670
586
|
end
|
671
587
|
end
|
672
|
-
module_function :status_code
|
673
|
-
|
674
|
-
Multipart = Rack::Multipart
|
675
588
|
|
676
589
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
677
590
|
|
@@ -685,11 +598,16 @@ module Rack
|
|
685
598
|
part == '..' ? clean.pop : clean << part
|
686
599
|
end
|
687
600
|
|
688
|
-
|
601
|
+
clean_path = clean.join(::File::SEPARATOR)
|
602
|
+
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
603
|
+
clean_path
|
604
|
+
end
|
605
|
+
|
606
|
+
NULL_BYTE = "\0"
|
689
607
|
|
690
|
-
|
608
|
+
def valid_path?(path)
|
609
|
+
path.valid_encoding? && !path.include?(NULL_BYTE)
|
691
610
|
end
|
692
|
-
module_function :clean_path_info
|
693
611
|
|
694
612
|
end
|
695
613
|
end
|