diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r-- | actionpack/lib/action_dispatch/http/headers.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/request.rb | 22 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/http/url.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/remote_ip.rb | 97 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 6 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/route_set.rb | 2 |
6 files changed, 70 insertions, 61 deletions
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 505d5560b1..040b51e040 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,5 +1,3 @@ -require 'active_support/memoizable' - module ActionDispatch module Http class Headers < ::Hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 7a5237dcf3..b10f6b48c7 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -155,26 +155,10 @@ module ActionDispatch @ip ||= super end - # Which IP addresses are "trusted proxies" that can be stripped from - # the right-hand-side of X-Forwarded-For. - # - # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces. - TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^(10 | # private IP 10.x.x.x - 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 # private IP 192.168.x.x - )\. - }x - - # Determines originating IP address. REMOTE_ADDR is the standard - # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or - # HTTP_X_FORWARDED_FOR are set by proxies so check for these if - # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma- - # delimited list in the case of multiple chained proxies; the last - # address which is not trusted is the originating IP. + # Originating IP address, usually set by the RemoteIp middleware. def remote_ip - @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s + # Coerce the remote_ip object into a string, because to_s could return nil + @remote_ip ||= @env["action_dispatch.remote_ip"].to_s || ip end # Returns the unique request id, which is based off either the X-Request-Id header that can diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index c8ddd07bfa..129a8b1031 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -64,7 +64,7 @@ module ActionDispatch end def host_or_subdomain_and_domain(options) - return options[:host] if options[:subdomain].nil? && options[:domain].nil? + return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?) tld_length = options[:tld_length] || @@tld_length diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index c7d710b98e..ee0d19a50d 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -2,50 +2,75 @@ module ActionDispatch class RemoteIp class IpSpoofAttackError < StandardError ; end - class RemoteIpGetter - def initialize(env, check_ip_spoofing, trusted_proxies) - @env = env - @check_ip_spoofing = check_ip_spoofing - @trusted_proxies = trusted_proxies + # IP addresses that are "trusted proxies" that can be stripped from + # the comma-delimited list in the X-Forwarded-For header. See also: + # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces + TRUSTED_PROXIES = %r{ + ^127\.0\.0\.1$ | # localhost + ^(10 | # private IP 10.x.x.x + 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 + 192\.168 # private IP 192.168.x.x + )\. + }x + + attr_reader :check_ip, :proxies + + def initialize(app, check_ip_spoofing = true, custom_proxies = nil) + @app = app + @check_ip = check_ip_spoofing + if custom_proxies + custom_regexp = Regexp.new(custom_proxies) + @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp) + else + @proxies = TRUSTED_PROXIES end + end - def remote_addrs - @remote_addrs ||= begin - list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : [] - list.reject { |addr| addr =~ @trusted_proxies } - end + def call(env) + env["action_dispatch.remote_ip"] = GetIp.new(env, self) + @app.call(env) + end + + class GetIp + def initialize(env, middleware) + @env, @middleware = env, middleware end - def to_s - return remote_addrs.first if remote_addrs.any? - - forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : [] - - if client_ip = @env['HTTP_CLIENT_IP'] - if @check_ip_spoofing && !forwarded_ips.include?(client_ip) - # We don't know which came from the proxy, and which from the user - raise IpSpoofAttackError, "IP spoofing attack?!" \ - "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ - "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" - end - return client_ip + # Determines originating IP address. REMOTE_ADDR is the standard + # but will be wrong if the user is behind a proxy. Proxies will set + # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. + # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of + # multiple chained proxies. The last address which is not a known proxy + # will be the originating IP. + def calculate_ip + client_ip = @env['HTTP_CLIENT_IP'] + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR') + remote_addrs = ips_from('REMOTE_ADDR') + + check_ip = client_ip && @middleware.check_ip + if check_ip && !forwarded_ips.include?(client_ip) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?!" \ + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"] + client_ip || forwarded_ips.last || remote_addrs.first end - end - def initialize(app, check_ip_spoofing = true, trusted_proxies = nil) - @app = app - @check_ip_spoofing = check_ip_spoofing - regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)' - regex << "|(#{trusted_proxies})" if trusted_proxies - @trusted_proxies = Regexp.new(regex, "i") - end + def to_s + return @ip if @calculated_ip + @calculated_ip = true + @ip = calculate_ip + end - def call(env) - env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) - @app.call(env) + protected + + def ips_from(header) + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + ips.reject{|ip| ip =~ @middleware.proxies } + end end + end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 09a8c10043..970236a05a 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -285,7 +285,7 @@ module ActionDispatch # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" } + # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] } # match 'photos/:id' => PhotoRackApp # # Yes, controller actions are just rack endpoints # match 'photos/:id' => PhotosController.action(:show) @@ -735,7 +735,7 @@ module ActionDispatch # if the user should be given access to that route, or +false+ if the user should not. # # class Iphone - # def self.matches(request) + # def self.matches?(request) # request.env["HTTP_USER_AGENT"] =~ /iPhone/ # end # end @@ -1023,6 +1023,7 @@ module ActionDispatch # creates seven different routes in your application, all mapping to # the +Photos+ controller: # + # GET /photos # GET /photos/new # POST /photos # GET /photos/:id @@ -1038,6 +1039,7 @@ module ActionDispatch # # This generates the following comments routes: # + # GET /photos/:photo_id/comments # GET /photos/:photo_id/comments/new # POST /photos/:photo_id/comments # GET /photos/:photo_id/comments/:id diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index e7bc431783..2bcde16110 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -37,7 +37,7 @@ module ActionDispatch # If this is a default_controller (i.e. a controller specified by the user) # we should raise an error in case it's not found, because it usually means - # an user error. However, if the controller was retrieved through a dynamic + # a user error. However, if the controller was retrieved through a dynamic # segment, as in :controller(/:action), we should simply return nil and # delegate the control back to Rack cascade. Besides, if this is not a default # controller, it means we should respect the @scope[:module] parameter. |