rack 2.0.8 → 2.2.2
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 +690 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +152 -148
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +29 -5
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack.rb +67 -73
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- 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 +4 -2
- data/lib/rack/auth/digest/request.rb +5 -3
- data/lib/rack/body_proxy.rb +15 -14
- data/lib/rack/builder.rb +116 -23
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +68 -20
- data/lib/rack/common_logger.rb +33 -25
- data/lib/rack/conditional_get.rb +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +8 -7
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +59 -34
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +19 -20
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +2 -3
- data/lib/rack/handler/fastcgi.rb +4 -4
- data/lib/rack/handler/lsws.rb +3 -3
- data/lib/rack/handler/scgi.rb +9 -8
- data/lib/rack/handler/thin.rb +3 -3
- data/lib/rack/handler/webrick.rb +15 -6
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +71 -25
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +2 -1
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +97 -20
- data/lib/rack/multipart.rb +6 -4
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +54 -56
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +53 -28
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +220 -61
- data/lib/rack/response.rb +127 -44
- data/lib/rack/rewindable_input.rb +4 -3
- data/lib/rack/runtime.rb +6 -4
- data/lib/rack/sendfile.rb +13 -9
- data/lib/rack/server.rb +95 -24
- data/lib/rack/session/abstract/id.rb +36 -23
- data/lib/rack/session/cookie.rb +11 -12
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +5 -3
- data/lib/rack/show_exceptions.rb +21 -17
- data/lib/rack/show_status.rb +9 -9
- data/lib/rack/static.rb +23 -11
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +12 -6
- data/lib/rack/utils.rb +98 -109
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +40 -28
- metadata +36 -177
- data/HISTORY.md +0 -505
- data/test/builder/an_underscore_app.rb +0 -5
- 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 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- 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_encoded_words +0 -7
- 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_single_quote +0 -7
- 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/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- 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 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -510
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.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
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Multipart
|
3
5
|
class UploadedFile
|
@@ -7,17 +9,23 @@ module Rack
|
|
7
9
|
# The content type of the "uploaded" file
|
8
10
|
attr_accessor :content_type
|
9
11
|
|
10
|
-
def initialize(
|
11
|
-
|
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
|
12
24
|
@content_type = content_type
|
13
|
-
@original_filename = ::File.basename(path)
|
14
|
-
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
15
|
-
@tempfile.binmode if binary
|
16
|
-
FileUtils.copy_file(path, @tempfile.path)
|
17
25
|
end
|
18
26
|
|
19
27
|
def path
|
20
|
-
@tempfile.path
|
28
|
+
@tempfile.path if @tempfile.respond_to?(:path)
|
21
29
|
end
|
22
30
|
alias_method :local_path, :path
|
23
31
|
|
data/lib/rack/null_logger.rb
CHANGED
data/lib/rack/query_parser.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class QueryParser
|
5
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
6
|
+
|
3
7
|
DEFAULT_SEP = /[&;] */n
|
4
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
5
9
|
|
@@ -36,7 +40,7 @@ module Rack
|
|
36
40
|
|
37
41
|
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
38
42
|
next if p.empty?
|
39
|
-
k, v = p.split('='
|
43
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
40
44
|
|
41
45
|
if cur = params[k]
|
42
46
|
if cur.class == Array
|
@@ -49,7 +53,7 @@ module Rack
|
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
return params.
|
56
|
+
return params.to_h
|
53
57
|
end
|
54
58
|
|
55
59
|
# parse_nested_query expands a query string into structural types. Supported
|
@@ -58,18 +62,19 @@ module Rack
|
|
58
62
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
59
63
|
# case.
|
60
64
|
def parse_nested_query(qs, d = nil)
|
61
|
-
return {} if qs.nil? || qs.empty?
|
62
65
|
params = make_params
|
63
66
|
|
64
|
-
|
65
|
-
|
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) }
|
66
70
|
|
67
|
-
|
71
|
+
normalize_params(params, k, v, param_depth_limit)
|
72
|
+
end
|
68
73
|
end
|
69
74
|
|
70
|
-
return params.
|
75
|
+
return params.to_h
|
71
76
|
rescue ArgumentError => e
|
72
|
-
raise InvalidParameterError, e.message
|
77
|
+
raise InvalidParameterError, e.message, e.backtrace
|
73
78
|
end
|
74
79
|
|
75
80
|
# normalize_params recursively expands parameters into structural types. If
|
@@ -79,22 +84,22 @@ module Rack
|
|
79
84
|
raise RangeError if depth <= 0
|
80
85
|
|
81
86
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
82
|
-
k = $1 || ''
|
83
|
-
after = $' || ''
|
87
|
+
k = $1 || ''
|
88
|
+
after = $' || ''
|
84
89
|
|
85
90
|
if k.empty?
|
86
|
-
if !v.nil? && name == "[]"
|
91
|
+
if !v.nil? && name == "[]"
|
87
92
|
return Array(v)
|
88
93
|
else
|
89
94
|
return
|
90
95
|
end
|
91
96
|
end
|
92
97
|
|
93
|
-
if after == ''
|
98
|
+
if after == ''
|
94
99
|
params[k] = v
|
95
|
-
elsif after == "["
|
100
|
+
elsif after == "["
|
96
101
|
params[name] = v
|
97
|
-
elsif after == "[]"
|
102
|
+
elsif after == "[]"
|
98
103
|
params[k] ||= []
|
99
104
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
100
105
|
params[k] << v
|
@@ -135,7 +140,7 @@ module Rack
|
|
135
140
|
end
|
136
141
|
|
137
142
|
def params_hash_has_key?(hash, key)
|
138
|
-
return false if
|
143
|
+
return false if /\[\]/.match?(key)
|
139
144
|
|
140
145
|
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
141
146
|
next h if part == ''
|
@@ -171,22 +176,42 @@ module Rack
|
|
171
176
|
@params.key?(key)
|
172
177
|
end
|
173
178
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
179
|
+
# Recursively unwraps nested `Params` objects and constructs an object
|
180
|
+
# of the same shape, but using the objects' internal representations
|
181
|
+
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
182
|
+
# purely of Ruby primitives.
|
183
|
+
#
|
184
|
+
# Mutation warning!
|
185
|
+
#
|
186
|
+
# 1. This method mutates the internal representation of the `Params`
|
187
|
+
# objects in order to save object allocations.
|
188
|
+
#
|
189
|
+
# 2. The value you get back is a reference to the internal hash
|
190
|
+
# representation, not a copy.
|
191
|
+
#
|
192
|
+
# 3. Because the `Params` object's internal representation is mutable
|
193
|
+
# through the `#[]=` method, it is not thread safe. The result of
|
194
|
+
# getting the hash representation while another thread is adding a
|
195
|
+
# key to it is non-deterministic.
|
196
|
+
#
|
197
|
+
def to_h
|
198
|
+
@params.each do |key, value|
|
199
|
+
case value
|
200
|
+
when self
|
201
|
+
# Handle circular references gracefully.
|
202
|
+
@params[key] = @params
|
203
|
+
when Params
|
204
|
+
@params[key] = value.to_h
|
205
|
+
when Array
|
206
|
+
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
207
|
+
else
|
208
|
+
# Ignore anything that is not a `Params` object or
|
209
|
+
# a collection that can contain one.
|
186
210
|
end
|
187
211
|
end
|
188
|
-
|
212
|
+
@params
|
189
213
|
end
|
214
|
+
alias_method :to_params_hash, :to_h
|
190
215
|
end
|
191
216
|
end
|
192
217
|
end
|
data/lib/rack/recursive.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
|
3
5
|
module Rack
|
@@ -10,14 +12,14 @@ module Rack
|
|
10
12
|
class ForwardRequest < Exception
|
11
13
|
attr_reader :url, :env
|
12
14
|
|
13
|
-
def initialize(url, env={})
|
15
|
+
def initialize(url, env = {})
|
14
16
|
@url = URI(url)
|
15
17
|
@env = env
|
16
18
|
|
17
|
-
@env[PATH_INFO]
|
18
|
-
@env[QUERY_STRING]
|
19
|
-
@env[HTTP_HOST]
|
20
|
-
@env[
|
19
|
+
@env[PATH_INFO] = @url.path
|
20
|
+
@env[QUERY_STRING] = @url.query if @url.query
|
21
|
+
@env[HTTP_HOST] = @url.host if @url.host
|
22
|
+
@env[HTTP_PORT] = @url.port if @url.port
|
21
23
|
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
22
24
|
|
23
25
|
super "forwarding to #{url}"
|
data/lib/rack/reloader.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
|
4
|
+
# Rack::Reloader is subject to the terms of an MIT-style license.
|
5
|
+
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
4
6
|
|
5
7
|
require 'pathname'
|
6
8
|
|
@@ -20,6 +22,8 @@ module Rack
|
|
20
22
|
# It is performing a check/reload cycle at the start of every request, but
|
21
23
|
# also respects a cool down time, during which nothing will be done.
|
22
24
|
class Reloader
|
25
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
26
|
+
|
23
27
|
def initialize(app, cooldown = 10, backend = Stat)
|
24
28
|
@app = app
|
25
29
|
@cooldown = cooldown
|
@@ -69,7 +73,7 @@ module Rack
|
|
69
73
|
paths = ['./', *$LOAD_PATH].uniq
|
70
74
|
|
71
75
|
files.map{|file|
|
72
|
-
next if
|
76
|
+
next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
|
73
77
|
|
74
78
|
found, stat = figure_path(file, paths)
|
75
79
|
next unless found && stat && mtime = stat.mtime
|
data/lib/rack/request.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'rack/media_type'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Rack
|
5
4
|
# Rack::Request provides a convenient interface to a Rack
|
@@ -11,7 +10,18 @@ module Rack
|
|
11
10
|
# req.params["data"]
|
12
11
|
|
13
12
|
class Request
|
14
|
-
|
13
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :ip_filter
|
17
|
+
end
|
18
|
+
|
19
|
+
self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
|
20
|
+
ALLOWED_SCHEMES = %w(https http).freeze
|
21
|
+
SCHEME_WHITELIST = ALLOWED_SCHEMES
|
22
|
+
if Object.respond_to?(:deprecate_constant)
|
23
|
+
deprecate_constant :SCHEME_WHITELIST
|
24
|
+
end
|
15
25
|
|
16
26
|
def initialize(env)
|
17
27
|
@params = nil
|
@@ -78,7 +88,7 @@ module Rack
|
|
78
88
|
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
79
89
|
#
|
80
90
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
81
|
-
def add_header
|
91
|
+
def add_header(key, v)
|
82
92
|
if v.nil?
|
83
93
|
get_header key
|
84
94
|
elsif has_header? key
|
@@ -100,7 +110,7 @@ module Rack
|
|
100
110
|
|
101
111
|
module Helpers
|
102
112
|
# The set of form-data media-types. Requests that do not indicate
|
103
|
-
# one of the media types
|
113
|
+
# one of the media types present in this list will not be eligible
|
104
114
|
# for form-data / param parsing.
|
105
115
|
FORM_DATA_MEDIA_TYPES = [
|
106
116
|
'application/x-www-form-urlencoded',
|
@@ -108,7 +118,7 @@ module Rack
|
|
108
118
|
]
|
109
119
|
|
110
120
|
# The set of media-types. Requests that do not indicate
|
111
|
-
# one of the media types
|
121
|
+
# one of the media types present in this list will not be eligible
|
112
122
|
# for param parsing like soap attachments or generic multiparts
|
113
123
|
PARSEABLE_DATA_MEDIA_TYPES = [
|
114
124
|
'multipart/related',
|
@@ -119,11 +129,23 @@ module Rack
|
|
119
129
|
# to include the port in a generated URI.
|
120
130
|
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
121
131
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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.
|
139
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
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'
|
127
149
|
|
128
150
|
def body; get_header(RACK_INPUT) end
|
129
151
|
def script_name; get_header(SCRIPT_NAME).to_s end
|
@@ -159,10 +181,10 @@ module Rack
|
|
159
181
|
def delete?; request_method == DELETE end
|
160
182
|
|
161
183
|
# Checks the HTTP request method (or verb) to see if it was of type GET
|
162
|
-
def get?; request_method == GET
|
184
|
+
def get?; request_method == GET end
|
163
185
|
|
164
186
|
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
165
|
-
def head?; request_method == HEAD
|
187
|
+
def head?; request_method == HEAD end
|
166
188
|
|
167
189
|
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
168
190
|
def options?; request_method == OPTIONS end
|
@@ -197,19 +219,52 @@ module Rack
|
|
197
219
|
end
|
198
220
|
end
|
199
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.
|
200
227
|
def authority
|
201
|
-
|
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
|
202
254
|
end
|
203
255
|
|
204
256
|
def cookies
|
205
|
-
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |
|
206
|
-
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)
|
207
266
|
end
|
208
|
-
string = get_header HTTP_COOKIE
|
209
267
|
|
210
|
-
return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
|
211
|
-
hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
|
212
|
-
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
213
268
|
hash
|
214
269
|
end
|
215
270
|
|
@@ -222,46 +277,101 @@ module Rack
|
|
222
277
|
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
223
278
|
end
|
224
279
|
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
228
290
|
else
|
229
|
-
|
291
|
+
authority
|
230
292
|
end
|
231
293
|
end
|
232
294
|
|
295
|
+
# Returns a formatted host, suitable for being used in a URI.
|
233
296
|
def host
|
234
|
-
|
235
|
-
|
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]
|
236
306
|
end
|
237
307
|
|
238
308
|
def port
|
239
|
-
if
|
240
|
-
port.
|
241
|
-
|
242
|
-
port
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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)
|
249
347
|
end
|
250
348
|
end
|
251
349
|
|
252
350
|
def ssl?
|
253
|
-
scheme == 'https'
|
351
|
+
scheme == 'https' || scheme == 'wss'
|
254
352
|
end
|
255
353
|
|
256
354
|
def ip
|
257
|
-
|
258
|
-
|
355
|
+
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
356
|
+
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
259
357
|
|
260
|
-
|
358
|
+
unless external_addresses.empty?
|
359
|
+
return external_addresses.first
|
360
|
+
end
|
261
361
|
|
262
|
-
|
362
|
+
if forwarded_for = self.forwarded_for
|
363
|
+
unless forwarded_for.empty?
|
364
|
+
# The forwarded for addresses are ordered: client, proxy1, proxy2.
|
365
|
+
# So we reject all the trusted addresses (proxy*) and return the
|
366
|
+
# last client. Or if we trust everyone, we just return the first
|
367
|
+
# address.
|
368
|
+
return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
|
369
|
+
end
|
370
|
+
end
|
263
371
|
|
264
|
-
|
372
|
+
# If all the addresses are trusted, and we aren't forwarded, just return
|
373
|
+
# the first remote address, which represents the source of the request.
|
374
|
+
remote_addresses.first
|
265
375
|
end
|
266
376
|
|
267
377
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
@@ -302,6 +412,7 @@ module Rack
|
|
302
412
|
def form_data?
|
303
413
|
type = media_type
|
304
414
|
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
415
|
+
|
305
416
|
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
306
417
|
end
|
307
418
|
|
@@ -337,7 +448,7 @@ module Rack
|
|
337
448
|
|
338
449
|
# Fix for Safari Ajax postings that always append \0
|
339
450
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
340
|
-
form_vars.slice!(-1) if form_vars
|
451
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
341
452
|
|
342
453
|
set_header RACK_REQUEST_FORM_VARS, form_vars
|
343
454
|
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
@@ -356,8 +467,6 @@ module Rack
|
|
356
467
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
357
468
|
def params
|
358
469
|
self.GET.merge(self.POST)
|
359
|
-
rescue EOFError
|
360
|
-
self.GET.dup
|
361
470
|
end
|
362
471
|
|
363
472
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
@@ -386,13 +495,12 @@ module Rack
|
|
386
495
|
#
|
387
496
|
# <tt>env['rack.input']</tt> is not touched.
|
388
497
|
def delete_param(k)
|
389
|
-
|
498
|
+
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
499
|
+
post_value || get_value
|
390
500
|
end
|
391
501
|
|
392
502
|
def base_url
|
393
|
-
|
394
|
-
url << ":#{port}" if port != DEFAULT_PORTS[scheme]
|
395
|
-
url
|
503
|
+
"#{scheme}://#{host_with_port}"
|
396
504
|
end
|
397
505
|
|
398
506
|
# Tries to return a remake of the original request URL as a string.
|
@@ -417,7 +525,7 @@ module Rack
|
|
417
525
|
end
|
418
526
|
|
419
527
|
def trusted_proxy?(ip)
|
420
|
-
ip
|
528
|
+
Rack::Request.ip_filter.call(ip)
|
421
529
|
end
|
422
530
|
|
423
531
|
# shortcut for <tt>request.params[key]</tt>
|
@@ -449,6 +557,20 @@ module Rack
|
|
449
557
|
|
450
558
|
def default_session; {}; end
|
451
559
|
|
560
|
+
# Assist with compatibility when processing `X-Forwarded-For`.
|
561
|
+
def wrap_ipv6(host)
|
562
|
+
# Even thought IPv6 addresses should be wrapped in square brackets,
|
563
|
+
# sometimes this is not done in various legacy/underspecified headers.
|
564
|
+
# So we try to fix this situation for compatibility reasons.
|
565
|
+
|
566
|
+
# Try to detect IPv6 addresses which aren't escaped yet:
|
567
|
+
if !host.start_with?('[') && host.count(':') > 1
|
568
|
+
"[#{host}]"
|
569
|
+
else
|
570
|
+
host
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
452
574
|
def parse_http_accept_header(header)
|
453
575
|
header.to_s.split(/\s*,\s*/).map do |part|
|
454
576
|
attribute, parameters = part.split(/\s*;\s*/, 2)
|
@@ -464,7 +586,7 @@ module Rack
|
|
464
586
|
Utils.default_query_parser
|
465
587
|
end
|
466
588
|
|
467
|
-
def parse_query(qs, d='&')
|
589
|
+
def parse_query(qs, d = '&')
|
468
590
|
query_parser.parse_nested_query(qs, d)
|
469
591
|
end
|
470
592
|
|
@@ -472,8 +594,39 @@ module Rack
|
|
472
594
|
Rack::Multipart.extract_multipart(self, query_parser)
|
473
595
|
end
|
474
596
|
|
475
|
-
def
|
476
|
-
|
597
|
+
def split_header(value)
|
598
|
+
value ? value.strip.split(/[,\s]+/) : []
|
599
|
+
end
|
600
|
+
|
601
|
+
AUTHORITY = /^
|
602
|
+
# The host:
|
603
|
+
(?<host>
|
604
|
+
# An IPv6 address:
|
605
|
+
(\[(?<ip6>.*)\])
|
606
|
+
|
|
607
|
+
# An IPv4 address:
|
608
|
+
(?<ip4>[\d\.]+)
|
609
|
+
|
|
610
|
+
# A hostname:
|
611
|
+
(?<name>[a-zA-Z0-9\.\-]+)
|
612
|
+
)
|
613
|
+
# The optional port:
|
614
|
+
(:(?<port>\d+))?
|
615
|
+
$/x
|
616
|
+
|
617
|
+
private_constant :AUTHORITY
|
618
|
+
|
619
|
+
def split_authority(authority)
|
620
|
+
if match = AUTHORITY.match(authority)
|
621
|
+
if address = match[:ip6]
|
622
|
+
return match[:host], address, match[:port]&.to_i
|
623
|
+
else
|
624
|
+
return match[:host], match[:host], match[:port]&.to_i
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
# Give up!
|
629
|
+
return authority, authority, nil
|
477
630
|
end
|
478
631
|
|
479
632
|
def reject_trusted_ip_addresses(ip_addresses)
|
@@ -481,16 +634,22 @@ module Rack
|
|
481
634
|
end
|
482
635
|
|
483
636
|
def forwarded_scheme
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
]
|
637
|
+
allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
|
638
|
+
allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
|
639
|
+
end
|
488
640
|
|
489
|
-
|
490
|
-
|
491
|
-
|
641
|
+
def allowed_scheme(header)
|
642
|
+
header if ALLOWED_SCHEMES.include?(header)
|
643
|
+
end
|
492
644
|
|
493
|
-
|
645
|
+
def extract_proto_header(header)
|
646
|
+
if header
|
647
|
+
if (comma_index = header.index(','))
|
648
|
+
header[0, comma_index]
|
649
|
+
else
|
650
|
+
header
|
651
|
+
end
|
652
|
+
end
|
494
653
|
end
|
495
654
|
end
|
496
655
|
|