rack 1.4.7 → 1.5.0.beta.1
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.
- data/README.rdoc +2 -30
- data/Rakefile +1 -0
- data/SPEC +68 -4
- data/example/protectedlobster.rb +1 -1
- data/lib/rack.rb +2 -14
- data/lib/rack/auth/abstract/request.rb +1 -5
- data/lib/rack/builder.rb +8 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/config.rb +5 -0
- data/lib/rack/deflater.rb +2 -1
- data/lib/rack/file.rb +25 -28
- data/lib/rack/handler.rb +18 -5
- data/lib/rack/handler/mongrel.rb +1 -1
- data/lib/rack/handler/scgi.rb +1 -1
- data/lib/rack/handler/thin.rb +6 -3
- data/lib/rack/handler/webrick.rb +1 -0
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +132 -7
- data/lib/rack/lobster.rb +3 -3
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/methodoverride.rb +0 -2
- data/lib/rack/mime.rb +29 -0
- data/lib/rack/multipart/parser.rb +0 -9
- data/lib/rack/request.rb +66 -25
- data/lib/rack/response.rb +1 -2
- data/lib/rack/sendfile.rb +18 -4
- data/lib/rack/server.rb +20 -12
- data/lib/rack/session/abstract/id.rb +60 -59
- data/lib/rack/session/cookie.rb +11 -16
- data/lib/rack/utils.rb +97 -85
- data/rack.gemspec +1 -6
- data/test/spec_builder.rb +7 -0
- data/test/spec_cgi.rb +1 -1
- data/test/spec_chunked.rb +3 -5
- data/test/spec_content_length.rb +3 -6
- data/test/spec_deflater.rb +26 -9
- data/test/spec_fastcgi.rb +1 -1
- data/test/spec_file.rb +24 -11
- data/test/spec_head.rb +3 -8
- data/test/spec_lint.rb +6 -6
- data/test/spec_lock.rb +4 -7
- data/test/spec_methodoverride.rb +4 -1
- data/test/spec_mime.rb +51 -0
- data/test/spec_mongrel.rb +1 -1
- data/test/spec_multipart.rb +15 -49
- data/test/spec_nulllogger.rb +3 -6
- data/test/spec_request.rb +112 -18
- data/test/spec_response.rb +8 -8
- data/test/spec_sendfile.rb +52 -13
- data/test/spec_server.rb +6 -0
- data/test/spec_session_abstract_id.rb +11 -1
- data/test/spec_session_cookie.rb +140 -153
- data/test/spec_thin.rb +6 -1
- data/test/spec_utils.rb +23 -17
- data/test/spec_webrick.rb +1 -1
- metadata +37 -83
- checksums.yaml +0 -7
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
- data/test/spec_auth.rb +0 -57
data/lib/rack/handler/thin.rb
CHANGED
@@ -6,9 +6,12 @@ module Rack
|
|
6
6
|
module Handler
|
7
7
|
class Thin
|
8
8
|
def self.run(app, options={})
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
host = options.delete(:Host) || '0.0.0.0'
|
10
|
+
port = options.delete(:Port) || 8080
|
11
|
+
args = [host, port, app, options]
|
12
|
+
# Thin versions below 0.8.0 do not support additional options
|
13
|
+
args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8
|
14
|
+
server = ::Thin::Server.new(*args)
|
12
15
|
yield server if block_given?
|
13
16
|
server.start
|
14
17
|
end
|
data/lib/rack/handler/webrick.rb
CHANGED
@@ -7,6 +7,7 @@ module Rack
|
|
7
7
|
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
|
8
8
|
def self.run(app, options={})
|
9
9
|
options[:BindAddress] = options.delete(:Host) if options[:Host]
|
10
|
+
options[:Port] ||= 8080
|
10
11
|
@server = ::WEBrick::HTTPServer.new(options)
|
11
12
|
@server.mount "/", Rack::Handler::WEBrick, app
|
12
13
|
yield @server if block_given?
|
data/lib/rack/head.rb
CHANGED
data/lib/rack/lint.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rack/utils'
|
2
|
+
require 'forwardable'
|
2
3
|
|
3
4
|
module Rack
|
4
5
|
# Rack::Lint validates your application and the requests and
|
@@ -50,6 +51,9 @@ module Rack
|
|
50
51
|
check_status status
|
51
52
|
## the *headers*,
|
52
53
|
check_headers headers
|
54
|
+
|
55
|
+
check_hijack_response headers, env
|
56
|
+
|
53
57
|
## and the *body*.
|
54
58
|
check_content_type status, headers
|
55
59
|
check_content_length status, headers
|
@@ -108,7 +112,8 @@ module Rack
|
|
108
112
|
## variables should correspond with
|
109
113
|
## the presence or absence of the
|
110
114
|
## appropriate HTTP header in the
|
111
|
-
## request.
|
115
|
+
## request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
|
116
|
+
## RFC3875 section 4.1.18</a> for specific behavior.
|
112
117
|
|
113
118
|
## In addition to this, the Rack environment must include these
|
114
119
|
## Rack-specific variables:
|
@@ -120,6 +125,9 @@ module Rack
|
|
120
125
|
## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
|
121
126
|
## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
|
122
127
|
## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
|
128
|
+
## <tt>rack.hijack?</tt>:: present and true if the server supports connection hijacking. See below, hijacking.
|
129
|
+
## <tt>rack.hijack</tt>:: an object responding to #call that must be called at least once before using rack.hijack_io. It is recommended #call return rack.hijack_io as well as setting it in env if necessary.
|
130
|
+
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking.
|
123
131
|
##
|
124
132
|
|
125
133
|
## Additional environment specifications have approved to
|
@@ -226,6 +234,8 @@ module Rack
|
|
226
234
|
check_input env["rack.input"]
|
227
235
|
## * There must be a valid error stream in <tt>rack.errors</tt>.
|
228
236
|
check_error env["rack.errors"]
|
237
|
+
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
|
238
|
+
check_hijack env
|
229
239
|
|
230
240
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
231
241
|
assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
|
@@ -416,6 +426,121 @@ module Rack
|
|
416
426
|
end
|
417
427
|
end
|
418
428
|
|
429
|
+
class HijackWrapper
|
430
|
+
include Assertion
|
431
|
+
extend Forwardable
|
432
|
+
|
433
|
+
REQUIRED_METHODS = [
|
434
|
+
:read, :write, :read_nonblock, :write_nonblock, :flush, :close,
|
435
|
+
:close_read, :close_write, :closed?
|
436
|
+
]
|
437
|
+
|
438
|
+
def_delegators :@io, *REQUIRED_METHODS
|
439
|
+
|
440
|
+
def initialize(io)
|
441
|
+
@io = io
|
442
|
+
REQUIRED_METHODS.each do |meth|
|
443
|
+
assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
## === Hijacking
|
449
|
+
#
|
450
|
+
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
|
451
|
+
# should not be removed. The whitespace creates paragraphs in the RDoc
|
452
|
+
# output.
|
453
|
+
#
|
454
|
+
## ==== Request (before status)
|
455
|
+
def check_hijack(env)
|
456
|
+
if env['rack.hijack?']
|
457
|
+
## If rack.hijack? is true then rack.hijack must respond to #call.
|
458
|
+
original_hijack = env['rack.hijack']
|
459
|
+
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
|
460
|
+
env['rack.hijack'] = proc do
|
461
|
+
## rack.hijack must return the io that will also be assigned (or is
|
462
|
+
## already present, in rack.hijack_io.
|
463
|
+
io = original_hijack.call
|
464
|
+
HijackWrapper.new(io)
|
465
|
+
##
|
466
|
+
## rack.hijack_io must respond to:
|
467
|
+
## <tt>read, write, read_nonblock, write_nonblock, flush, close,
|
468
|
+
## close_read, close_write, closed?</tt>
|
469
|
+
##
|
470
|
+
## The semantics of these IO methods must be a best effort match to
|
471
|
+
## those of a normal ruby IO or Socket object, using standard
|
472
|
+
## arguments and raising standard exceptions. Servers are encouraged
|
473
|
+
## to simply pass on real IO objects, although it is recognized that
|
474
|
+
## this approach is not directly compatible with SPDY and HTTP 2.0.
|
475
|
+
##
|
476
|
+
## IO provided in rack.hijack_io should preference the
|
477
|
+
## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
|
478
|
+
##
|
479
|
+
## There is a deliberate lack of full specification around
|
480
|
+
## rack.hijack_io, as semantics will change from server to server.
|
481
|
+
## Users are encouraged to utilize this API with a knowledge of their
|
482
|
+
## server choice, and servers may extend the functionality of
|
483
|
+
## hijack_io to provide additional features to users. The purpose of
|
484
|
+
## rack.hijack is for Rack to "get out of the way", as such, Rack only
|
485
|
+
## provides the minimum of specification and support.
|
486
|
+
env['rack.hijack_io'] = HijackWrapper.new(env['rack.hijack_io'])
|
487
|
+
io
|
488
|
+
end
|
489
|
+
else
|
490
|
+
##
|
491
|
+
## If rack.hijack? is false, then rack.hijack should not be set.
|
492
|
+
assert("rack.hijack? is false, but rack.hijack is present") { env['rack.hijack'].nil? }
|
493
|
+
##
|
494
|
+
## If rack.hijack? is false, then rack.hijack_io should not be set.
|
495
|
+
assert("rack.hijack? is false, but rack.hijack_io is present") { env['rack.hijack_io'].nil? }
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
## ==== Response (after headers)
|
500
|
+
## It is also possible to hijack a response after the status and headers
|
501
|
+
## have been sent.
|
502
|
+
def check_hijack_response(headers, env)
|
503
|
+
## In order to do this, an application may set the special header
|
504
|
+
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
505
|
+
## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
|
506
|
+
## protocol.
|
507
|
+
##
|
508
|
+
## After the headers have been sent, and this hijack callback has been
|
509
|
+
## called, the application is now responsible for the remaining lifecycle
|
510
|
+
## of the IO. The application is also responsible for maintaining HTTP
|
511
|
+
## semantics. Of specific note, in almost all cases in the current SPEC,
|
512
|
+
## applications will have wanted to specify the header Connection:close in
|
513
|
+
## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
|
514
|
+
## returning hijacked sockets to the web server. For that purpose, use the
|
515
|
+
## body streaming API instead (progressively yielding strings via each).
|
516
|
+
##
|
517
|
+
## Servers must ignore the <tt>body</tt> part of the response tuple when
|
518
|
+
## the <tt>rack.hijack</tt> response API is in use.
|
519
|
+
|
520
|
+
if env['rack.hijack?'] && headers['rack.hijack']
|
521
|
+
assert('rack.hijack header must respond to #call') {
|
522
|
+
headers['rack.hijack'].respond_to? :call
|
523
|
+
}
|
524
|
+
original_hijack = headers['rack.hijack']
|
525
|
+
headers['rack.hijack'] = proc do |io|
|
526
|
+
original_hijack.call HijackWrapper.new(io)
|
527
|
+
end
|
528
|
+
else
|
529
|
+
##
|
530
|
+
## The special response header <tt>rack.hijack</tt> must only be set
|
531
|
+
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
|
532
|
+
assert('rack.hijack header must not be present if server does not support hijacking') {
|
533
|
+
headers['rack.hijack'].nil?
|
534
|
+
}
|
535
|
+
end
|
536
|
+
end
|
537
|
+
## ==== Conventions
|
538
|
+
## * Middleware should not use hijack unless it is handling the whole
|
539
|
+
## response.
|
540
|
+
## * Middleware may wrap the IO object for the response pattern.
|
541
|
+
## * Middleware should not wrap the IO object for the request pattern. The
|
542
|
+
## request pattern is intended to provide the hijacker with "raw tcp".
|
543
|
+
|
419
544
|
## == The Response
|
420
545
|
|
421
546
|
## === The Status
|
@@ -432,6 +557,10 @@ module Rack
|
|
432
557
|
header.respond_to? :each
|
433
558
|
}
|
434
559
|
header.each { |key, value|
|
560
|
+
## Special headers starting "rack." are for communicating with the
|
561
|
+
## server, and must not be sent back to the client.
|
562
|
+
next if key =~ /^rack\..+$/
|
563
|
+
|
435
564
|
## The header keys must be Strings.
|
436
565
|
assert("header key must be a string, was #{key.class}") {
|
437
566
|
key.kind_of? String
|
@@ -463,9 +592,8 @@ module Rack
|
|
463
592
|
## === The Content-Type
|
464
593
|
def check_content_type(status, headers)
|
465
594
|
headers.each { |key, value|
|
466
|
-
## There must be a <tt>Content-Type</tt>,
|
467
|
-
##
|
468
|
-
## given.
|
595
|
+
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
|
596
|
+
## 204, 205 or 304.
|
469
597
|
if key.downcase == "content-type"
|
470
598
|
assert("Content-Type header found in #{status} response, not allowed") {
|
471
599
|
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
|
@@ -473,9 +601,6 @@ module Rack
|
|
473
601
|
return
|
474
602
|
end
|
475
603
|
}
|
476
|
-
assert("No Content-Type header found") {
|
477
|
-
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
|
478
|
-
}
|
479
604
|
end
|
480
605
|
|
481
606
|
## === The Content-Length
|
data/lib/rack/lobster.rb
CHANGED
@@ -59,7 +59,7 @@ end
|
|
59
59
|
if $0 == __FILE__
|
60
60
|
require 'rack'
|
61
61
|
require 'rack/showexceptions'
|
62
|
-
Rack::
|
63
|
-
Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
|
64
|
-
|
62
|
+
Rack::Server.start(
|
63
|
+
:app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292
|
64
|
+
)
|
65
65
|
end
|
data/lib/rack/lock.rb
CHANGED
data/lib/rack/methodoverride.rb
CHANGED
data/lib/rack/mime.rb
CHANGED
@@ -18,6 +18,35 @@ module Rack
|
|
18
18
|
end
|
19
19
|
module_function :mime_type
|
20
20
|
|
21
|
+
# Returns true if the given value is a mime match for the given mime match
|
22
|
+
# specification, false otherwise.
|
23
|
+
#
|
24
|
+
# Rack::Mime.match?('text/html', 'text/*') => true
|
25
|
+
# Rack::Mime.match?('text/plain', '*') => true
|
26
|
+
# Rack::Mime.match?('text/html', 'application/json') => false
|
27
|
+
|
28
|
+
def match?(value, matcher)
|
29
|
+
v1, v2 = value.split('/', 2)
|
30
|
+
m1, m2 = matcher.split('/', 2)
|
31
|
+
|
32
|
+
if m1 == '*'
|
33
|
+
if m2.nil? || m2 == '*'
|
34
|
+
return true
|
35
|
+
elsif m2 == v2
|
36
|
+
return true
|
37
|
+
else
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return false if v1 != m1
|
43
|
+
|
44
|
+
return true if m2.nil? || m2 == '*'
|
45
|
+
|
46
|
+
m2 == v2
|
47
|
+
end
|
48
|
+
module_function :match?
|
49
|
+
|
21
50
|
# List of most common mime-types, selected various sources
|
22
51
|
# according to their usefulness in a webserving scope for Ruby
|
23
52
|
# users.
|
@@ -2,8 +2,6 @@ require 'rack/utils'
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
module Multipart
|
5
|
-
class MultipartLimitError < Errno::EMFILE; end
|
6
|
-
|
7
5
|
class Parser
|
8
6
|
BUFSIZE = 16384
|
9
7
|
|
@@ -16,17 +14,10 @@ module Rack
|
|
16
14
|
|
17
15
|
fast_forward_to_first_boundary
|
18
16
|
|
19
|
-
opened_files = 0
|
20
17
|
loop do
|
21
|
-
|
22
18
|
head, filename, content_type, name, body =
|
23
19
|
get_current_head_and_filename_and_content_type_and_name_and_body
|
24
20
|
|
25
|
-
if Utils.multipart_part_limit > 0
|
26
|
-
opened_files += 1 if filename
|
27
|
-
raise MultipartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit
|
28
|
-
end
|
29
|
-
|
30
21
|
# Save the rest.
|
31
22
|
if i = @buf.index(rx)
|
32
23
|
body << @buf.slice!(0, i)
|
data/lib/rack/request.rb
CHANGED
@@ -98,10 +98,8 @@ module Rack
|
|
98
98
|
port.to_i
|
99
99
|
elsif port = @env['HTTP_X_FORWARDED_PORT']
|
100
100
|
port.to_i
|
101
|
-
elsif ssl?
|
102
|
-
443
|
103
101
|
elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
|
104
|
-
|
102
|
+
DEFAULT_PORTS[scheme]
|
105
103
|
else
|
106
104
|
@env["SERVER_PORT"].to_i
|
107
105
|
end
|
@@ -118,25 +116,25 @@ module Rack
|
|
118
116
|
|
119
117
|
# Checks the HTTP request method (or verb) to see if it was of type DELETE
|
120
118
|
def delete?; request_method == "DELETE" end
|
121
|
-
|
119
|
+
|
122
120
|
# Checks the HTTP request method (or verb) to see if it was of type GET
|
123
121
|
def get?; request_method == "GET" end
|
124
|
-
|
122
|
+
|
125
123
|
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
126
124
|
def head?; request_method == "HEAD" end
|
127
|
-
|
125
|
+
|
128
126
|
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
129
127
|
def options?; request_method == "OPTIONS" end
|
130
|
-
|
128
|
+
|
131
129
|
# Checks the HTTP request method (or verb) to see if it was of type PATCH
|
132
130
|
def patch?; request_method == "PATCH" end
|
133
|
-
|
131
|
+
|
134
132
|
# Checks the HTTP request method (or verb) to see if it was of type POST
|
135
133
|
def post?; request_method == "POST" end
|
136
|
-
|
134
|
+
|
137
135
|
# Checks the HTTP request method (or verb) to see if it was of type PUT
|
138
136
|
def put?; request_method == "PUT" end
|
139
|
-
|
137
|
+
|
140
138
|
# Checks the HTTP request method (or verb) to see if it was of type TRACE
|
141
139
|
def trace?; request_method == "TRACE" end
|
142
140
|
|
@@ -157,6 +155,10 @@ module Rack
|
|
157
155
|
'multipart/mixed'
|
158
156
|
]
|
159
157
|
|
158
|
+
# Default ports depending on scheme. Used to decide whether or not
|
159
|
+
# to include the port in a generated URI.
|
160
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
161
|
+
|
160
162
|
# Determine whether the request body contains form-data by checking
|
161
163
|
# the request Content-Type for one of the media-types:
|
162
164
|
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
@@ -217,10 +219,45 @@ module Rack
|
|
217
219
|
end
|
218
220
|
|
219
221
|
# The union of GET and POST data.
|
222
|
+
#
|
223
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
220
224
|
def params
|
221
225
|
@params ||= self.GET.merge(self.POST)
|
222
226
|
rescue EOFError
|
223
|
-
self.GET
|
227
|
+
self.GET.dup
|
228
|
+
end
|
229
|
+
|
230
|
+
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
231
|
+
#
|
232
|
+
# 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.
|
233
|
+
#
|
234
|
+
# env['rack.input'] is not touched.
|
235
|
+
def update_param(k, v)
|
236
|
+
found = false
|
237
|
+
if self.GET.has_key?(k)
|
238
|
+
found = true
|
239
|
+
self.GET[k] = v
|
240
|
+
end
|
241
|
+
if self.POST.has_key?(k)
|
242
|
+
found = true
|
243
|
+
self.POST[k] = v
|
244
|
+
end
|
245
|
+
unless found
|
246
|
+
self.GET[k] = v
|
247
|
+
end
|
248
|
+
@params = nil
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
# Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
|
253
|
+
#
|
254
|
+
# If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
|
255
|
+
#
|
256
|
+
# env['rack.input'] is not touched.
|
257
|
+
def delete_param(k)
|
258
|
+
v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
|
259
|
+
@params = nil
|
260
|
+
v
|
224
261
|
end
|
225
262
|
|
226
263
|
# shortcut for request.params[key]
|
@@ -229,6 +266,8 @@ module Rack
|
|
229
266
|
end
|
230
267
|
|
231
268
|
# shortcut for request.params[key] = value
|
269
|
+
#
|
270
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
232
271
|
def []=(key, value)
|
233
272
|
params[key.to_s] = value
|
234
273
|
end
|
@@ -271,14 +310,8 @@ module Rack
|
|
271
310
|
end
|
272
311
|
|
273
312
|
def base_url
|
274
|
-
url = scheme
|
275
|
-
url <<
|
276
|
-
|
277
|
-
if scheme == "https" && port != 443 ||
|
278
|
-
scheme == "http" && port != 80
|
279
|
-
url << ":#{port}"
|
280
|
-
end
|
281
|
-
|
313
|
+
url = "#{scheme}://#{host}"
|
314
|
+
url << ":#{port}" if port != DEFAULT_PORTS[scheme]
|
282
315
|
url
|
283
316
|
end
|
284
317
|
|
@@ -307,16 +340,16 @@ module Rack
|
|
307
340
|
end
|
308
341
|
|
309
342
|
def trusted_proxy?(ip)
|
310
|
-
ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost
|
343
|
+
ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$|^unix$|^unix:/i
|
311
344
|
end
|
312
345
|
|
313
346
|
def ip
|
314
|
-
remote_addrs = @env['REMOTE_ADDR']
|
315
|
-
remote_addrs
|
316
|
-
|
347
|
+
remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
|
348
|
+
remote_addrs = reject_trusted_ip_addresses(remote_addrs)
|
349
|
+
|
317
350
|
return remote_addrs.first if remote_addrs.any?
|
318
351
|
|
319
|
-
forwarded_ips = @env['HTTP_X_FORWARDED_FOR']
|
352
|
+
forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
|
320
353
|
|
321
354
|
if client_ip = @env['HTTP_CLIENT_IP']
|
322
355
|
# If forwarded_ips doesn't include the client_ip, it might be an
|
@@ -324,10 +357,18 @@ module Rack
|
|
324
357
|
return client_ip if forwarded_ips.include?(client_ip)
|
325
358
|
end
|
326
359
|
|
327
|
-
return forwarded_ips
|
360
|
+
return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
|
328
361
|
end
|
329
362
|
|
330
363
|
protected
|
364
|
+
def split_ip_addresses(ip_addresses)
|
365
|
+
ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
|
366
|
+
end
|
367
|
+
|
368
|
+
def reject_trusted_ip_addresses(ip_addresses)
|
369
|
+
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
370
|
+
end
|
371
|
+
|
331
372
|
def parse_query(qs)
|
332
373
|
Utils.parse_nested_query(qs)
|
333
374
|
end
|