rack 1.6.11 → 2.1.4
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 +77 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +89 -139
- data/Rakefile +27 -28
- data/SPEC +6 -7
- 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 +7 -1
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- 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 +3 -1
- data/lib/rack/body_proxy.rb +11 -9
- data/lib/rack/builder.rb +42 -18
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +33 -10
- data/lib/rack/{commonlogger.rb → common_logger.rb} +11 -10
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +5 -3
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +33 -53
- data/lib/rack/directory.rb +75 -60
- data/lib/rack/etag.rb +8 -5
- data/lib/rack/events.rb +156 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +18 -17
- data/lib/rack/handler/fastcgi.rb +17 -16
- data/lib/rack/handler/lsws.rb +14 -12
- data/lib/rack/handler/scgi.rb +22 -19
- data/lib/rack/handler/thin.rb +6 -1
- data/lib/rack/handler/webrick.rb +28 -28
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +17 -17
- data/lib/rack/lint.rb +54 -51
- data/lib/rack/lobster.rb +8 -6
- data/lib/rack/lock.rb +17 -10
- 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 +101 -60
- data/lib/rack/multipart/generator.rb +11 -12
- data/lib/rack/multipart/parser.rb +280 -161
- data/lib/rack/multipart/uploaded_file.rb +3 -2
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +218 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +447 -305
- data/lib/rack/response.rb +196 -83
- data/lib/rack/rewindable_input.rb +5 -14
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +19 -14
- data/lib/rack/server.rb +118 -41
- data/lib/rack/session/abstract/id.rb +215 -94
- data/lib/rack/session/cookie.rb +45 -28
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +25 -16
- data/lib/rack/show_exceptions.rb +392 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
- data/lib/rack/static.rb +41 -11
- data/lib/rack/tempfile_reaper.rb +4 -2
- data/lib/rack/urlmap.rb +25 -15
- data/lib/rack/utils.rb +186 -272
- data/lib/rack.rb +76 -24
- data/rack.gemspec +25 -14
- metadata +62 -182
- 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/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,35 +1,34 @@
|
|
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/
|
8
|
+
require 'rack/query_parser'
|
6
9
|
require 'time'
|
7
10
|
|
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
|
11
|
+
require_relative 'core_ext/regexp'
|
19
12
|
|
20
13
|
module Rack
|
21
14
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
22
15
|
# applications adopted from all kinds of Ruby libraries.
|
23
16
|
|
24
17
|
module Utils
|
25
|
-
|
26
|
-
# parameters (parsed by parse_nested_query) contain conflicting types.
|
27
|
-
class ParameterTypeError < TypeError; end
|
18
|
+
using ::Rack::RegexpExtensions
|
28
19
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
ParameterTypeError = QueryParser::ParameterTypeError
|
21
|
+
InvalidParameterError = QueryParser::InvalidParameterError
|
22
|
+
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
23
|
+
COMMON_SEP = QueryParser::COMMON_SEP
|
24
|
+
KeySpaceConstrainedParams = QueryParser::Params
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :default_query_parser
|
28
|
+
end
|
29
|
+
# The default number of bytes to allow parameter keys to take up.
|
30
|
+
# This helps prevent a rogue client from flooding a Request.
|
31
|
+
self.default_query_parser = QueryParser.make_default(65536, 100)
|
33
32
|
|
34
33
|
# URI escapes. (CGI style space to +)
|
35
34
|
def escape(s)
|
@@ -40,137 +39,70 @@ module Rack
|
|
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
|
44
43
|
end
|
45
44
|
module_function :escape_path
|
46
45
|
|
46
|
+
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
47
|
+
# unescaping query parameters or form components.
|
48
|
+
def unescape_path(s)
|
49
|
+
::URI::DEFAULT_PARSER.unescape s
|
50
|
+
end
|
51
|
+
module_function :unescape_path
|
52
|
+
|
53
|
+
|
47
54
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
48
55
|
# 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
|
56
|
+
def unescape(s, encoding = Encoding::UTF_8)
|
57
|
+
URI.decode_www_form_component(s, encoding)
|
57
58
|
end
|
58
59
|
module_function :unescape
|
59
60
|
|
60
|
-
DEFAULT_SEP = /[&;] */n
|
61
|
-
|
62
61
|
class << self
|
63
|
-
attr_accessor :key_space_limit
|
64
|
-
attr_accessor :param_depth_limit
|
65
62
|
attr_accessor :multipart_part_limit
|
66
63
|
end
|
67
64
|
|
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
65
|
# The maximum number of parts a request can contain. Accepting too many part
|
77
66
|
# can lead to the server running out of file handles.
|
78
67
|
# 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)
|
95
|
-
|
96
|
-
if cur = params[k]
|
97
|
-
if cur.class == Array
|
98
|
-
params[k] << v
|
99
|
-
else
|
100
|
-
params[k] = [cur, v]
|
101
|
-
end
|
102
|
-
else
|
103
|
-
params[k] = v
|
104
|
-
end
|
105
|
-
end
|
68
|
+
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
106
69
|
|
107
|
-
|
70
|
+
def self.param_depth_limit
|
71
|
+
default_query_parser.param_depth_limit
|
108
72
|
end
|
109
|
-
module_function :parse_query
|
110
|
-
|
111
|
-
# parse_nested_query expands a query string into structural types. Supported
|
112
|
-
# types are Arrays, Hashes and basic value types. It is possible to supply
|
113
|
-
# query strings with parameters of conflicting types, in this case a
|
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
|
118
73
|
|
119
|
-
|
120
|
-
|
74
|
+
def self.param_depth_limit=(v)
|
75
|
+
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
76
|
+
end
|
121
77
|
|
122
|
-
|
123
|
-
|
78
|
+
def self.key_space_limit
|
79
|
+
default_query_parser.key_space_limit
|
80
|
+
end
|
124
81
|
|
125
|
-
|
126
|
-
|
127
|
-
raise InvalidParameterError, e.message
|
82
|
+
def self.key_space_limit=(v)
|
83
|
+
self.default_query_parser = self.default_query_parser.new_space_limit(v)
|
128
84
|
end
|
129
|
-
module_function :parse_nested_query
|
130
85
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
87
|
+
def clock_time
|
88
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
def clock_time
|
92
|
+
Time.now.to_f
|
164
93
|
end
|
94
|
+
end
|
95
|
+
module_function :clock_time
|
165
96
|
|
166
|
-
|
97
|
+
def parse_query(qs, d = nil, &unescaper)
|
98
|
+
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
167
99
|
end
|
168
|
-
module_function :
|
100
|
+
module_function :parse_query
|
169
101
|
|
170
|
-
def
|
171
|
-
|
102
|
+
def parse_nested_query(qs, d = nil)
|
103
|
+
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
172
104
|
end
|
173
|
-
module_function :
|
105
|
+
module_function :parse_nested_query
|
174
106
|
|
175
107
|
def build_query(params)
|
176
108
|
params.map { |k, v|
|
@@ -192,7 +124,7 @@ module Rack
|
|
192
124
|
when Hash
|
193
125
|
value.map { |k, v|
|
194
126
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
195
|
-
}.
|
127
|
+
}.delete_if(&:empty?).join('&')
|
196
128
|
when nil
|
197
129
|
prefix
|
198
130
|
else
|
@@ -206,7 +138,7 @@ module Rack
|
|
206
138
|
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
207
139
|
value, parameters = part.split(/\s*;\s*/, 2)
|
208
140
|
quality = 1.0
|
209
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
141
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
210
142
|
quality = md[1].to_f
|
211
143
|
end
|
212
144
|
[value, quality]
|
@@ -236,13 +168,8 @@ module Rack
|
|
236
168
|
'"' => """,
|
237
169
|
"/" => "/"
|
238
170
|
}
|
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
|
171
|
+
|
172
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
246
173
|
|
247
174
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
248
175
|
def escape_html(string)
|
@@ -254,137 +181,139 @@ module Rack
|
|
254
181
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
255
182
|
|
256
183
|
expanded_accept_encoding =
|
257
|
-
accept_encoding.
|
184
|
+
accept_encoding.each_with_object([]) do |(m, q), list|
|
258
185
|
if m == "*"
|
259
|
-
(available_encodings - accept_encoding.map
|
186
|
+
(available_encodings - accept_encoding.map(&:first))
|
187
|
+
.each { |m2| list << [m2, q] }
|
260
188
|
else
|
261
|
-
[
|
189
|
+
list << [m, q]
|
262
190
|
end
|
263
|
-
|
264
|
-
mem + list
|
265
|
-
}
|
191
|
+
end
|
266
192
|
|
267
|
-
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map
|
193
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.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
205
|
module_function :select_best_encoding
|
280
206
|
|
281
|
-
def
|
207
|
+
def parse_cookies(env)
|
208
|
+
parse_cookies_header env[HTTP_COOKIE]
|
209
|
+
end
|
210
|
+
module_function :parse_cookies
|
211
|
+
|
212
|
+
def parse_cookies_header(header)
|
213
|
+
# According to RFC 2109:
|
214
|
+
# If multiple cookies satisfy the criteria above, they are ordered in
|
215
|
+
# the Cookie header such that those with more specific Path attributes
|
216
|
+
# precede those with less specific. Ordering with respect to other
|
217
|
+
# attributes (e.g., Domain) is unspecified.
|
218
|
+
return {} unless header
|
219
|
+
header.split(/[;,] */n).each_with_object({}) do |cookie, cookies|
|
220
|
+
next if cookie.empty?
|
221
|
+
key, value = cookie.split('=', 2)
|
222
|
+
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
module_function :parse_cookies_header
|
226
|
+
|
227
|
+
def add_cookie_to_header(header, key, value)
|
282
228
|
case value
|
283
229
|
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]
|
230
|
+
domain = "; domain=#{value[:domain]}" if value[:domain]
|
231
|
+
path = "; path=#{value[:path]}" if value[:path]
|
232
|
+
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
233
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
312
234
|
secure = "; secure" if value[:secure]
|
313
235
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
314
236
|
same_site =
|
315
237
|
case value[:same_site]
|
316
238
|
when false, nil
|
317
239
|
nil
|
240
|
+
when :none, 'None', :None
|
241
|
+
'; SameSite=None'
|
318
242
|
when :lax, 'Lax', :Lax
|
319
|
-
'; SameSite=Lax'
|
243
|
+
'; SameSite=Lax'
|
320
244
|
when true, :strict, 'Strict', :Strict
|
321
|
-
'; SameSite=Strict'
|
245
|
+
'; SameSite=Strict'
|
322
246
|
else
|
323
247
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
324
248
|
end
|
325
249
|
value = value[:value]
|
326
250
|
end
|
327
251
|
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
252
|
|
332
|
-
|
253
|
+
cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
254
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
255
|
+
|
256
|
+
case header
|
333
257
|
when nil, ''
|
334
|
-
|
258
|
+
cookie
|
335
259
|
when String
|
336
|
-
|
260
|
+
[header, cookie].join("\n")
|
337
261
|
when Array
|
338
|
-
|
262
|
+
(header + [cookie]).join("\n")
|
263
|
+
else
|
264
|
+
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
339
265
|
end
|
266
|
+
end
|
267
|
+
module_function :add_cookie_to_header
|
340
268
|
|
269
|
+
def set_cookie_header!(header, key, value)
|
270
|
+
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
341
271
|
nil
|
342
272
|
end
|
343
273
|
module_function :set_cookie_header!
|
344
274
|
|
345
|
-
def
|
346
|
-
case header
|
275
|
+
def make_delete_cookie_header(header, key, value)
|
276
|
+
case header
|
347
277
|
when nil, ''
|
348
278
|
cookies = []
|
349
279
|
when String
|
350
|
-
cookies = header
|
280
|
+
cookies = header.split("\n")
|
351
281
|
when Array
|
352
|
-
cookies = header
|
282
|
+
cookies = header
|
353
283
|
end
|
354
284
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
end
|
363
|
-
}
|
285
|
+
regexp = if value[:domain]
|
286
|
+
/\A#{escape(key)}=.*domain=#{value[:domain]}/
|
287
|
+
elsif value[:path]
|
288
|
+
/\A#{escape(key)}=.*path=#{value[:path]}/
|
289
|
+
else
|
290
|
+
/\A#{escape(key)}=/
|
291
|
+
end
|
364
292
|
|
365
|
-
|
293
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
366
294
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
:expires => Time.at(0) }.merge(value))
|
295
|
+
cookies.join("\n")
|
296
|
+
end
|
297
|
+
module_function :make_delete_cookie_header
|
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
303
|
module_function :delete_cookie_header!
|
375
304
|
|
376
|
-
#
|
377
|
-
#
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
305
|
+
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
306
|
+
# strange method name.
|
307
|
+
def add_remove_cookie_to_header(header, key, value = {})
|
308
|
+
new_header = make_delete_cookie_header(header, key, value)
|
309
|
+
|
310
|
+
add_cookie_to_header(new_header, key,
|
311
|
+
{ value: '', path: nil, domain: nil,
|
312
|
+
max_age: '0',
|
313
|
+
expires: Time.at(0) }.merge(value))
|
314
|
+
|
386
315
|
end
|
387
|
-
module_function :
|
316
|
+
module_function :add_remove_cookie_to_header
|
388
317
|
|
389
318
|
def rfc2822(time)
|
390
319
|
time.rfc2822
|
@@ -411,13 +340,18 @@ module Rack
|
|
411
340
|
# Returns nil if the header is missing or syntactically invalid.
|
412
341
|
# Returns an empty array if none of the ranges are satisfiable.
|
413
342
|
def byte_ranges(env, size)
|
343
|
+
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
344
|
+
get_byte_ranges env['HTTP_RANGE'], size
|
345
|
+
end
|
346
|
+
module_function :byte_ranges
|
347
|
+
|
348
|
+
def get_byte_ranges(http_range, size)
|
414
349
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
415
|
-
http_range = env['HTTP_RANGE']
|
416
350
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
417
351
|
ranges = []
|
418
352
|
$1.split(/,\s*/).each do |range_spec|
|
419
353
|
return nil unless range_spec =~ /(\d*)-(\d*)/
|
420
|
-
r0,r1 = $1, $2
|
354
|
+
r0, r1 = $1, $2
|
421
355
|
if r0.empty?
|
422
356
|
return nil if r1.empty?
|
423
357
|
# suffix-byte-range-spec, represents trailing suffix of file
|
@@ -431,14 +365,14 @@ module Rack
|
|
431
365
|
else
|
432
366
|
r1 = r1.to_i
|
433
367
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
434
|
-
r1 = size-1 if r1 >= size
|
368
|
+
r1 = size - 1 if r1 >= size
|
435
369
|
end
|
436
370
|
end
|
437
371
|
ranges << (r0..r1) if r0 <= r1
|
438
372
|
end
|
439
373
|
ranges
|
440
374
|
end
|
441
|
-
module_function :
|
375
|
+
module_function :get_byte_ranges
|
442
376
|
|
443
377
|
# Constant time string comparison.
|
444
378
|
#
|
@@ -447,12 +381,12 @@ module Rack
|
|
447
381
|
# on variable length plaintext strings because it could leak length info
|
448
382
|
# via timing attacks.
|
449
383
|
def secure_compare(a, b)
|
450
|
-
return false unless bytesize
|
384
|
+
return false unless a.bytesize == b.bytesize
|
451
385
|
|
452
386
|
l = a.unpack("C*")
|
453
387
|
|
454
388
|
r, i = 0, -1
|
455
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
389
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
456
390
|
r == 0
|
457
391
|
end
|
458
392
|
module_function :secure_compare
|
@@ -478,24 +412,28 @@ module Rack
|
|
478
412
|
self.class.new(@for, app)
|
479
413
|
end
|
480
414
|
|
481
|
-
def context(env, app
|
415
|
+
def context(env, app = @app)
|
482
416
|
recontext(app).call(env)
|
483
417
|
end
|
484
418
|
end
|
485
419
|
|
486
420
|
# A case-insensitive Hash that preserves the original case of a
|
487
421
|
# header when set.
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
def initialize(hash={})
|
422
|
+
#
|
423
|
+
# @api private
|
424
|
+
class HeaderHash < Hash # :nodoc:
|
425
|
+
def initialize(hash = {})
|
494
426
|
super()
|
495
427
|
@names = {}
|
496
428
|
hash.each { |k, v| self[k] = v }
|
497
429
|
end
|
498
430
|
|
431
|
+
# on dup/clone, we need to duplicate @names hash
|
432
|
+
def initialize_copy(other)
|
433
|
+
super
|
434
|
+
@names = other.names.dup
|
435
|
+
end
|
436
|
+
|
499
437
|
def each
|
500
438
|
super do |k, v|
|
501
439
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -504,7 +442,7 @@ module Rack
|
|
504
442
|
|
505
443
|
def to_hash
|
506
444
|
hash = {}
|
507
|
-
each { |k,v| hash[k] = v }
|
445
|
+
each { |k, v| hash[k] = v }
|
508
446
|
hash
|
509
447
|
end
|
510
448
|
|
@@ -513,21 +451,20 @@ module Rack
|
|
513
451
|
end
|
514
452
|
|
515
453
|
def []=(k, v)
|
516
|
-
canonical = k.downcase
|
454
|
+
canonical = k.downcase.freeze
|
517
455
|
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
518
|
-
@names[
|
456
|
+
@names[canonical] = k
|
519
457
|
super k, v
|
520
458
|
end
|
521
459
|
|
522
460
|
def delete(k)
|
523
461
|
canonical = k.downcase
|
524
462
|
result = super @names.delete(canonical)
|
525
|
-
@names.delete_if { |name,| name.downcase == canonical }
|
526
463
|
result
|
527
464
|
end
|
528
465
|
|
529
466
|
def include?(k)
|
530
|
-
|
467
|
+
super || @names.include?(k.downcase)
|
531
468
|
end
|
532
469
|
|
533
470
|
alias_method :has_key?, :include?
|
@@ -549,56 +486,23 @@ module Rack
|
|
549
486
|
other.each { |k, v| self[k] = v }
|
550
487
|
self
|
551
488
|
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
|
-
|
561
|
-
def [](key)
|
562
|
-
@params[key]
|
563
|
-
end
|
564
489
|
|
565
|
-
|
566
|
-
|
567
|
-
|
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
|
490
|
+
protected
|
491
|
+
def names
|
492
|
+
@names
|
588
493
|
end
|
589
|
-
hash
|
590
|
-
end
|
591
494
|
end
|
592
495
|
|
593
496
|
# Every standard HTTP code mapped to the appropriate message.
|
594
497
|
# Generated with:
|
595
|
-
#
|
596
|
-
#
|
597
|
-
#
|
498
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
499
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
500
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
598
501
|
HTTP_STATUS_CODES = {
|
599
502
|
100 => 'Continue',
|
600
503
|
101 => 'Switching Protocols',
|
601
504
|
102 => 'Processing',
|
505
|
+
103 => 'Early Hints',
|
602
506
|
200 => 'OK',
|
603
507
|
201 => 'Created',
|
604
508
|
202 => 'Accepted',
|
@@ -615,6 +519,7 @@ module Rack
|
|
615
519
|
303 => 'See Other',
|
616
520
|
304 => 'Not Modified',
|
617
521
|
305 => 'Use Proxy',
|
522
|
+
306 => '(Unused)',
|
618
523
|
307 => 'Temporary Redirect',
|
619
524
|
308 => 'Permanent Redirect',
|
620
525
|
400 => 'Bad Request',
|
@@ -635,13 +540,16 @@ module Rack
|
|
635
540
|
415 => 'Unsupported Media Type',
|
636
541
|
416 => 'Range Not Satisfiable',
|
637
542
|
417 => 'Expectation Failed',
|
543
|
+
421 => 'Misdirected Request',
|
638
544
|
422 => 'Unprocessable Entity',
|
639
545
|
423 => 'Locked',
|
640
546
|
424 => 'Failed Dependency',
|
547
|
+
425 => 'Too Early',
|
641
548
|
426 => 'Upgrade Required',
|
642
549
|
428 => 'Precondition Required',
|
643
550
|
429 => 'Too Many Requests',
|
644
551
|
431 => 'Request Header Fields Too Large',
|
552
|
+
451 => 'Unavailable for Legal Reasons',
|
645
553
|
500 => 'Internal Server Error',
|
646
554
|
501 => 'Not Implemented',
|
647
555
|
502 => 'Bad Gateway',
|
@@ -651,12 +559,13 @@ module Rack
|
|
651
559
|
506 => 'Variant Also Negotiates',
|
652
560
|
507 => 'Insufficient Storage',
|
653
561
|
508 => 'Loop Detected',
|
562
|
+
509 => 'Bandwidth Limit Exceeded',
|
654
563
|
510 => 'Not Extended',
|
655
564
|
511 => 'Network Authentication Required'
|
656
565
|
}
|
657
566
|
|
658
567
|
# Responses with HTTP status codes that should not have an entity body
|
659
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
568
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
660
569
|
|
661
570
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
662
571
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -664,15 +573,13 @@ module Rack
|
|
664
573
|
|
665
574
|
def status_code(status)
|
666
575
|
if status.is_a?(Symbol)
|
667
|
-
SYMBOL_TO_STATUS_CODE
|
576
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
668
577
|
else
|
669
578
|
status.to_i
|
670
579
|
end
|
671
580
|
end
|
672
581
|
module_function :status_code
|
673
582
|
|
674
|
-
Multipart = Rack::Multipart
|
675
|
-
|
676
583
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
677
584
|
|
678
585
|
def clean_path_info(path_info)
|
@@ -687,9 +594,16 @@ module Rack
|
|
687
594
|
|
688
595
|
clean.unshift '/' if parts.empty? || parts.first.empty?
|
689
596
|
|
690
|
-
::File.join
|
597
|
+
::File.join clean
|
691
598
|
end
|
692
599
|
module_function :clean_path_info
|
693
600
|
|
601
|
+
NULL_BYTE = "\0"
|
602
|
+
|
603
|
+
def valid_path?(path)
|
604
|
+
path.valid_encoding? && !path.include?(NULL_BYTE)
|
605
|
+
end
|
606
|
+
module_function :valid_path?
|
607
|
+
|
694
608
|
end
|
695
609
|
end
|