rack 1.6.11 → 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 +675 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +157 -163
- data/Rakefile +38 -32
- data/{SPEC → SPEC.rdoc} +41 -13
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +6 -2
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +6 -4
- data/lib/rack/body_proxy.rb +21 -15
- data/lib/rack/builder.rb +119 -26
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +70 -22
- data/lib/rack/common_logger.rb +80 -0
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +9 -8
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +60 -70
- data/lib/rack/directory.rb +117 -85
- data/lib/rack/etag.rb +9 -7
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +17 -19
- data/lib/rack/handler/fastcgi.rb +17 -18
- data/lib/rack/handler/lsws.rb +14 -14
- data/lib/rack/handler/scgi.rb +22 -21
- data/lib/rack/handler/thin.rb +20 -11
- data/lib/rack/handler/webrick.rb +39 -32
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +16 -18
- data/lib/rack/lint.rb +110 -64
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +17 -11
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +124 -65
- data/lib/rack/multipart/generator.rb +20 -16
- data/lib/rack/multipart/parser.rb +273 -162
- data/lib/rack/multipart/uploaded_file.rb +15 -8
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +217 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +543 -305
- data/lib/rack/response.rb +244 -88
- data/lib/rack/rewindable_input.rb +5 -15
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +17 -15
- data/lib/rack/server.rb +125 -47
- data/lib/rack/session/abstract/id.rb +216 -93
- data/lib/rack/session/cookie.rb +47 -31
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +26 -17
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +8 -8
- data/lib/rack/static.rb +48 -11
- data/lib/rack/tempfile_reaper.rb +3 -3
- data/lib/rack/urlmap.rb +26 -19
- data/lib/rack/utils.rb +208 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- 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/commonlogger.rb +0 -72
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -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/request.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
# Rack::Request provides a convenient interface to a Rack
|
@@ -10,369 +10,555 @@ module Rack
|
|
10
10
|
# req.params["data"]
|
11
11
|
|
12
12
|
class Request
|
13
|
-
|
14
|
-
attr_reader :env
|
13
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
def initialize(env)
|
19
|
-
@env = env
|
15
|
+
class << self
|
16
|
+
attr_accessor :ip_filter
|
20
17
|
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
28
25
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
26
|
+
def initialize(env)
|
27
|
+
@params = nil
|
28
|
+
super(env)
|
32
29
|
end
|
33
30
|
|
34
|
-
def
|
35
|
-
|
36
|
-
def logger; @env['rack.logger'] end
|
37
|
-
|
38
|
-
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
39
|
-
# without any media type parameters. e.g., when CONTENT_TYPE is
|
40
|
-
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
41
|
-
#
|
42
|
-
# For more information on the use of media types in HTTP, see:
|
43
|
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
44
|
-
def media_type
|
45
|
-
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
|
31
|
+
def params
|
32
|
+
@params ||= super
|
46
33
|
end
|
47
34
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# this method responds with the following Hash:
|
52
|
-
# { 'charset' => 'utf-8' }
|
53
|
-
def media_type_params
|
54
|
-
return {} if content_type.nil?
|
55
|
-
Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
|
56
|
-
collect { |s| s.split('=', 2) }.
|
57
|
-
map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
|
35
|
+
def update_param(k, v)
|
36
|
+
super
|
37
|
+
@params = nil
|
58
38
|
end
|
59
39
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def content_charset
|
65
|
-
media_type_params['charset']
|
40
|
+
def delete_param(k)
|
41
|
+
v = super
|
42
|
+
@params = nil
|
43
|
+
v
|
66
44
|
end
|
67
45
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
else
|
76
|
-
@env["rack.url_scheme"]
|
46
|
+
module Env
|
47
|
+
# The environment of the request.
|
48
|
+
attr_reader :env
|
49
|
+
|
50
|
+
def initialize(env)
|
51
|
+
@env = env
|
52
|
+
super()
|
77
53
|
end
|
78
|
-
end
|
79
54
|
|
80
|
-
|
81
|
-
|
82
|
-
|
55
|
+
# Predicate method to test to see if `name` has been set as request
|
56
|
+
# specific data
|
57
|
+
def has_header?(name)
|
58
|
+
@env.key? name
|
59
|
+
end
|
83
60
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
else
|
88
|
-
@env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
|
61
|
+
# Get a request specific value for `name`.
|
62
|
+
def get_header(name)
|
63
|
+
@env[name]
|
89
64
|
end
|
90
|
-
end
|
91
65
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
66
|
+
# If a block is given, it yields to the block if the value hasn't been set
|
67
|
+
# on the request.
|
68
|
+
def fetch_header(name, &block)
|
69
|
+
@env.fetch(name, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Loops through each key / value pair in the request specific data.
|
73
|
+
def each_header(&block)
|
74
|
+
@env.each(&block)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Set a request specific value for `name` to `v`
|
78
|
+
def set_header(name, v)
|
79
|
+
@env[name] = v
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add a header that may have multiple values.
|
83
|
+
#
|
84
|
+
# Example:
|
85
|
+
# request.add_header 'Accept', 'image/png'
|
86
|
+
# request.add_header 'Accept', '*/*'
|
87
|
+
#
|
88
|
+
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
89
|
+
#
|
90
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
91
|
+
def add_header(key, v)
|
92
|
+
if v.nil?
|
93
|
+
get_header key
|
94
|
+
elsif has_header? key
|
95
|
+
set_header key, "#{get_header key},#{v}"
|
96
|
+
else
|
97
|
+
set_header key, v
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Delete a request specific value for `name`.
|
102
|
+
def delete_header(name)
|
103
|
+
@env.delete name
|
103
104
|
end
|
104
|
-
end
|
105
105
|
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
def initialize_copy(other)
|
107
|
+
@env = other.env.dup
|
108
|
+
end
|
109
109
|
end
|
110
110
|
|
111
|
-
|
112
|
-
|
111
|
+
module Helpers
|
112
|
+
# The set of form-data media-types. Requests that do not indicate
|
113
|
+
# one of the media types present in this list will not be eligible
|
114
|
+
# for form-data / param parsing.
|
115
|
+
FORM_DATA_MEDIA_TYPES = [
|
116
|
+
'application/x-www-form-urlencoded',
|
117
|
+
'multipart/form-data'
|
118
|
+
]
|
119
|
+
|
120
|
+
# The set of media-types. Requests that do not indicate
|
121
|
+
# one of the media types present in this list will not be eligible
|
122
|
+
# for param parsing like soap attachments or generic multiparts
|
123
|
+
PARSEABLE_DATA_MEDIA_TYPES = [
|
124
|
+
'multipart/related',
|
125
|
+
'multipart/mixed'
|
126
|
+
]
|
127
|
+
|
128
|
+
# Default ports depending on scheme. Used to decide whether or not
|
129
|
+
# to include the port in a generated URI.
|
130
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
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'
|
113
137
|
|
138
|
+
# The value of the scheme sent to the proxy.
|
139
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
114
140
|
|
115
|
-
|
116
|
-
|
141
|
+
# The protocol used to connect to the proxy.
|
142
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
117
143
|
|
118
|
-
|
119
|
-
|
144
|
+
# The port used to connect to the proxy.
|
145
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
120
146
|
|
121
|
-
|
122
|
-
|
147
|
+
# Another way for specifing https scheme was used.
|
148
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
123
149
|
|
124
|
-
|
125
|
-
|
150
|
+
def body; get_header(RACK_INPUT) end
|
151
|
+
def script_name; get_header(SCRIPT_NAME).to_s end
|
152
|
+
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
|
126
153
|
|
127
|
-
|
128
|
-
|
154
|
+
def path_info; get_header(PATH_INFO).to_s end
|
155
|
+
def path_info=(s); set_header(PATH_INFO, s.to_s) end
|
129
156
|
|
130
|
-
|
131
|
-
|
157
|
+
def request_method; get_header(REQUEST_METHOD) end
|
158
|
+
def query_string; get_header(QUERY_STRING).to_s end
|
159
|
+
def content_length; get_header('CONTENT_LENGTH') end
|
160
|
+
def logger; get_header(RACK_LOGGER) end
|
161
|
+
def user_agent; get_header('HTTP_USER_AGENT') end
|
162
|
+
def multithread?; get_header(RACK_MULTITHREAD) end
|
132
163
|
|
133
|
-
|
134
|
-
|
164
|
+
# the referer of the client
|
165
|
+
def referer; get_header('HTTP_REFERER') end
|
166
|
+
alias referrer referer
|
135
167
|
|
136
|
-
|
137
|
-
|
168
|
+
def session
|
169
|
+
fetch_header(RACK_SESSION) do |k|
|
170
|
+
set_header RACK_SESSION, default_session
|
171
|
+
end
|
172
|
+
end
|
138
173
|
|
139
|
-
|
140
|
-
|
174
|
+
def session_options
|
175
|
+
fetch_header(RACK_SESSION_OPTIONS) do |k|
|
176
|
+
set_header RACK_SESSION_OPTIONS, {}
|
177
|
+
end
|
178
|
+
end
|
141
179
|
|
142
|
-
|
143
|
-
|
180
|
+
# Checks the HTTP request method (or verb) to see if it was of type DELETE
|
181
|
+
def delete?; request_method == DELETE end
|
144
182
|
|
183
|
+
# Checks the HTTP request method (or verb) to see if it was of type GET
|
184
|
+
def get?; request_method == GET end
|
145
185
|
|
146
|
-
|
147
|
-
|
148
|
-
# for form-data / param parsing.
|
149
|
-
FORM_DATA_MEDIA_TYPES = [
|
150
|
-
'application/x-www-form-urlencoded',
|
151
|
-
'multipart/form-data'
|
152
|
-
]
|
186
|
+
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
187
|
+
def head?; request_method == HEAD end
|
153
188
|
|
154
|
-
|
155
|
-
|
156
|
-
# for param parsing like soap attachments or generic multiparts
|
157
|
-
PARSEABLE_DATA_MEDIA_TYPES = [
|
158
|
-
'multipart/related',
|
159
|
-
'multipart/mixed'
|
160
|
-
]
|
189
|
+
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
190
|
+
def options?; request_method == OPTIONS end
|
161
191
|
|
162
|
-
|
163
|
-
|
164
|
-
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
192
|
+
# Checks the HTTP request method (or verb) to see if it was of type LINK
|
193
|
+
def link?; request_method == LINK end
|
165
194
|
|
166
|
-
|
167
|
-
|
168
|
-
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
169
|
-
# list of form-data media types can be modified through the
|
170
|
-
# +FORM_DATA_MEDIA_TYPES+ array.
|
171
|
-
#
|
172
|
-
# A request body is also assumed to contain form-data when no
|
173
|
-
# Content-Type header is provided and the request_method is POST.
|
174
|
-
def form_data?
|
175
|
-
type = media_type
|
176
|
-
meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
|
177
|
-
(meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
178
|
-
end
|
195
|
+
# Checks the HTTP request method (or verb) to see if it was of type PATCH
|
196
|
+
def patch?; request_method == PATCH end
|
179
197
|
|
180
|
-
|
181
|
-
|
182
|
-
def parseable_data?
|
183
|
-
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
184
|
-
end
|
198
|
+
# Checks the HTTP request method (or verb) to see if it was of type POST
|
199
|
+
def post?; request_method == POST end
|
185
200
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
201
|
+
# Checks the HTTP request method (or verb) to see if it was of type PUT
|
202
|
+
def put?; request_method == PUT end
|
203
|
+
|
204
|
+
# Checks the HTTP request method (or verb) to see if it was of type TRACE
|
205
|
+
def trace?; request_method == TRACE end
|
206
|
+
|
207
|
+
# Checks the HTTP request method (or verb) to see if it was of type UNLINK
|
208
|
+
def unlink?; request_method == UNLINK end
|
209
|
+
|
210
|
+
def scheme
|
211
|
+
if get_header(HTTPS) == 'on'
|
212
|
+
'https'
|
213
|
+
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
|
214
|
+
'https'
|
215
|
+
elsif forwarded_scheme
|
216
|
+
forwarded_scheme
|
217
|
+
else
|
218
|
+
get_header(RACK_URL_SCHEME)
|
219
|
+
end
|
194
220
|
end
|
195
|
-
end
|
196
221
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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.
|
227
|
+
def authority
|
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
|
218
243
|
end
|
219
|
-
@env["rack.request.form_input"] = @env["rack.input"]
|
220
|
-
@env["rack.request.form_hash"]
|
221
|
-
else
|
222
|
-
{}
|
223
244
|
end
|
224
|
-
end
|
225
245
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
def params
|
230
|
-
@params ||= self.GET.merge(self.POST)
|
231
|
-
rescue EOFError
|
232
|
-
self.GET.dup
|
233
|
-
end
|
246
|
+
def server_name
|
247
|
+
get_header(SERVER_NAME)
|
248
|
+
end
|
234
249
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
# env['rack.input'] is not touched.
|
240
|
-
def update_param(k, v)
|
241
|
-
found = false
|
242
|
-
if self.GET.has_key?(k)
|
243
|
-
found = true
|
244
|
-
self.GET[k] = v
|
250
|
+
def server_port
|
251
|
+
if port = get_header(SERVER_PORT)
|
252
|
+
Integer(port)
|
253
|
+
end
|
245
254
|
end
|
246
|
-
|
247
|
-
|
248
|
-
|
255
|
+
|
256
|
+
def cookies
|
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)
|
266
|
+
end
|
267
|
+
|
268
|
+
hash
|
249
269
|
end
|
250
|
-
|
251
|
-
|
270
|
+
|
271
|
+
def content_type
|
272
|
+
content_type = get_header('CONTENT_TYPE')
|
273
|
+
content_type.nil? || content_type.empty? ? nil : content_type
|
252
274
|
end
|
253
|
-
@params = nil
|
254
|
-
nil
|
255
|
-
end
|
256
275
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
#
|
261
|
-
# env['rack.input'] is not touched.
|
262
|
-
def delete_param(k)
|
263
|
-
v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
|
264
|
-
@params = nil
|
265
|
-
v
|
266
|
-
end
|
276
|
+
def xhr?
|
277
|
+
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
278
|
+
end
|
267
279
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
280
|
+
# The `HTTP_HOST` header.
|
281
|
+
def host_authority
|
282
|
+
get_header(HTTP_HOST)
|
283
|
+
end
|
272
284
|
|
273
|
-
|
274
|
-
|
275
|
-
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
276
|
-
def []=(key, value)
|
277
|
-
params[key.to_s] = value
|
278
|
-
end
|
285
|
+
def host_with_port(authority = self.authority)
|
286
|
+
host, _, port = split_authority(authority)
|
279
287
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
288
|
+
if port == DEFAULT_PORTS[self.scheme]
|
289
|
+
host
|
290
|
+
else
|
291
|
+
authority
|
292
|
+
end
|
293
|
+
end
|
284
294
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
alias referrer referer
|
295
|
+
# Returns a formatted host, suitable for being used in a URI.
|
296
|
+
def host
|
297
|
+
split_authority(self.authority)[0]
|
298
|
+
end
|
290
299
|
|
291
|
-
|
292
|
-
|
293
|
-
|
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]
|
306
|
+
end
|
294
307
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
return hash if string == @env["rack.request.cookie_string"]
|
300
|
-
hash.clear
|
301
|
-
|
302
|
-
# According to RFC 2109:
|
303
|
-
# If multiple cookies satisfy the criteria above, they are ordered in
|
304
|
-
# the Cookie header such that those with more specific Path attributes
|
305
|
-
# precede those with less specific. Ordering with respect to other
|
306
|
-
# attributes (e.g., Domain) is unspecified.
|
307
|
-
cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
|
308
|
-
cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
|
309
|
-
@env["rack.request.cookie_string"] = string
|
310
|
-
hash
|
311
|
-
end
|
308
|
+
def port
|
309
|
+
if authority = self.authority
|
310
|
+
_, _, port = split_authority(self.authority)
|
312
311
|
|
313
|
-
|
314
|
-
|
315
|
-
|
312
|
+
if port
|
313
|
+
return port
|
314
|
+
end
|
315
|
+
end
|
316
316
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
url
|
321
|
-
end
|
317
|
+
if forwarded_port = self.forwarded_port
|
318
|
+
return forwarded_port.first
|
319
|
+
end
|
322
320
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
321
|
+
if scheme = self.scheme
|
322
|
+
if port = DEFAULT_PORTS[self.scheme]
|
323
|
+
return port
|
324
|
+
end
|
325
|
+
end
|
327
326
|
|
328
|
-
|
329
|
-
|
330
|
-
end
|
327
|
+
self.server_port
|
328
|
+
end
|
331
329
|
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
335
337
|
|
336
|
-
|
337
|
-
|
338
|
-
|
338
|
+
def forwarded_port
|
339
|
+
if value = get_header(HTTP_X_FORWARDED_PORT)
|
340
|
+
split_header(value).map(&:to_i)
|
341
|
+
end
|
342
|
+
end
|
339
343
|
|
340
|
-
|
341
|
-
|
342
|
-
|
344
|
+
def forwarded_authority
|
345
|
+
if value = get_header(HTTP_X_FORWARDED_HOST)
|
346
|
+
wrap_ipv6(split_header(value).first)
|
347
|
+
end
|
348
|
+
end
|
343
349
|
|
344
|
-
|
345
|
-
|
346
|
-
|
350
|
+
def ssl?
|
351
|
+
scheme == 'https' || scheme == 'wss'
|
352
|
+
end
|
347
353
|
|
348
|
-
|
349
|
-
|
350
|
-
|
354
|
+
def ip
|
355
|
+
remote_addrs = split_header(get_header('REMOTE_ADDR'))
|
356
|
+
remote_addrs = reject_trusted_ip_addresses(remote_addrs)
|
351
357
|
|
352
|
-
|
358
|
+
if remote_addrs.any?
|
359
|
+
remote_addrs.first
|
360
|
+
else
|
361
|
+
forwarded_ips = self.forwarded_for
|
353
362
|
|
354
|
-
|
363
|
+
reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
|
364
|
+
end
|
365
|
+
end
|
355
366
|
|
356
|
-
|
357
|
-
|
367
|
+
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
368
|
+
# without any media type parameters. e.g., when CONTENT_TYPE is
|
369
|
+
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
370
|
+
#
|
371
|
+
# For more information on the use of media types in HTTP, see:
|
372
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
373
|
+
def media_type
|
374
|
+
MediaType.type(content_type)
|
375
|
+
end
|
358
376
|
|
359
|
-
|
360
|
-
|
361
|
-
|
377
|
+
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
378
|
+
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
379
|
+
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
380
|
+
# this method responds with the following Hash:
|
381
|
+
# { 'charset' => 'utf-8' }
|
382
|
+
def media_type_params
|
383
|
+
MediaType.params(content_type)
|
362
384
|
end
|
363
385
|
|
364
|
-
|
365
|
-
|
386
|
+
# The character set of the request body if a "charset" media type
|
387
|
+
# parameter was given, or nil if no "charset" was specified. Note
|
388
|
+
# that, per RFC2616, text/* media types that specify no explicit
|
389
|
+
# charset are to be considered ISO-8859-1.
|
390
|
+
def content_charset
|
391
|
+
media_type_params['charset']
|
392
|
+
end
|
393
|
+
|
394
|
+
# Determine whether the request body contains form-data by checking
|
395
|
+
# the request Content-Type for one of the media-types:
|
396
|
+
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
397
|
+
# list of form-data media types can be modified through the
|
398
|
+
# +FORM_DATA_MEDIA_TYPES+ array.
|
399
|
+
#
|
400
|
+
# A request body is also assumed to contain form-data when no
|
401
|
+
# Content-Type header is provided and the request_method is POST.
|
402
|
+
def form_data?
|
403
|
+
type = media_type
|
404
|
+
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
405
|
+
|
406
|
+
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
407
|
+
end
|
408
|
+
|
409
|
+
# Determine whether the request body contains data by checking
|
410
|
+
# the request media_type against registered parse-data media-types
|
411
|
+
def parseable_data?
|
412
|
+
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Returns the data received in the query string.
|
416
|
+
def GET
|
417
|
+
if get_header(RACK_REQUEST_QUERY_STRING) == query_string
|
418
|
+
get_header(RACK_REQUEST_QUERY_HASH)
|
419
|
+
else
|
420
|
+
query_hash = parse_query(query_string, '&;')
|
421
|
+
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
422
|
+
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Returns the data received in the request body.
|
427
|
+
#
|
428
|
+
# This method support both application/x-www-form-urlencoded and
|
429
|
+
# multipart/form-data.
|
430
|
+
def POST
|
431
|
+
if get_header(RACK_INPUT).nil?
|
432
|
+
raise "Missing rack.input"
|
433
|
+
elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
|
434
|
+
get_header(RACK_REQUEST_FORM_HASH)
|
435
|
+
elsif form_data? || parseable_data?
|
436
|
+
unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
|
437
|
+
form_vars = get_header(RACK_INPUT).read
|
438
|
+
|
439
|
+
# Fix for Safari Ajax postings that always append \0
|
440
|
+
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
441
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
442
|
+
|
443
|
+
set_header RACK_REQUEST_FORM_VARS, form_vars
|
444
|
+
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
445
|
+
|
446
|
+
get_header(RACK_INPUT).rewind
|
447
|
+
end
|
448
|
+
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
449
|
+
get_header RACK_REQUEST_FORM_HASH
|
450
|
+
else
|
451
|
+
{}
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# The union of GET and POST data.
|
456
|
+
#
|
457
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
458
|
+
def params
|
459
|
+
self.GET.merge(self.POST)
|
460
|
+
end
|
461
|
+
|
462
|
+
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
463
|
+
#
|
464
|
+
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
465
|
+
#
|
466
|
+
# <tt>env['rack.input']</tt> is not touched.
|
467
|
+
def update_param(k, v)
|
468
|
+
found = false
|
469
|
+
if self.GET.has_key?(k)
|
470
|
+
found = true
|
471
|
+
self.GET[k] = v
|
472
|
+
end
|
473
|
+
if self.POST.has_key?(k)
|
474
|
+
found = true
|
475
|
+
self.POST[k] = v
|
476
|
+
end
|
477
|
+
unless found
|
478
|
+
self.GET[k] = v
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
|
483
|
+
#
|
484
|
+
# If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
|
485
|
+
#
|
486
|
+
# <tt>env['rack.input']</tt> is not touched.
|
487
|
+
def delete_param(k)
|
488
|
+
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
489
|
+
post_value || get_value
|
366
490
|
end
|
367
491
|
|
368
|
-
def
|
369
|
-
|
370
|
-
qs, d = qs[:query], qs[:separator] if Hash === qs
|
371
|
-
Utils.parse_nested_query(qs, d)
|
492
|
+
def base_url
|
493
|
+
"#{scheme}://#{host_with_port}"
|
372
494
|
end
|
373
495
|
|
374
|
-
|
375
|
-
|
496
|
+
# Tries to return a remake of the original request URL as a string.
|
497
|
+
def url
|
498
|
+
base_url + fullpath
|
499
|
+
end
|
500
|
+
|
501
|
+
def path
|
502
|
+
script_name + path_info
|
503
|
+
end
|
504
|
+
|
505
|
+
def fullpath
|
506
|
+
query_string.empty? ? path : "#{path}?#{query_string}"
|
507
|
+
end
|
508
|
+
|
509
|
+
def accept_encoding
|
510
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
|
511
|
+
end
|
512
|
+
|
513
|
+
def accept_language
|
514
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
|
515
|
+
end
|
516
|
+
|
517
|
+
def trusted_proxy?(ip)
|
518
|
+
Rack::Request.ip_filter.call(ip)
|
519
|
+
end
|
520
|
+
|
521
|
+
# shortcut for <tt>request.params[key]</tt>
|
522
|
+
def [](key)
|
523
|
+
if $VERBOSE
|
524
|
+
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
|
525
|
+
end
|
526
|
+
|
527
|
+
params[key.to_s]
|
528
|
+
end
|
529
|
+
|
530
|
+
# shortcut for <tt>request.params[key] = value</tt>
|
531
|
+
#
|
532
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
533
|
+
def []=(key, value)
|
534
|
+
if $VERBOSE
|
535
|
+
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
|
536
|
+
end
|
537
|
+
|
538
|
+
params[key.to_s] = value
|
539
|
+
end
|
540
|
+
|
541
|
+
# like Hash#values_at
|
542
|
+
def values_at(*keys)
|
543
|
+
keys.map { |key| params[key] }
|
544
|
+
end
|
545
|
+
|
546
|
+
private
|
547
|
+
|
548
|
+
def default_session; {}; end
|
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
|
376
562
|
end
|
377
563
|
|
378
564
|
def parse_http_accept_header(header)
|
@@ -386,26 +572,78 @@ module Rack
|
|
386
572
|
end
|
387
573
|
end
|
388
574
|
|
389
|
-
|
390
|
-
|
391
|
-
if s[0] == ?" && s[-1] == ?"
|
392
|
-
s[1..-2]
|
393
|
-
else
|
394
|
-
s
|
575
|
+
def query_parser
|
576
|
+
Utils.default_query_parser
|
395
577
|
end
|
396
|
-
end
|
397
578
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
579
|
+
def parse_query(qs, d = '&')
|
580
|
+
query_parser.parse_nested_query(qs, d)
|
581
|
+
end
|
582
|
+
|
583
|
+
def parse_multipart
|
584
|
+
Rack::Multipart.extract_multipart(self, query_parser)
|
585
|
+
end
|
403
586
|
|
404
|
-
|
405
|
-
|
587
|
+
def split_header(value)
|
588
|
+
value ? value.strip.split(/[,\s]+/) : []
|
406
589
|
end
|
407
590
|
|
408
|
-
|
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
|
616
|
+
end
|
617
|
+
|
618
|
+
# Give up!
|
619
|
+
return authority, authority, nil
|
620
|
+
end
|
621
|
+
|
622
|
+
def reject_trusted_ip_addresses(ip_addresses)
|
623
|
+
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
624
|
+
end
|
625
|
+
|
626
|
+
def forwarded_scheme
|
627
|
+
allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
|
628
|
+
allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
|
629
|
+
end
|
630
|
+
|
631
|
+
def allowed_scheme(header)
|
632
|
+
header if ALLOWED_SCHEMES.include?(header)
|
633
|
+
end
|
634
|
+
|
635
|
+
def extract_proto_header(header)
|
636
|
+
if header
|
637
|
+
if (comma_index = header.index(','))
|
638
|
+
header[0, comma_index]
|
639
|
+
else
|
640
|
+
header
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
409
644
|
end
|
645
|
+
|
646
|
+
include Env
|
647
|
+
include Helpers
|
410
648
|
end
|
411
649
|
end
|