homura-runtime 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/exe/compile-assets +2 -2
- data/exe/compile-erb +5 -7
- data/lib/homura/runtime/build_support.rb +19 -2
- data/lib/homura/runtime/version.rb +1 -1
- data/vendor/rack/auth/abstract/handler.rb +41 -0
- data/vendor/rack/auth/abstract/request.rb +51 -0
- data/vendor/rack/auth/basic.rb +58 -0
- data/vendor/rack/bad_request.rb +8 -0
- data/vendor/rack/body_proxy.rb +63 -0
- data/vendor/rack/builder.rb +315 -0
- data/vendor/rack/cascade.rb +67 -0
- data/vendor/rack/common_logger.rb +94 -0
- data/vendor/rack/conditional_get.rb +87 -0
- data/vendor/rack/config.rb +22 -0
- data/vendor/rack/constants.rb +68 -0
- data/vendor/rack/content_length.rb +34 -0
- data/vendor/rack/content_type.rb +33 -0
- data/vendor/rack/deflater.rb +159 -0
- data/vendor/rack/directory.rb +210 -0
- data/vendor/rack/etag.rb +71 -0
- data/vendor/rack/events.rb +172 -0
- data/vendor/rack/files.rb +224 -0
- data/vendor/rack/head.rb +25 -0
- data/vendor/rack/headers.rb +238 -0
- data/vendor/rack/lint.rb +1000 -0
- data/vendor/rack/lock.rb +29 -0
- data/vendor/rack/media_type.rb +42 -0
- data/vendor/rack/method_override.rb +56 -0
- data/vendor/rack/mime.rb +694 -0
- data/vendor/rack/mock.rb +3 -0
- data/vendor/rack/mock_request.rb +161 -0
- data/vendor/rack/mock_response.rb +147 -0
- data/vendor/rack/multipart/generator.rb +99 -0
- data/vendor/rack/multipart/parser.rb +586 -0
- data/vendor/rack/multipart/uploaded_file.rb +82 -0
- data/vendor/rack/multipart.rb +77 -0
- data/vendor/rack/null_logger.rb +48 -0
- data/vendor/rack/protection/authenticity_token.rb +256 -0
- data/vendor/rack/protection/base.rb +140 -0
- data/vendor/rack/protection/content_security_policy.rb +80 -0
- data/vendor/rack/protection/cookie_tossing.rb +77 -0
- data/vendor/rack/protection/escaped_params.rb +93 -0
- data/vendor/rack/protection/form_token.rb +25 -0
- data/vendor/rack/protection/frame_options.rb +39 -0
- data/vendor/rack/protection/http_origin.rb +43 -0
- data/vendor/rack/protection/ip_spoofing.rb +27 -0
- data/vendor/rack/protection/json_csrf.rb +60 -0
- data/vendor/rack/protection/path_traversal.rb +45 -0
- data/vendor/rack/protection/referrer_policy.rb +27 -0
- data/vendor/rack/protection/remote_referrer.rb +22 -0
- data/vendor/rack/protection/remote_token.rb +24 -0
- data/vendor/rack/protection/session_hijacking.rb +37 -0
- data/vendor/rack/protection/strict_transport.rb +41 -0
- data/vendor/rack/protection/version.rb +7 -0
- data/vendor/rack/protection/xss_header.rb +27 -0
- data/vendor/rack/protection.rb +58 -0
- data/vendor/rack/query_parser.rb +261 -0
- data/vendor/rack/recursive.rb +66 -0
- data/vendor/rack/reloader.rb +112 -0
- data/vendor/rack/request.rb +818 -0
- data/vendor/rack/response.rb +403 -0
- data/vendor/rack/rewindable_input.rb +116 -0
- data/vendor/rack/runtime.rb +35 -0
- data/vendor/rack/sendfile.rb +197 -0
- data/vendor/rack/session/abstract/id.rb +533 -0
- data/vendor/rack/session/constants.rb +13 -0
- data/vendor/rack/session/cookie.rb +292 -0
- data/vendor/rack/session/encryptor.rb +415 -0
- data/vendor/rack/session/pool.rb +76 -0
- data/vendor/rack/session/version.rb +10 -0
- data/vendor/rack/session.rb +12 -0
- data/vendor/rack/show_exceptions.rb +433 -0
- data/vendor/rack/show_status.rb +121 -0
- data/vendor/rack/static.rb +188 -0
- data/vendor/rack/tempfile_reaper.rb +44 -0
- data/vendor/rack/urlmap.rb +99 -0
- data/vendor/rack/utils.rb +631 -0
- data/vendor/rack/version.rb +17 -0
- data/vendor/rack.rb +66 -0
- metadata +76 -1
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'constants'
|
|
4
|
+
require_relative 'utils'
|
|
5
|
+
require_relative 'media_type'
|
|
6
|
+
|
|
7
|
+
module Rack
|
|
8
|
+
# Rack::Request provides a convenient interface to a Rack
|
|
9
|
+
# environment. It is stateless, the environment +env+ passed to the
|
|
10
|
+
# constructor will be directly modified.
|
|
11
|
+
#
|
|
12
|
+
# req = Rack::Request.new(env)
|
|
13
|
+
# req.post?
|
|
14
|
+
# req.params["data"]
|
|
15
|
+
|
|
16
|
+
class Request
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :ip_filter
|
|
19
|
+
|
|
20
|
+
# The priority when checking forwarded headers. The default
|
|
21
|
+
# is <tt>[:forwarded, :x_forwarded]</tt>, which means, check the
|
|
22
|
+
# +Forwarded+ header first, followed by the appropriate
|
|
23
|
+
# <tt>X-Forwarded-*</tt> header. You can revert the priority by
|
|
24
|
+
# reversing the priority, or remove checking of either
|
|
25
|
+
# or both headers by removing elements from the array.
|
|
26
|
+
#
|
|
27
|
+
# This should be set as appropriate in your environment
|
|
28
|
+
# based on what reverse proxies are in use. If you are not
|
|
29
|
+
# using reverse proxies, you should probably use an empty
|
|
30
|
+
# array.
|
|
31
|
+
attr_accessor :forwarded_priority
|
|
32
|
+
|
|
33
|
+
# The priority when checking either the <tt>X-Forwarded-Proto</tt>
|
|
34
|
+
# or <tt>X-Forwarded-Scheme</tt> header for the forwarded protocol.
|
|
35
|
+
# The default is <tt>[:proto, :scheme]</tt>, to try the
|
|
36
|
+
# <tt>X-Forwarded-Proto</tt> header before the
|
|
37
|
+
# <tt>X-Forwarded-Scheme</tt> header. Rack 2 had behavior
|
|
38
|
+
# similar to <tt>[:scheme, :proto]</tt>. You can remove either or
|
|
39
|
+
# both of the entries in array to ignore that respective header.
|
|
40
|
+
attr_accessor :x_forwarded_proto_priority
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@forwarded_priority = [:forwarded, :x_forwarded]
|
|
44
|
+
@x_forwarded_proto_priority = [:proto, :scheme]
|
|
45
|
+
|
|
46
|
+
valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
|
|
47
|
+
|
|
48
|
+
# homura patch: Opal compiles to JS regex, which cannot mix
|
|
49
|
+
# case-insensitive (`i`) and case-sensitive members in a Regexp.union.
|
|
50
|
+
# The two `i`-flagged literals are rewritten with explicit character
|
|
51
|
+
# classes so the union is uniform.
|
|
52
|
+
trusted_proxies = Regexp.union(
|
|
53
|
+
/\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330
|
|
54
|
+
/\A::1\z/, # localhost IPv6 ::1
|
|
55
|
+
/\A[Ff][CcDd][0-9a-fA-F]{2}(?::[0-9a-fA-F]{0,4}){0,7}\z/, # private IPv6 range fc00 .. fdff
|
|
56
|
+
/\A10#{valid_ipv4_octet}{3}\z/, # private IPv4 range 10.x.x.x
|
|
57
|
+
/\A172\.(1[6-9]|2[0-9]|3[01])#{valid_ipv4_octet}{2}\z/, # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
|
58
|
+
/\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x
|
|
59
|
+
/\A[Ll][Oo][Cc][Aa][Ll][Hh][Oo][Ss][Tt]\z|\A[Uu][Nn][Ii][Xx](\z|:)/, # localhost hostname, and unix domain sockets
|
|
60
|
+
).freeze
|
|
61
|
+
|
|
62
|
+
self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
|
|
63
|
+
|
|
64
|
+
ALLOWED_SCHEMES = %w(https http wss ws).freeze
|
|
65
|
+
|
|
66
|
+
def initialize(env)
|
|
67
|
+
@env = env
|
|
68
|
+
@ip = nil
|
|
69
|
+
@params = nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ip
|
|
73
|
+
@ip ||= super
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def params
|
|
77
|
+
@params ||= super
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def update_param(k, v)
|
|
81
|
+
super
|
|
82
|
+
@params = nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def delete_param(k)
|
|
86
|
+
v = super
|
|
87
|
+
@params = nil
|
|
88
|
+
v
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module Env
|
|
92
|
+
# The environment of the request.
|
|
93
|
+
attr_reader :env
|
|
94
|
+
|
|
95
|
+
def initialize(env)
|
|
96
|
+
@env = env
|
|
97
|
+
# This module is included at least in `ActionDispatch::Request`
|
|
98
|
+
# The call to `super()` allows additional mixed-in initializers are called
|
|
99
|
+
super()
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Predicate method to test to see if `name` has been set as request
|
|
103
|
+
# specific data
|
|
104
|
+
def has_header?(name)
|
|
105
|
+
@env.key? name
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get a request specific value for `name`.
|
|
109
|
+
def get_header(name)
|
|
110
|
+
@env[name]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# If a block is given, it yields to the block if the value hasn't been set
|
|
114
|
+
# on the request.
|
|
115
|
+
def fetch_header(name, &block)
|
|
116
|
+
@env.fetch(name, &block)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Loops through each key / value pair in the request specific data.
|
|
120
|
+
def each_header(&block)
|
|
121
|
+
@env.each(&block)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Set a request specific value for `name` to `v`
|
|
125
|
+
def set_header(name, v)
|
|
126
|
+
@env[name] = v
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Add a header that may have multiple values.
|
|
130
|
+
#
|
|
131
|
+
# Example:
|
|
132
|
+
# request.add_header 'Accept', 'image/png'
|
|
133
|
+
# request.add_header 'Accept', '*/*'
|
|
134
|
+
#
|
|
135
|
+
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
|
136
|
+
#
|
|
137
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
138
|
+
def add_header(key, v)
|
|
139
|
+
if v.nil?
|
|
140
|
+
get_header key
|
|
141
|
+
elsif has_header? key
|
|
142
|
+
set_header key, "#{get_header key},#{v}"
|
|
143
|
+
else
|
|
144
|
+
set_header key, v
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Delete a request specific value for `name`.
|
|
149
|
+
def delete_header(name)
|
|
150
|
+
@env.delete name
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def initialize_copy(other)
|
|
154
|
+
@env = other.env.dup
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
module Helpers
|
|
159
|
+
# The set of form-data media-types. Requests that do not indicate
|
|
160
|
+
# one of the media types present in this list will not be eligible
|
|
161
|
+
# for form-data / param parsing.
|
|
162
|
+
FORM_DATA_MEDIA_TYPES = [
|
|
163
|
+
'application/x-www-form-urlencoded',
|
|
164
|
+
'multipart/form-data'
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
# The set of media-types. Requests that do not indicate
|
|
168
|
+
# one of the media types present in this list will not be eligible
|
|
169
|
+
# for param parsing like soap attachments or generic multiparts
|
|
170
|
+
PARSEABLE_DATA_MEDIA_TYPES = [
|
|
171
|
+
'multipart/related',
|
|
172
|
+
'multipart/mixed'
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
# Default ports depending on scheme. Used to decide whether or not
|
|
176
|
+
# to include the port in a generated URI.
|
|
177
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
|
178
|
+
|
|
179
|
+
# The address of the client which connected to the proxy.
|
|
180
|
+
HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
|
|
181
|
+
|
|
182
|
+
# The contents of the host/:authority header sent to the proxy.
|
|
183
|
+
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
|
184
|
+
|
|
185
|
+
HTTP_FORWARDED = 'HTTP_FORWARDED'
|
|
186
|
+
|
|
187
|
+
# The value of the scheme sent to the proxy.
|
|
188
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
|
189
|
+
|
|
190
|
+
# The protocol used to connect to the proxy.
|
|
191
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
|
192
|
+
|
|
193
|
+
# The port used to connect to the proxy.
|
|
194
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
|
195
|
+
|
|
196
|
+
# Another way for specifying https scheme was used.
|
|
197
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
|
198
|
+
|
|
199
|
+
def body; get_header(RACK_INPUT) end
|
|
200
|
+
def script_name; get_header(SCRIPT_NAME).to_s end
|
|
201
|
+
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
|
|
202
|
+
|
|
203
|
+
def path_info; get_header(PATH_INFO).to_s end
|
|
204
|
+
def path_info=(s); set_header(PATH_INFO, s.to_s) end
|
|
205
|
+
|
|
206
|
+
def request_method; get_header(REQUEST_METHOD) end
|
|
207
|
+
def query_string; get_header(QUERY_STRING).to_s end
|
|
208
|
+
def content_length; get_header('CONTENT_LENGTH') end
|
|
209
|
+
def logger; get_header(RACK_LOGGER) end
|
|
210
|
+
def user_agent; get_header('HTTP_USER_AGENT') end
|
|
211
|
+
|
|
212
|
+
# the referer of the client
|
|
213
|
+
def referer; get_header('HTTP_REFERER') end
|
|
214
|
+
alias referrer referer
|
|
215
|
+
|
|
216
|
+
def session
|
|
217
|
+
fetch_header(RACK_SESSION) do |k|
|
|
218
|
+
set_header RACK_SESSION, default_session
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def session_options
|
|
223
|
+
fetch_header(RACK_SESSION_OPTIONS) do |k|
|
|
224
|
+
set_header RACK_SESSION_OPTIONS, {}
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Checks the HTTP request method (or verb) to see if it was of type DELETE
|
|
229
|
+
def delete?; request_method == DELETE end
|
|
230
|
+
|
|
231
|
+
# Checks the HTTP request method (or verb) to see if it was of type GET
|
|
232
|
+
def get?; request_method == GET end
|
|
233
|
+
|
|
234
|
+
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
|
235
|
+
def head?; request_method == HEAD end
|
|
236
|
+
|
|
237
|
+
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
|
238
|
+
def options?; request_method == OPTIONS end
|
|
239
|
+
|
|
240
|
+
# Checks the HTTP request method (or verb) to see if it was of type LINK
|
|
241
|
+
def link?; request_method == LINK end
|
|
242
|
+
|
|
243
|
+
# Checks the HTTP request method (or verb) to see if it was of type PATCH
|
|
244
|
+
def patch?; request_method == PATCH end
|
|
245
|
+
|
|
246
|
+
# Checks the HTTP request method (or verb) to see if it was of type POST
|
|
247
|
+
def post?; request_method == POST end
|
|
248
|
+
|
|
249
|
+
# Checks the HTTP request method (or verb) to see if it was of type PUT
|
|
250
|
+
def put?; request_method == PUT end
|
|
251
|
+
|
|
252
|
+
# Checks the HTTP request method (or verb) to see if it was of type TRACE
|
|
253
|
+
def trace?; request_method == TRACE end
|
|
254
|
+
|
|
255
|
+
# Checks the HTTP request method (or verb) to see if it was of type UNLINK
|
|
256
|
+
def unlink?; request_method == UNLINK end
|
|
257
|
+
|
|
258
|
+
def scheme
|
|
259
|
+
if get_header(HTTPS) == 'on'
|
|
260
|
+
'https'
|
|
261
|
+
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
|
|
262
|
+
'https'
|
|
263
|
+
elsif forwarded_scheme
|
|
264
|
+
forwarded_scheme
|
|
265
|
+
else
|
|
266
|
+
get_header(RACK_URL_SCHEME)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The authority of the incoming request as defined by RFC3976.
|
|
271
|
+
# https://tools.ietf.org/html/rfc3986#section-3.2
|
|
272
|
+
#
|
|
273
|
+
# In HTTP/1, this is the `host` header.
|
|
274
|
+
# In HTTP/2, this is the `:authority` pseudo-header.
|
|
275
|
+
def authority
|
|
276
|
+
forwarded_authority || host_authority || server_authority
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
|
|
280
|
+
# variables.
|
|
281
|
+
def server_authority
|
|
282
|
+
host = self.server_name
|
|
283
|
+
port = self.server_port
|
|
284
|
+
|
|
285
|
+
if host
|
|
286
|
+
if port
|
|
287
|
+
"#{host}:#{port}"
|
|
288
|
+
else
|
|
289
|
+
host
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def server_name
|
|
295
|
+
get_header(SERVER_NAME)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def server_port
|
|
299
|
+
get_header(SERVER_PORT)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def cookies
|
|
303
|
+
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
|
|
304
|
+
set_header(key, {})
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
string = get_header(HTTP_COOKIE)
|
|
308
|
+
|
|
309
|
+
unless string == get_header(RACK_REQUEST_COOKIE_STRING)
|
|
310
|
+
hash.replace Utils.parse_cookies_header(string)
|
|
311
|
+
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
hash
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def content_type
|
|
318
|
+
content_type = get_header('CONTENT_TYPE')
|
|
319
|
+
content_type.nil? || content_type.empty? ? nil : content_type
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def xhr?
|
|
323
|
+
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# The `HTTP_HOST` header.
|
|
327
|
+
def host_authority
|
|
328
|
+
get_header(HTTP_HOST)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def host_with_port(authority = self.authority)
|
|
332
|
+
host, _, port = split_authority(authority)
|
|
333
|
+
|
|
334
|
+
if port == DEFAULT_PORTS[self.scheme]
|
|
335
|
+
host
|
|
336
|
+
else
|
|
337
|
+
authority
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Returns a formatted host, suitable for being used in a URI.
|
|
342
|
+
def host
|
|
343
|
+
split_authority(self.authority)[0]
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Returns an address suitable for being to resolve to an address.
|
|
347
|
+
# In the case of a domain name or IPv4 address, the result is the same
|
|
348
|
+
# as +host+. In the case of IPv6 or future address formats, the square
|
|
349
|
+
# brackets are removed.
|
|
350
|
+
def hostname
|
|
351
|
+
split_authority(self.authority)[1]
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def port
|
|
355
|
+
if authority = self.authority
|
|
356
|
+
_, _, authority_port = split_authority(authority)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
authority_port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def forwarded_for
|
|
363
|
+
forwarded_priority.each do |type|
|
|
364
|
+
case type
|
|
365
|
+
when :forwarded
|
|
366
|
+
if forwarded_for = get_http_forwarded(:for)
|
|
367
|
+
return(forwarded_for.map! do |authority|
|
|
368
|
+
split_authority(authority)[1]
|
|
369
|
+
end)
|
|
370
|
+
end
|
|
371
|
+
when :x_forwarded
|
|
372
|
+
if value = get_header(HTTP_X_FORWARDED_FOR)
|
|
373
|
+
return(split_header(value).map do |authority|
|
|
374
|
+
split_authority(wrap_ipv6(authority))[1]
|
|
375
|
+
end)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
nil
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def forwarded_port
|
|
384
|
+
forwarded_priority.each do |type|
|
|
385
|
+
case type
|
|
386
|
+
when :forwarded
|
|
387
|
+
if forwarded = get_http_forwarded(:for)
|
|
388
|
+
return(forwarded.map do |authority|
|
|
389
|
+
split_authority(authority)[2]
|
|
390
|
+
end.compact)
|
|
391
|
+
end
|
|
392
|
+
when :x_forwarded
|
|
393
|
+
if value = get_header(HTTP_X_FORWARDED_PORT)
|
|
394
|
+
return split_header(value).map(&:to_i)
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
nil
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def forwarded_authority
|
|
403
|
+
forwarded_priority.each do |type|
|
|
404
|
+
case type
|
|
405
|
+
when :forwarded
|
|
406
|
+
if forwarded = get_http_forwarded(:host)
|
|
407
|
+
return forwarded.last
|
|
408
|
+
end
|
|
409
|
+
when :x_forwarded
|
|
410
|
+
if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
|
|
411
|
+
return wrap_ipv6(x_forwarded_host)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
nil
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def ssl?
|
|
420
|
+
scheme == 'https' || scheme == 'wss'
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def ip
|
|
424
|
+
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
|
425
|
+
|
|
426
|
+
remote_addresses.reverse_each do |ip|
|
|
427
|
+
return ip unless trusted_proxy?(ip)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
|
|
431
|
+
# The forwarded for addresses are ordered: client, proxy1, proxy2.
|
|
432
|
+
# So we reject all the trusted addresses (proxy*) and return the
|
|
433
|
+
# last client. Or if we trust everyone, we just return the first
|
|
434
|
+
# address.
|
|
435
|
+
forwarded_for.reverse_each do |ip|
|
|
436
|
+
return ip unless trusted_proxy?(ip)
|
|
437
|
+
end
|
|
438
|
+
return forwarded_for.first
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# If all the addresses are trusted, and we aren't forwarded, just return
|
|
442
|
+
# the first remote address, which represents the source of the request.
|
|
443
|
+
remote_addresses.first
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
|
447
|
+
# without any media type parameters. e.g., when CONTENT_TYPE is
|
|
448
|
+
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
|
449
|
+
#
|
|
450
|
+
# For more information on the use of media types in HTTP, see:
|
|
451
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
|
452
|
+
def media_type
|
|
453
|
+
MediaType.type(content_type)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
|
457
|
+
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
|
458
|
+
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
|
459
|
+
# this method responds with the following Hash:
|
|
460
|
+
# { 'charset' => 'utf-8' }
|
|
461
|
+
def media_type_params
|
|
462
|
+
MediaType.params(content_type)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# The character set of the request body if a "charset" media type
|
|
466
|
+
# parameter was given, or nil if no "charset" was specified. Note
|
|
467
|
+
# that, per RFC2616, text/* media types that specify no explicit
|
|
468
|
+
# charset are to be considered ISO-8859-1.
|
|
469
|
+
def content_charset
|
|
470
|
+
media_type_params['charset']
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Determine whether the request body contains form-data by checking
|
|
474
|
+
# the request content-type for one of the media-types:
|
|
475
|
+
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
|
476
|
+
# list of form-data media types can be modified through the
|
|
477
|
+
# +FORM_DATA_MEDIA_TYPES+ array.
|
|
478
|
+
#
|
|
479
|
+
# A request body is also assumed to contain form-data when no
|
|
480
|
+
# content-type header is provided and the request_method is POST.
|
|
481
|
+
def form_data?
|
|
482
|
+
type = media_type
|
|
483
|
+
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
|
484
|
+
|
|
485
|
+
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Determine whether the request body contains data by checking
|
|
489
|
+
# the request media_type against registered parse-data media-types
|
|
490
|
+
def parseable_data?
|
|
491
|
+
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Returns the data received in the query string.
|
|
495
|
+
def GET
|
|
496
|
+
get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Returns the form data pairs received in the request body.
|
|
500
|
+
#
|
|
501
|
+
# This method support both application/x-www-form-urlencoded and
|
|
502
|
+
# multipart/form-data.
|
|
503
|
+
def form_pairs
|
|
504
|
+
if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
|
|
505
|
+
return pairs
|
|
506
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
507
|
+
raise error.class, error.message, cause: error.cause
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
begin
|
|
511
|
+
rack_input = get_header(RACK_INPUT)
|
|
512
|
+
|
|
513
|
+
# Otherwise, figure out how to parse the input:
|
|
514
|
+
if rack_input.nil?
|
|
515
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
516
|
+
elsif form_data? || parseable_data?
|
|
517
|
+
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
|
518
|
+
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
|
519
|
+
else
|
|
520
|
+
# Add 2 bytes. One to check whether it is over the limit, and a second
|
|
521
|
+
# in case the slice! call below removes the last byte
|
|
522
|
+
# If read returns nil, use the empty string
|
|
523
|
+
form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
|
|
524
|
+
|
|
525
|
+
# Fix for Safari Ajax postings that always append \0
|
|
526
|
+
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
527
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
528
|
+
|
|
529
|
+
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
530
|
+
pairs = query_parser.parse_query_pairs(form_vars, '&')
|
|
531
|
+
set_header(RACK_REQUEST_FORM_PAIRS, pairs)
|
|
532
|
+
end
|
|
533
|
+
else
|
|
534
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
535
|
+
end
|
|
536
|
+
rescue => error
|
|
537
|
+
set_header(RACK_REQUEST_FORM_ERROR, error)
|
|
538
|
+
raise
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Returns the data received in the request body.
|
|
543
|
+
#
|
|
544
|
+
# This method support both application/x-www-form-urlencoded and
|
|
545
|
+
# multipart/form-data.
|
|
546
|
+
def POST
|
|
547
|
+
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
|
548
|
+
return form_hash
|
|
549
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
550
|
+
raise error.class, error.message, cause: error.cause
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
pairs = form_pairs
|
|
554
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# The union of GET and POST data.
|
|
558
|
+
#
|
|
559
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
560
|
+
def params
|
|
561
|
+
self.GET.merge(self.POST)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Allow overriding the query parser that the receiver will use.
|
|
565
|
+
# By default Rack::Utils.default_query_parser is used.
|
|
566
|
+
attr_writer :query_parser
|
|
567
|
+
|
|
568
|
+
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
|
569
|
+
#
|
|
570
|
+
# 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.
|
|
571
|
+
#
|
|
572
|
+
# <tt>env['rack.input']</tt> is not touched.
|
|
573
|
+
def update_param(k, v)
|
|
574
|
+
found = false
|
|
575
|
+
if self.GET.has_key?(k)
|
|
576
|
+
found = true
|
|
577
|
+
self.GET[k] = v
|
|
578
|
+
end
|
|
579
|
+
if self.POST.has_key?(k)
|
|
580
|
+
found = true
|
|
581
|
+
self.POST[k] = v
|
|
582
|
+
end
|
|
583
|
+
unless found
|
|
584
|
+
self.GET[k] = v
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
|
|
589
|
+
#
|
|
590
|
+
# If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
|
|
591
|
+
#
|
|
592
|
+
# <tt>env['rack.input']</tt> is not touched.
|
|
593
|
+
def delete_param(k)
|
|
594
|
+
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
|
595
|
+
post_value || get_value
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def base_url
|
|
599
|
+
"#{scheme}://#{host_with_port}"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Tries to return a remake of the original request URL as a string.
|
|
603
|
+
def url
|
|
604
|
+
base_url + fullpath
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def path
|
|
608
|
+
script_name + path_info
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def fullpath
|
|
612
|
+
query_string.empty? ? path : "#{path}?#{query_string}"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def accept_encoding
|
|
616
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def accept_language
|
|
620
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def trusted_proxy?(ip)
|
|
624
|
+
Rack::Request.ip_filter.call(ip)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
private
|
|
628
|
+
|
|
629
|
+
def default_session; {}; end
|
|
630
|
+
|
|
631
|
+
# Assist with compatibility when processing `X-Forwarded-For`.
|
|
632
|
+
def wrap_ipv6(host)
|
|
633
|
+
# Even thought IPv6 addresses should be wrapped in square brackets,
|
|
634
|
+
# sometimes this is not done in various legacy/underspecified headers.
|
|
635
|
+
# So we try to fix this situation for compatibility reasons.
|
|
636
|
+
|
|
637
|
+
# Try to detect IPv6 addresses which aren't escaped yet:
|
|
638
|
+
if !host.start_with?('[') && host.count(':') > 1
|
|
639
|
+
"[#{host}]"
|
|
640
|
+
else
|
|
641
|
+
host
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def parse_http_accept_header(header)
|
|
646
|
+
# It would be nice to use filter_map here, but it's Ruby 2.7+
|
|
647
|
+
parts = header.to_s.split(',')
|
|
648
|
+
|
|
649
|
+
parts.map! do |part|
|
|
650
|
+
part.strip!
|
|
651
|
+
next if part.empty?
|
|
652
|
+
|
|
653
|
+
attribute, parameters = part.split(';', 2)
|
|
654
|
+
attribute.strip!
|
|
655
|
+
parameters&.strip!
|
|
656
|
+
quality = 1.0
|
|
657
|
+
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
|
658
|
+
quality = $1.to_f
|
|
659
|
+
end
|
|
660
|
+
[attribute, quality]
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
parts.compact!
|
|
664
|
+
|
|
665
|
+
parts
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# Get an array of values set in the RFC 7239 `Forwarded` request header.
|
|
669
|
+
def get_http_forwarded(token)
|
|
670
|
+
Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def query_parser
|
|
674
|
+
@query_parser || Utils.default_query_parser
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def parse_query(qs, d = '&')
|
|
678
|
+
query_parser.parse_nested_query(qs, d)
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def parse_multipart
|
|
682
|
+
warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
|
|
683
|
+
Rack::Multipart.extract_multipart(self, query_parser)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def expand_param_pairs(pairs, query_parser = query_parser())
|
|
687
|
+
params = query_parser.make_params
|
|
688
|
+
|
|
689
|
+
pairs.each do |k, v|
|
|
690
|
+
query_parser.normalize_params(params, k, v)
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
params.to_params_hash
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def split_header(value)
|
|
697
|
+
value ? value.strip.split(/[, \t]+/) : []
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# homura patch: the upstream ipv6 / AUTHORITY regexes use Ruby's
|
|
701
|
+
# `x` (extended) flag and POSIX character classes (`[:graph:]`),
|
|
702
|
+
# neither of which Opal's JS regex backend supports. We collapse
|
|
703
|
+
# the IPv6 alternation into a single whitespace-free regex and
|
|
704
|
+
# rewrite AUTHORITY similarly. The resulting matcher is slightly
|
|
705
|
+
# looser than upstream but covers the cases Phase 2's hello-world
|
|
706
|
+
# actually needs (and is only invoked at request time).
|
|
707
|
+
ipv6 = Regexp.union(
|
|
708
|
+
/(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}/,
|
|
709
|
+
/(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/,
|
|
710
|
+
/(?:[0-9A-Fa-f]{1,4}:){6,6}\d+\.\d+\.\d+\.\d+/,
|
|
711
|
+
/(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}:)*\d+\.\d+\.\d+\.\d+/,
|
|
712
|
+
/[Ff][Ee]80(?::[0-9A-Fa-f]{1,4}){7}%[\-0-9A-Za-z._~]+/,
|
|
713
|
+
/[Ff][Ee]80:(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?|:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)?:[0-9A-Fa-f]{1,4}%[\-0-9A-Za-z._~]+/
|
|
714
|
+
).freeze
|
|
715
|
+
|
|
716
|
+
# homura patch: JS regex disallows duplicate named capture groups,
|
|
717
|
+
# so we collapse the two `(?<address>...)` alternatives into a
|
|
718
|
+
# single `(?<host>...)` and re-derive `address` from `host` in
|
|
719
|
+
# `split_authority` below.
|
|
720
|
+
AUTHORITY = /\A(?<host>\[#{ipv6}\]|[^\[\]:]*?)(:(?<port>\d+))?\z/
|
|
721
|
+
|
|
722
|
+
private_constant :AUTHORITY
|
|
723
|
+
|
|
724
|
+
def split_authority(authority)
|
|
725
|
+
return [] if authority.nil?
|
|
726
|
+
authority = authority.to_s
|
|
727
|
+
|
|
728
|
+
host = nil
|
|
729
|
+
port = nil
|
|
730
|
+
|
|
731
|
+
if authority.start_with?('[')
|
|
732
|
+
closing = authority.index(']')
|
|
733
|
+
return [] unless closing
|
|
734
|
+
|
|
735
|
+
host = authority[0..closing]
|
|
736
|
+
rest = authority[(closing + 1)..]
|
|
737
|
+
if rest && !rest.empty?
|
|
738
|
+
return [] unless rest.start_with?(':')
|
|
739
|
+
port_str = rest[1..]
|
|
740
|
+
return [] unless port_str.match?(/\A\d+\z/)
|
|
741
|
+
|
|
742
|
+
port = port_str.to_i
|
|
743
|
+
end
|
|
744
|
+
else
|
|
745
|
+
first_colon = authority.index(':')
|
|
746
|
+
last_colon = authority.rindex(':')
|
|
747
|
+
|
|
748
|
+
if first_colon && first_colon == last_colon
|
|
749
|
+
candidate_host = authority[0...last_colon]
|
|
750
|
+
candidate_port = authority[(last_colon + 1)..]
|
|
751
|
+
|
|
752
|
+
if !candidate_host.empty? && candidate_port.match?(/\A\d+\z/)
|
|
753
|
+
host = candidate_host
|
|
754
|
+
port = candidate_port.to_i
|
|
755
|
+
else
|
|
756
|
+
host = authority
|
|
757
|
+
end
|
|
758
|
+
else
|
|
759
|
+
host = authority
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
return [] if host.nil? || host.empty?
|
|
764
|
+
|
|
765
|
+
address = host.start_with?('[') && host.end_with?(']') ? host[1..-2] : host
|
|
766
|
+
[host, address, port]
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
FORWARDED_SCHEME_HEADERS = {
|
|
770
|
+
proto: HTTP_X_FORWARDED_PROTO,
|
|
771
|
+
scheme: HTTP_X_FORWARDED_SCHEME
|
|
772
|
+
}.freeze
|
|
773
|
+
private_constant :FORWARDED_SCHEME_HEADERS
|
|
774
|
+
def forwarded_scheme
|
|
775
|
+
forwarded_priority.each do |type|
|
|
776
|
+
case type
|
|
777
|
+
when :forwarded
|
|
778
|
+
if (forwarded_proto = get_http_forwarded(:proto)) &&
|
|
779
|
+
(scheme = allowed_scheme(forwarded_proto.last))
|
|
780
|
+
return scheme
|
|
781
|
+
end
|
|
782
|
+
when :x_forwarded
|
|
783
|
+
x_forwarded_proto_priority.each do |x_type|
|
|
784
|
+
if header = FORWARDED_SCHEME_HEADERS[x_type]
|
|
785
|
+
split_header(get_header(header)).reverse_each do |scheme|
|
|
786
|
+
if allowed_scheme(scheme)
|
|
787
|
+
return scheme
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
nil
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
def allowed_scheme(header)
|
|
799
|
+
header if ALLOWED_SCHEMES.include?(header)
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
def forwarded_priority
|
|
803
|
+
Request.forwarded_priority
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
def x_forwarded_proto_priority
|
|
807
|
+
Request.x_forwarded_proto_priority
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
include Env
|
|
812
|
+
include Helpers
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
# :nocov:
|
|
817
|
+
require_relative 'multipart' unless defined?(Rack::Multipart)
|
|
818
|
+
# :nocov:
|