rack 2.1.4.4 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +598 -15
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +84 -54
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +3 -3
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +25 -21
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +2 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +15 -11
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +58 -15
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/mock.rb +22 -4
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +12 -32
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/multipart.rb +5 -4
- data/lib/rack/query_parser.rb +7 -8
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +172 -76
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +0 -3
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +20 -18
- data/lib/rack/session/cookie.rb +2 -3
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/show_exceptions.rb +2 -4
- data/lib/rack/show_status.rb +1 -3
- data/lib/rack/static.rb +13 -6
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +1 -4
- data/lib/rack/utils.rb +70 -82
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +7 -16
- data/rack.gemspec +31 -29
- metadata +14 -15
@@ -17,9 +17,13 @@ module Rack
|
|
17
17
|
|
18
18
|
flattened_params.map do |name, file|
|
19
19
|
if file.respond_to?(:original_filename)
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
if file.path
|
21
|
+
::File.open(file.path, 'rb') do |f|
|
22
|
+
f.set_encoding(Encoding::BINARY)
|
23
|
+
content_for_tempfile(f, file, name)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
content_for_tempfile(file, file, name)
|
23
27
|
end
|
24
28
|
else
|
25
29
|
content_for_other(file, name)
|
@@ -69,12 +73,13 @@ module Rack
|
|
69
73
|
end
|
70
74
|
|
71
75
|
def content_for_tempfile(io, file, name)
|
76
|
+
length = ::File.stat(file.path).size if file.path
|
77
|
+
filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
|
72
78
|
<<-EOF
|
73
79
|
--#{MULTIPART_BOUNDARY}\r
|
74
|
-
Content-Disposition: form-data; name="#{name}"
|
80
|
+
Content-Disposition: form-data; name="#{name}"#{filename}\r
|
75
81
|
Content-Type: #{file.content_type}\r
|
76
|
-
Content-Length: #{
|
77
|
-
\r
|
82
|
+
#{"Content-Length: #{length}\r\n" if length}\r
|
78
83
|
#{io.read}\r
|
79
84
|
EOF
|
80
85
|
end
|
@@ -1,16 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/utils'
|
4
3
|
require 'strscan'
|
5
|
-
require 'rack/core_ext/regexp'
|
6
4
|
|
7
5
|
module Rack
|
8
6
|
module Multipart
|
9
7
|
class MultipartPartLimitError < Errno::EMFILE; end
|
10
|
-
class MultipartTotalPartLimitError < StandardError; end
|
11
8
|
|
12
9
|
class Parser
|
13
|
-
using ::Rack::RegexpExtensions
|
10
|
+
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
14
11
|
|
15
12
|
BUFSIZE = 1_048_576
|
16
13
|
TEXT_PLAIN = "text/plain"
|
@@ -102,12 +99,6 @@ module Rack
|
|
102
99
|
|
103
100
|
data = { filename: fn, type: content_type,
|
104
101
|
name: name, tempfile: body, head: head }
|
105
|
-
elsif !filename && content_type && body.is_a?(IO)
|
106
|
-
body.rewind
|
107
|
-
|
108
|
-
# Generic multipart cases, not coming from a form
|
109
|
-
data = { type: content_type,
|
110
|
-
name: name, tempfile: body, head: head }
|
111
102
|
end
|
112
103
|
|
113
104
|
yield data
|
@@ -126,7 +117,7 @@ module Rack
|
|
126
117
|
|
127
118
|
include Enumerable
|
128
119
|
|
129
|
-
def initialize
|
120
|
+
def initialize(tempfile)
|
130
121
|
@tempfile = tempfile
|
131
122
|
@mime_parts = []
|
132
123
|
@open_files = 0
|
@@ -136,7 +127,7 @@ module Rack
|
|
136
127
|
@mime_parts.each { |part| yield part }
|
137
128
|
end
|
138
129
|
|
139
|
-
def on_mime_head
|
130
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
140
131
|
if filename
|
141
132
|
body = @tempfile.call(filename, content_type)
|
142
133
|
body.binmode if body.respond_to?(:binmode)
|
@@ -149,35 +140,25 @@ module Rack
|
|
149
140
|
|
150
141
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
151
142
|
|
152
|
-
|
143
|
+
check_open_files
|
153
144
|
end
|
154
145
|
|
155
|
-
def on_mime_body
|
146
|
+
def on_mime_body(mime_index, content)
|
156
147
|
@mime_parts[mime_index].body << content
|
157
148
|
end
|
158
149
|
|
159
|
-
def on_mime_finish
|
150
|
+
def on_mime_finish(mime_index)
|
160
151
|
end
|
161
152
|
|
162
153
|
private
|
163
154
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if file_limit && file_limit > 0
|
169
|
-
if @open_files >= file_limit
|
155
|
+
def check_open_files
|
156
|
+
if Utils.multipart_part_limit > 0
|
157
|
+
if @open_files >= Utils.multipart_part_limit
|
170
158
|
@mime_parts.each(&:close)
|
171
159
|
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
172
160
|
end
|
173
161
|
end
|
174
|
-
|
175
|
-
if part_limit && part_limit > 0
|
176
|
-
if @mime_parts.size >= part_limit
|
177
|
-
@mime_parts.each(&:close)
|
178
|
-
raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
|
179
|
-
end
|
180
|
-
end
|
181
162
|
end
|
182
163
|
end
|
183
164
|
|
@@ -201,7 +182,7 @@ module Rack
|
|
201
182
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
202
183
|
end
|
203
184
|
|
204
|
-
def on_read
|
185
|
+
def on_read(content)
|
205
186
|
handle_empty_content!(content)
|
206
187
|
@sbuf.concat content
|
207
188
|
run_parser
|
@@ -320,9 +301,8 @@ module Rack
|
|
320
301
|
elsif filename = params['filename*']
|
321
302
|
encoding, _, filename = filename.split("'", 3)
|
322
303
|
end
|
323
|
-
when
|
304
|
+
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
324
305
|
filename = $1
|
325
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
326
306
|
end
|
327
307
|
|
328
308
|
return unless filename
|
@@ -359,7 +339,7 @@ module Rack
|
|
359
339
|
type_subtype = list.first
|
360
340
|
type_subtype.strip!
|
361
341
|
if TEXT_PLAIN == type_subtype
|
362
|
-
rest
|
342
|
+
rest = list.drop 1
|
363
343
|
rest.each do |param|
|
364
344
|
k, v = param.split('=', 2)
|
365
345
|
k.strip!
|
@@ -9,17 +9,23 @@ module Rack
|
|
9
9
|
# The content type of the "uploaded" file
|
10
10
|
attr_accessor :content_type
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
|
12
|
+
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
13
|
+
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
14
|
+
if io
|
15
|
+
@tempfile = io
|
16
|
+
@original_filename = filename
|
17
|
+
else
|
18
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
19
|
+
@original_filename = filename || ::File.basename(path)
|
20
|
+
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
21
|
+
@tempfile.binmode if binary
|
22
|
+
FileUtils.copy_file(path, @tempfile.path)
|
23
|
+
end
|
14
24
|
@content_type = content_type
|
15
|
-
@original_filename = ::File.basename(path)
|
16
|
-
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
17
|
-
@tempfile.binmode if binary
|
18
|
-
FileUtils.copy_file(path, @tempfile.path)
|
19
25
|
end
|
20
26
|
|
21
27
|
def path
|
22
|
-
@tempfile.path
|
28
|
+
@tempfile.path if @tempfile.respond_to?(:path)
|
23
29
|
end
|
24
30
|
alias_method :local_path, :path
|
25
31
|
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative 'multipart/parser'
|
4
4
|
|
5
5
|
module Rack
|
6
6
|
# A multipart form data parser, adapted from IOWA.
|
@@ -16,12 +16,13 @@ module Rack
|
|
16
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
17
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
18
18
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
19
|
-
|
19
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
20
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
20
21
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
21
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
22
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
22
23
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
23
24
|
# Updated definitions from RFC 2231
|
24
|
-
ATTRIBUTE_CHAR = %r{[^ \
|
25
|
+
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
25
26
|
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
26
27
|
SECTION = /\*[0-9]+/
|
27
28
|
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'core_ext/regexp'
|
4
|
-
|
5
3
|
module Rack
|
6
4
|
class QueryParser
|
7
|
-
using ::Rack::RegexpExtensions
|
5
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
8
6
|
|
9
7
|
DEFAULT_SEP = /[&;] */n
|
10
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
@@ -64,18 +62,19 @@ module Rack
|
|
64
62
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
65
63
|
# case.
|
66
64
|
def parse_nested_query(qs, d = nil)
|
67
|
-
return {} if qs.nil? || qs.empty?
|
68
65
|
params = make_params
|
69
66
|
|
70
|
-
qs.
|
71
|
-
|
67
|
+
unless qs.nil? || qs.empty?
|
68
|
+
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
69
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
72
70
|
|
73
|
-
|
71
|
+
normalize_params(params, k, v, param_depth_limit)
|
72
|
+
end
|
74
73
|
end
|
75
74
|
|
76
75
|
return params.to_h
|
77
76
|
rescue ArgumentError => e
|
78
|
-
raise InvalidParameterError, e.message
|
77
|
+
raise InvalidParameterError, e.message, e.backtrace
|
79
78
|
end
|
80
79
|
|
81
80
|
# normalize_params recursively expands parameters into structural types. If
|
data/lib/rack/recursive.rb
CHANGED
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
@env[PATH_INFO] = @url.path
|
20
20
|
@env[QUERY_STRING] = @url.query if @url.query
|
21
21
|
@env[HTTP_HOST] = @url.host if @url.host
|
22
|
-
@env[
|
22
|
+
@env[HTTP_PORT] = @url.port if @url.port
|
23
23
|
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
24
24
|
|
25
25
|
super "forwarding to #{url}"
|
data/lib/rack/reloader.rb
CHANGED
@@ -6,8 +6,6 @@
|
|
6
6
|
|
7
7
|
require 'pathname'
|
8
8
|
|
9
|
-
require_relative 'core_ext/regexp'
|
10
|
-
|
11
9
|
module Rack
|
12
10
|
|
13
11
|
# High performant source reloader
|
@@ -24,7 +22,7 @@ module Rack
|
|
24
22
|
# It is performing a check/reload cycle at the start of every request, but
|
25
23
|
# also respects a cool down time, during which nothing will be done.
|
26
24
|
class Reloader
|
27
|
-
using ::Rack::RegexpExtensions
|
25
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
28
26
|
|
29
27
|
def initialize(app, cooldown = 10, backend = Stat)
|
30
28
|
@app = app
|
data/lib/rack/request.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/utils'
|
4
|
-
require 'rack/media_type'
|
5
|
-
|
6
|
-
require_relative 'core_ext/regexp'
|
7
|
-
|
8
3
|
module Rack
|
9
4
|
# Rack::Request provides a convenient interface to a Rack
|
10
5
|
# environment. It is stateless, the environment +env+ passed to the
|
@@ -15,7 +10,7 @@ module Rack
|
|
15
10
|
# req.params["data"]
|
16
11
|
|
17
12
|
class Request
|
18
|
-
using ::Rack::RegexpExtensions
|
13
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
19
14
|
|
20
15
|
class << self
|
21
16
|
attr_accessor :ip_filter
|
@@ -93,7 +88,7 @@ module Rack
|
|
93
88
|
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
94
89
|
#
|
95
90
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
96
|
-
def add_header
|
91
|
+
def add_header(key, v)
|
97
92
|
if v.nil?
|
98
93
|
get_header key
|
99
94
|
elsif has_header? key
|
@@ -134,11 +129,23 @@ module Rack
|
|
134
129
|
# to include the port in a generated URI.
|
135
130
|
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
136
131
|
|
132
|
+
# The address of the client which connected to the proxy.
|
133
|
+
HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
|
134
|
+
|
135
|
+
# The contents of the host/:authority header sent to the proxy.
|
136
|
+
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
137
|
+
|
138
|
+
# The value of the scheme sent to the proxy.
|
137
139
|
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
140
|
+
|
141
|
+
# The protocol used to connect to the proxy.
|
142
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
143
|
+
|
144
|
+
# The port used to connect to the proxy.
|
145
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
146
|
+
|
147
|
+
# Another way for specifing https scheme was used.
|
148
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
142
149
|
|
143
150
|
def body; get_header(RACK_INPUT) end
|
144
151
|
def script_name; get_header(SCRIPT_NAME).to_s end
|
@@ -212,19 +219,52 @@ module Rack
|
|
212
219
|
end
|
213
220
|
end
|
214
221
|
|
222
|
+
# The authority of the incoming request as defined by RFC3976.
|
223
|
+
# https://tools.ietf.org/html/rfc3986#section-3.2
|
224
|
+
#
|
225
|
+
# In HTTP/1, this is the `host` header.
|
226
|
+
# In HTTP/2, this is the `:authority` pseudo-header.
|
215
227
|
def authority
|
216
|
-
|
228
|
+
forwarded_authority || host_authority || server_authority
|
229
|
+
end
|
230
|
+
|
231
|
+
# The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
|
232
|
+
# variables.
|
233
|
+
def server_authority
|
234
|
+
host = self.server_name
|
235
|
+
port = self.server_port
|
236
|
+
|
237
|
+
if host
|
238
|
+
if port
|
239
|
+
"#{host}:#{port}"
|
240
|
+
else
|
241
|
+
host
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def server_name
|
247
|
+
get_header(SERVER_NAME)
|
248
|
+
end
|
249
|
+
|
250
|
+
def server_port
|
251
|
+
if port = get_header(SERVER_PORT)
|
252
|
+
Integer(port)
|
253
|
+
end
|
217
254
|
end
|
218
255
|
|
219
256
|
def cookies
|
220
|
-
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |
|
221
|
-
set_header(
|
257
|
+
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
|
258
|
+
set_header(key, {})
|
259
|
+
end
|
260
|
+
|
261
|
+
string = get_header(HTTP_COOKIE)
|
262
|
+
|
263
|
+
unless string == get_header(RACK_REQUEST_COOKIE_STRING)
|
264
|
+
hash.replace Utils.parse_cookies_header(string)
|
265
|
+
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
222
266
|
end
|
223
|
-
string = get_header HTTP_COOKIE
|
224
267
|
|
225
|
-
return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
|
226
|
-
hash.replace Utils.parse_cookies_header string
|
227
|
-
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
228
268
|
hash
|
229
269
|
end
|
230
270
|
|
@@ -237,52 +277,91 @@ module Rack
|
|
237
277
|
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
238
278
|
end
|
239
279
|
|
240
|
-
|
241
|
-
|
242
|
-
|
280
|
+
# The `HTTP_HOST` header.
|
281
|
+
def host_authority
|
282
|
+
get_header(HTTP_HOST)
|
283
|
+
end
|
284
|
+
|
285
|
+
def host_with_port(authority = self.authority)
|
286
|
+
host, _, port = split_authority(authority)
|
287
|
+
|
288
|
+
if port == DEFAULT_PORTS[self.scheme]
|
289
|
+
host
|
243
290
|
else
|
244
|
-
|
291
|
+
authority
|
245
292
|
end
|
246
293
|
end
|
247
294
|
|
295
|
+
# Returns a formatted host, suitable for being used in a URI.
|
248
296
|
def host
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
297
|
+
split_authority(self.authority)[0]
|
298
|
+
end
|
299
|
+
|
300
|
+
# Returns an address suitable for being to resolve to an address.
|
301
|
+
# In the case of a domain name or IPv4 address, the result is the same
|
302
|
+
# as +host+. In the case of IPv6 or future address formats, the square
|
303
|
+
# brackets are removed.
|
304
|
+
def hostname
|
305
|
+
split_authority(self.authority)[1]
|
256
306
|
end
|
257
307
|
|
258
308
|
def port
|
259
|
-
if
|
260
|
-
port.
|
261
|
-
|
262
|
-
port
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
309
|
+
if authority = self.authority
|
310
|
+
_, _, port = split_authority(self.authority)
|
311
|
+
|
312
|
+
if port
|
313
|
+
return port
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
if forwarded_port = self.forwarded_port
|
318
|
+
return forwarded_port.first
|
319
|
+
end
|
320
|
+
|
321
|
+
if scheme = self.scheme
|
322
|
+
if port = DEFAULT_PORTS[self.scheme]
|
323
|
+
return port
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
self.server_port
|
328
|
+
end
|
329
|
+
|
330
|
+
def forwarded_for
|
331
|
+
if value = get_header(HTTP_X_FORWARDED_FOR)
|
332
|
+
split_header(value).map do |authority|
|
333
|
+
split_authority(wrap_ipv6(authority))[1]
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def forwarded_port
|
339
|
+
if value = get_header(HTTP_X_FORWARDED_PORT)
|
340
|
+
split_header(value).map(&:to_i)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def forwarded_authority
|
345
|
+
if value = get_header(HTTP_X_FORWARDED_HOST)
|
346
|
+
wrap_ipv6(split_header(value).first)
|
269
347
|
end
|
270
348
|
end
|
271
349
|
|
272
350
|
def ssl?
|
273
|
-
scheme == 'https'
|
351
|
+
scheme == 'https' || scheme == 'wss'
|
274
352
|
end
|
275
353
|
|
276
354
|
def ip
|
277
|
-
remote_addrs =
|
355
|
+
remote_addrs = split_header(get_header('REMOTE_ADDR'))
|
278
356
|
remote_addrs = reject_trusted_ip_addresses(remote_addrs)
|
279
357
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
358
|
+
if remote_addrs.any?
|
359
|
+
remote_addrs.first
|
360
|
+
else
|
361
|
+
forwarded_ips = self.forwarded_for
|
284
362
|
|
285
|
-
|
363
|
+
reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
|
364
|
+
end
|
286
365
|
end
|
287
366
|
|
288
367
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
@@ -323,6 +402,7 @@ module Rack
|
|
323
402
|
def form_data?
|
324
403
|
type = media_type
|
325
404
|
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
405
|
+
|
326
406
|
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
327
407
|
end
|
328
408
|
|
@@ -377,8 +457,6 @@ module Rack
|
|
377
457
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
378
458
|
def params
|
379
459
|
self.GET.merge(self.POST)
|
380
|
-
rescue EOFError
|
381
|
-
self.GET.dup
|
382
460
|
end
|
383
461
|
|
384
462
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
@@ -412,9 +490,7 @@ module Rack
|
|
412
490
|
end
|
413
491
|
|
414
492
|
def base_url
|
415
|
-
|
416
|
-
url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
|
417
|
-
url
|
493
|
+
"#{scheme}://#{host_with_port}"
|
418
494
|
end
|
419
495
|
|
420
496
|
# Tries to return a remake of the original request URL as a string.
|
@@ -471,6 +547,20 @@ module Rack
|
|
471
547
|
|
472
548
|
def default_session; {}; end
|
473
549
|
|
550
|
+
# Assist with compatibility when processing `X-Forwarded-For`.
|
551
|
+
def wrap_ipv6(host)
|
552
|
+
# Even thought IPv6 addresses should be wrapped in square brackets,
|
553
|
+
# sometimes this is not done in various legacy/underspecified headers.
|
554
|
+
# So we try to fix this situation for compatibility reasons.
|
555
|
+
|
556
|
+
# Try to detect IPv6 addresses which aren't escaped yet:
|
557
|
+
if !host.start_with?('[') && host.count(':') > 1
|
558
|
+
"[#{host}]"
|
559
|
+
else
|
560
|
+
host
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
474
564
|
def parse_http_accept_header(header)
|
475
565
|
header.to_s.split(/\s*,\s*/).map do |part|
|
476
566
|
attribute, parameters = part.split(/\s*;\s*/, 2)
|
@@ -494,27 +584,39 @@ module Rack
|
|
494
584
|
Rack::Multipart.extract_multipart(self, query_parser)
|
495
585
|
end
|
496
586
|
|
497
|
-
def
|
498
|
-
|
499
|
-
end
|
500
|
-
|
501
|
-
|
502
|
-
#
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
587
|
+
def split_header(value)
|
588
|
+
value ? value.strip.split(/[,\s]+/) : []
|
589
|
+
end
|
590
|
+
|
591
|
+
AUTHORITY = /
|
592
|
+
# The host:
|
593
|
+
(?<host>
|
594
|
+
# An IPv6 address:
|
595
|
+
(\[(?<ip6>.*)\])
|
596
|
+
|
|
597
|
+
# An IPv4 address:
|
598
|
+
(?<ip4>[\d\.]+)
|
599
|
+
|
|
600
|
+
# A hostname:
|
601
|
+
(?<name>[a-zA-Z0-9\.\-]+)
|
602
|
+
)
|
603
|
+
# The optional port:
|
604
|
+
(:(?<port>\d+))?
|
605
|
+
/x
|
606
|
+
|
607
|
+
private_constant :AUTHORITY
|
608
|
+
|
609
|
+
def split_authority(authority)
|
610
|
+
if match = AUTHORITY.match(authority)
|
611
|
+
if address = match[:ip6]
|
612
|
+
return match[:host], address, match[:port]&.to_i
|
613
|
+
else
|
614
|
+
return match[:host], match[:host], match[:port]&.to_i
|
615
|
+
end
|
515
616
|
end
|
516
617
|
|
517
|
-
|
618
|
+
# Give up!
|
619
|
+
return authority, authority, nil
|
518
620
|
end
|
519
621
|
|
520
622
|
def reject_trusted_ip_addresses(ip_addresses)
|
@@ -539,12 +641,6 @@ module Rack
|
|
539
641
|
end
|
540
642
|
end
|
541
643
|
end
|
542
|
-
|
543
|
-
def extract_port(uri)
|
544
|
-
if (colon_index = uri.index(':'))
|
545
|
-
uri[colon_index + 1, uri.length]
|
546
|
-
end
|
547
|
-
end
|
548
644
|
end
|
549
645
|
|
550
646
|
include Env
|