rack 1.6.13 → 2.1.4.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 +92 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +105 -141
- 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} +14 -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 +55 -52
- 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 +292 -161
- data/lib/rack/multipart/uploaded_file.rb +3 -2
- data/lib/rack/multipart.rb +38 -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 +139 -94
- data/lib/rack/session/cookie.rb +34 -26
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +12 -10
- 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 +203 -277
- data/lib/rack.rb +76 -24
- data/rack.gemspec +25 -14
- 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/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,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,81 @@ 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 :
|
64
|
-
attr_accessor :param_depth_limit
|
65
|
-
attr_accessor :multipart_part_limit
|
66
|
-
end
|
62
|
+
attr_accessor :multipart_total_part_limit
|
67
63
|
|
68
|
-
|
69
|
-
# This helps prevent a rogue client from flooding a Request.
|
70
|
-
self.key_space_limit = 65536
|
64
|
+
attr_accessor :multipart_file_limit
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
66
|
+
# multipart_part_limit is the original name of multipart_file_limit, but
|
67
|
+
# the limit only counts parts with filenames.
|
68
|
+
alias multipart_part_limit multipart_file_limit
|
69
|
+
alias multipart_part_limit= multipart_file_limit=
|
70
|
+
end
|
75
71
|
|
76
|
-
# The maximum number of parts a request can contain. Accepting too
|
77
|
-
# can lead to the server running out of file handles.
|
72
|
+
# The maximum number of file parts a request can contain. Accepting too
|
73
|
+
# many parts can lead to the server running out of file handles.
|
78
74
|
# 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
|
75
|
+
self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
|
91
76
|
|
92
|
-
|
93
|
-
|
94
|
-
|
77
|
+
# The maximum total number of parts a request can contain. Accepting too
|
78
|
+
# many can lead to excessive memory use and parsing time.
|
79
|
+
self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
|
95
80
|
|
96
|
-
|
97
|
-
|
98
|
-
params[k] << v
|
99
|
-
else
|
100
|
-
params[k] = [cur, v]
|
101
|
-
end
|
102
|
-
else
|
103
|
-
params[k] = v
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
return params.to_params_hash
|
81
|
+
def self.param_depth_limit
|
82
|
+
default_query_parser.param_depth_limit
|
108
83
|
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
84
|
|
119
|
-
|
120
|
-
|
85
|
+
def self.param_depth_limit=(v)
|
86
|
+
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
87
|
+
end
|
121
88
|
|
122
|
-
|
123
|
-
|
89
|
+
def self.key_space_limit
|
90
|
+
default_query_parser.key_space_limit
|
91
|
+
end
|
124
92
|
|
125
|
-
|
126
|
-
|
127
|
-
raise InvalidParameterError, e.message
|
93
|
+
def self.key_space_limit=(v)
|
94
|
+
self.default_query_parser = self.default_query_parser.new_space_limit(v)
|
128
95
|
end
|
129
|
-
module_function :parse_nested_query
|
130
96
|
|
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)
|
97
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
98
|
+
def clock_time
|
99
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
def clock_time
|
103
|
+
Time.now.to_f
|
164
104
|
end
|
105
|
+
end
|
106
|
+
module_function :clock_time
|
165
107
|
|
166
|
-
|
108
|
+
def parse_query(qs, d = nil, &unescaper)
|
109
|
+
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
167
110
|
end
|
168
|
-
module_function :
|
111
|
+
module_function :parse_query
|
169
112
|
|
170
|
-
def
|
171
|
-
|
113
|
+
def parse_nested_query(qs, d = nil)
|
114
|
+
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
172
115
|
end
|
173
|
-
module_function :
|
116
|
+
module_function :parse_nested_query
|
174
117
|
|
175
118
|
def build_query(params)
|
176
119
|
params.map { |k, v|
|
@@ -192,7 +135,7 @@ module Rack
|
|
192
135
|
when Hash
|
193
136
|
value.map { |k, v|
|
194
137
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
195
|
-
}.
|
138
|
+
}.delete_if(&:empty?).join('&')
|
196
139
|
when nil
|
197
140
|
prefix
|
198
141
|
else
|
@@ -206,7 +149,7 @@ module Rack
|
|
206
149
|
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
207
150
|
value, parameters = part.split(/\s*;\s*/, 2)
|
208
151
|
quality = 1.0
|
209
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
152
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
210
153
|
quality = md[1].to_f
|
211
154
|
end
|
212
155
|
[value, quality]
|
@@ -236,13 +179,8 @@ module Rack
|
|
236
179
|
'"' => """,
|
237
180
|
"/" => "/"
|
238
181
|
}
|
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
|
182
|
+
|
183
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
246
184
|
|
247
185
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
248
186
|
def escape_html(string)
|
@@ -254,137 +192,139 @@ module Rack
|
|
254
192
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
255
193
|
|
256
194
|
expanded_accept_encoding =
|
257
|
-
accept_encoding.
|
195
|
+
accept_encoding.each_with_object([]) do |(m, q), list|
|
258
196
|
if m == "*"
|
259
|
-
(available_encodings - accept_encoding.map
|
197
|
+
(available_encodings - accept_encoding.map(&:first))
|
198
|
+
.each { |m2| list << [m2, q] }
|
260
199
|
else
|
261
|
-
[
|
200
|
+
list << [m, q]
|
262
201
|
end
|
263
|
-
|
264
|
-
mem + list
|
265
|
-
}
|
202
|
+
end
|
266
203
|
|
267
|
-
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map
|
204
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
|
268
205
|
|
269
206
|
unless encoding_candidates.include?("identity")
|
270
207
|
encoding_candidates.push("identity")
|
271
208
|
end
|
272
209
|
|
273
|
-
expanded_accept_encoding.each
|
210
|
+
expanded_accept_encoding.each do |m, q|
|
274
211
|
encoding_candidates.delete(m) if q == 0.0
|
275
|
-
|
212
|
+
end
|
276
213
|
|
277
|
-
|
214
|
+
(encoding_candidates & available_encodings)[0]
|
278
215
|
end
|
279
216
|
module_function :select_best_encoding
|
280
217
|
|
281
|
-
def
|
218
|
+
def parse_cookies(env)
|
219
|
+
parse_cookies_header env[HTTP_COOKIE]
|
220
|
+
end
|
221
|
+
module_function :parse_cookies
|
222
|
+
|
223
|
+
def parse_cookies_header(header)
|
224
|
+
# According to RFC 2109:
|
225
|
+
# If multiple cookies satisfy the criteria above, they are ordered in
|
226
|
+
# the Cookie header such that those with more specific Path attributes
|
227
|
+
# precede those with less specific. Ordering with respect to other
|
228
|
+
# attributes (e.g., Domain) is unspecified.
|
229
|
+
return {} unless header
|
230
|
+
header.split(/[;,] */n).each_with_object({}) do |cookie, cookies|
|
231
|
+
next if cookie.empty?
|
232
|
+
key, value = cookie.split('=', 2)
|
233
|
+
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
module_function :parse_cookies_header
|
237
|
+
|
238
|
+
def add_cookie_to_header(header, key, value)
|
282
239
|
case value
|
283
240
|
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]
|
241
|
+
domain = "; domain=#{value[:domain]}" if value[:domain]
|
242
|
+
path = "; path=#{value[:path]}" if value[:path]
|
243
|
+
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
244
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
312
245
|
secure = "; secure" if value[:secure]
|
313
246
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
314
247
|
same_site =
|
315
248
|
case value[:same_site]
|
316
249
|
when false, nil
|
317
250
|
nil
|
251
|
+
when :none, 'None', :None
|
252
|
+
'; SameSite=None'
|
318
253
|
when :lax, 'Lax', :Lax
|
319
|
-
'; SameSite=Lax'
|
254
|
+
'; SameSite=Lax'
|
320
255
|
when true, :strict, 'Strict', :Strict
|
321
|
-
'; SameSite=Strict'
|
256
|
+
'; SameSite=Strict'
|
322
257
|
else
|
323
258
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
324
259
|
end
|
325
260
|
value = value[:value]
|
326
261
|
end
|
327
262
|
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
263
|
|
332
|
-
|
264
|
+
cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
265
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
266
|
+
|
267
|
+
case header
|
333
268
|
when nil, ''
|
334
|
-
|
269
|
+
cookie
|
335
270
|
when String
|
336
|
-
|
271
|
+
[header, cookie].join("\n")
|
337
272
|
when Array
|
338
|
-
|
273
|
+
(header + [cookie]).join("\n")
|
274
|
+
else
|
275
|
+
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
339
276
|
end
|
277
|
+
end
|
278
|
+
module_function :add_cookie_to_header
|
340
279
|
|
280
|
+
def set_cookie_header!(header, key, value)
|
281
|
+
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
341
282
|
nil
|
342
283
|
end
|
343
284
|
module_function :set_cookie_header!
|
344
285
|
|
345
|
-
def
|
346
|
-
case header
|
286
|
+
def make_delete_cookie_header(header, key, value)
|
287
|
+
case header
|
347
288
|
when nil, ''
|
348
289
|
cookies = []
|
349
290
|
when String
|
350
|
-
cookies = header
|
291
|
+
cookies = header.split("\n")
|
351
292
|
when Array
|
352
|
-
cookies = header
|
293
|
+
cookies = header
|
353
294
|
end
|
354
295
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
end
|
363
|
-
}
|
296
|
+
regexp = if value[:domain]
|
297
|
+
/\A#{escape(key)}=.*domain=#{value[:domain]}/
|
298
|
+
elsif value[:path]
|
299
|
+
/\A#{escape(key)}=.*path=#{value[:path]}/
|
300
|
+
else
|
301
|
+
/\A#{escape(key)}=/
|
302
|
+
end
|
364
303
|
|
365
|
-
|
304
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
366
305
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
:expires => Time.at(0) }.merge(value))
|
306
|
+
cookies.join("\n")
|
307
|
+
end
|
308
|
+
module_function :make_delete_cookie_header
|
371
309
|
|
310
|
+
def delete_cookie_header!(header, key, value = {})
|
311
|
+
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
372
312
|
nil
|
373
313
|
end
|
374
314
|
module_function :delete_cookie_header!
|
375
315
|
|
376
|
-
#
|
377
|
-
#
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
316
|
+
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
317
|
+
# strange method name.
|
318
|
+
def add_remove_cookie_to_header(header, key, value = {})
|
319
|
+
new_header = make_delete_cookie_header(header, key, value)
|
320
|
+
|
321
|
+
add_cookie_to_header(new_header, key,
|
322
|
+
{ value: '', path: nil, domain: nil,
|
323
|
+
max_age: '0',
|
324
|
+
expires: Time.at(0) }.merge(value))
|
325
|
+
|
386
326
|
end
|
387
|
-
module_function :
|
327
|
+
module_function :add_remove_cookie_to_header
|
388
328
|
|
389
329
|
def rfc2822(time)
|
390
330
|
time.rfc2822
|
@@ -411,34 +351,40 @@ module Rack
|
|
411
351
|
# Returns nil if the header is missing or syntactically invalid.
|
412
352
|
# Returns an empty array if none of the ranges are satisfiable.
|
413
353
|
def byte_ranges(env, size)
|
354
|
+
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
355
|
+
get_byte_ranges env['HTTP_RANGE'], size
|
356
|
+
end
|
357
|
+
module_function :byte_ranges
|
358
|
+
|
359
|
+
def get_byte_ranges(http_range, size)
|
414
360
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
415
|
-
http_range = env['HTTP_RANGE']
|
416
361
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
417
362
|
ranges = []
|
418
363
|
$1.split(/,\s*/).each do |range_spec|
|
419
|
-
return nil
|
420
|
-
|
421
|
-
|
422
|
-
|
364
|
+
return nil unless range_spec.include?('-')
|
365
|
+
range = range_spec.split('-')
|
366
|
+
r0, r1 = range[0], range[1]
|
367
|
+
if r0.nil? || r0.empty?
|
368
|
+
return nil if r1.nil?
|
423
369
|
# suffix-byte-range-spec, represents trailing suffix of file
|
424
370
|
r0 = size - r1.to_i
|
425
371
|
r0 = 0 if r0 < 0
|
426
372
|
r1 = size - 1
|
427
373
|
else
|
428
374
|
r0 = r0.to_i
|
429
|
-
if r1.
|
375
|
+
if r1.nil?
|
430
376
|
r1 = size - 1
|
431
377
|
else
|
432
378
|
r1 = r1.to_i
|
433
379
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
434
|
-
r1 = size-1 if r1 >= size
|
380
|
+
r1 = size - 1 if r1 >= size
|
435
381
|
end
|
436
382
|
end
|
437
383
|
ranges << (r0..r1) if r0 <= r1
|
438
384
|
end
|
439
385
|
ranges
|
440
386
|
end
|
441
|
-
module_function :
|
387
|
+
module_function :get_byte_ranges
|
442
388
|
|
443
389
|
# Constant time string comparison.
|
444
390
|
#
|
@@ -447,12 +393,12 @@ module Rack
|
|
447
393
|
# on variable length plaintext strings because it could leak length info
|
448
394
|
# via timing attacks.
|
449
395
|
def secure_compare(a, b)
|
450
|
-
return false unless bytesize
|
396
|
+
return false unless a.bytesize == b.bytesize
|
451
397
|
|
452
398
|
l = a.unpack("C*")
|
453
399
|
|
454
400
|
r, i = 0, -1
|
455
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
401
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
456
402
|
r == 0
|
457
403
|
end
|
458
404
|
module_function :secure_compare
|
@@ -478,24 +424,28 @@ module Rack
|
|
478
424
|
self.class.new(@for, app)
|
479
425
|
end
|
480
426
|
|
481
|
-
def context(env, app
|
427
|
+
def context(env, app = @app)
|
482
428
|
recontext(app).call(env)
|
483
429
|
end
|
484
430
|
end
|
485
431
|
|
486
432
|
# A case-insensitive Hash that preserves the original case of a
|
487
433
|
# header when set.
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
def initialize(hash={})
|
434
|
+
#
|
435
|
+
# @api private
|
436
|
+
class HeaderHash < Hash # :nodoc:
|
437
|
+
def initialize(hash = {})
|
494
438
|
super()
|
495
439
|
@names = {}
|
496
440
|
hash.each { |k, v| self[k] = v }
|
497
441
|
end
|
498
442
|
|
443
|
+
# on dup/clone, we need to duplicate @names hash
|
444
|
+
def initialize_copy(other)
|
445
|
+
super
|
446
|
+
@names = other.names.dup
|
447
|
+
end
|
448
|
+
|
499
449
|
def each
|
500
450
|
super do |k, v|
|
501
451
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -504,7 +454,7 @@ module Rack
|
|
504
454
|
|
505
455
|
def to_hash
|
506
456
|
hash = {}
|
507
|
-
each { |k,v| hash[k] = v }
|
457
|
+
each { |k, v| hash[k] = v }
|
508
458
|
hash
|
509
459
|
end
|
510
460
|
|
@@ -513,21 +463,20 @@ module Rack
|
|
513
463
|
end
|
514
464
|
|
515
465
|
def []=(k, v)
|
516
|
-
canonical = k.downcase
|
466
|
+
canonical = k.downcase.freeze
|
517
467
|
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
518
|
-
@names[
|
468
|
+
@names[canonical] = k
|
519
469
|
super k, v
|
520
470
|
end
|
521
471
|
|
522
472
|
def delete(k)
|
523
473
|
canonical = k.downcase
|
524
474
|
result = super @names.delete(canonical)
|
525
|
-
@names.delete_if { |name,| name.downcase == canonical }
|
526
475
|
result
|
527
476
|
end
|
528
477
|
|
529
478
|
def include?(k)
|
530
|
-
|
479
|
+
super || @names.include?(k.downcase)
|
531
480
|
end
|
532
481
|
|
533
482
|
alias_method :has_key?, :include?
|
@@ -549,56 +498,23 @@ module Rack
|
|
549
498
|
other.each { |k, v| self[k] = v }
|
550
499
|
self
|
551
500
|
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
501
|
|
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
|
502
|
+
protected
|
503
|
+
def names
|
504
|
+
@names
|
588
505
|
end
|
589
|
-
hash
|
590
|
-
end
|
591
506
|
end
|
592
507
|
|
593
508
|
# Every standard HTTP code mapped to the appropriate message.
|
594
509
|
# Generated with:
|
595
|
-
#
|
596
|
-
#
|
597
|
-
#
|
510
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
511
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
512
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
598
513
|
HTTP_STATUS_CODES = {
|
599
514
|
100 => 'Continue',
|
600
515
|
101 => 'Switching Protocols',
|
601
516
|
102 => 'Processing',
|
517
|
+
103 => 'Early Hints',
|
602
518
|
200 => 'OK',
|
603
519
|
201 => 'Created',
|
604
520
|
202 => 'Accepted',
|
@@ -615,6 +531,7 @@ module Rack
|
|
615
531
|
303 => 'See Other',
|
616
532
|
304 => 'Not Modified',
|
617
533
|
305 => 'Use Proxy',
|
534
|
+
306 => '(Unused)',
|
618
535
|
307 => 'Temporary Redirect',
|
619
536
|
308 => 'Permanent Redirect',
|
620
537
|
400 => 'Bad Request',
|
@@ -635,13 +552,16 @@ module Rack
|
|
635
552
|
415 => 'Unsupported Media Type',
|
636
553
|
416 => 'Range Not Satisfiable',
|
637
554
|
417 => 'Expectation Failed',
|
555
|
+
421 => 'Misdirected Request',
|
638
556
|
422 => 'Unprocessable Entity',
|
639
557
|
423 => 'Locked',
|
640
558
|
424 => 'Failed Dependency',
|
559
|
+
425 => 'Too Early',
|
641
560
|
426 => 'Upgrade Required',
|
642
561
|
428 => 'Precondition Required',
|
643
562
|
429 => 'Too Many Requests',
|
644
563
|
431 => 'Request Header Fields Too Large',
|
564
|
+
451 => 'Unavailable for Legal Reasons',
|
645
565
|
500 => 'Internal Server Error',
|
646
566
|
501 => 'Not Implemented',
|
647
567
|
502 => 'Bad Gateway',
|
@@ -651,12 +571,13 @@ module Rack
|
|
651
571
|
506 => 'Variant Also Negotiates',
|
652
572
|
507 => 'Insufficient Storage',
|
653
573
|
508 => 'Loop Detected',
|
574
|
+
509 => 'Bandwidth Limit Exceeded',
|
654
575
|
510 => 'Not Extended',
|
655
576
|
511 => 'Network Authentication Required'
|
656
577
|
}
|
657
578
|
|
658
579
|
# Responses with HTTP status codes that should not have an entity body
|
659
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
580
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
660
581
|
|
661
582
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
662
583
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -664,15 +585,13 @@ module Rack
|
|
664
585
|
|
665
586
|
def status_code(status)
|
666
587
|
if status.is_a?(Symbol)
|
667
|
-
SYMBOL_TO_STATUS_CODE
|
588
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
668
589
|
else
|
669
590
|
status.to_i
|
670
591
|
end
|
671
592
|
end
|
672
593
|
module_function :status_code
|
673
594
|
|
674
|
-
Multipart = Rack::Multipart
|
675
|
-
|
676
595
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
677
596
|
|
678
597
|
def clean_path_info(path_info)
|
@@ -687,9 +606,16 @@ module Rack
|
|
687
606
|
|
688
607
|
clean.unshift '/' if parts.empty? || parts.first.empty?
|
689
608
|
|
690
|
-
::File.join
|
609
|
+
::File.join clean
|
691
610
|
end
|
692
611
|
module_function :clean_path_info
|
693
612
|
|
613
|
+
NULL_BYTE = "\0"
|
614
|
+
|
615
|
+
def valid_path?(path)
|
616
|
+
path.valid_encoding? && !path.include?(NULL_BYTE)
|
617
|
+
end
|
618
|
+
module_function :valid_path?
|
619
|
+
|
694
620
|
end
|
695
621
|
end
|