diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
8 files changed, 121 insertions, 68 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..69ca050d0c 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -155,24 +155,7 @@ 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 end 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..58e25aed5a 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -2,50 +2,69 @@ 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_spoofing, :trusted_proxies + + def initialize(app, check_ip_spoofing = true, custom_proxies = nil) + @app = app + @check_ip_spoofing = check_ip_spoofing + if custom_proxies + custom_regexp = Regexp.new(custom_proxies) + @trusted_proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp) + else + @trusted_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 + # 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 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 + 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_spoofing + 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 + protected - def call(env) - env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) - @app.call(env) + def ips_from(header) + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + ips.reject{|ip| ip =~ @middleware.trusted_proxies } + end end + end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 968ad6c28d..bee446c8a5 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,4 +1,6 @@ -require 'digest/md5' +require 'securerandom' +require 'active_support/core_ext/string/access' +require 'active_support/core_ext/object/blank' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through @@ -17,22 +19,21 @@ module ActionDispatch def call(env) env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id - status, headers, body = @app.call(env) headers["X-Request-Id"] = env["action_dispatch.request_id"] [ status, headers, body ] end - + private def external_request_id(env) - if env["HTTP_X_REQUEST_ID"].present? - env["HTTP_X_REQUEST_ID"].gsub(/[^\w\d\-]/, "").first(255) + if request_id = env["HTTP_X_REQUEST_ID"].presence + request_id.gsub(/[^\w\-]/, "").first(255) end end - + def internal_request_id - SecureRandom.uuid + SecureRandom.hex(16) end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb new file mode 100644 index 0000000000..d3b6fd12fa --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -0,0 +1,50 @@ +require 'action_dispatch/middleware/session/abstract_store' +require 'rack/session/memcache' + +module ActionDispatch + module Session + # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful + # if you don't store critical data in your sessions and you don't need them to live for extended periods + # of time. + class CacheStore < AbstractStore + # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is + # not specified, <tt>Rails.cache</tt> will be used. + def initialize(app, options = {}) + @cache = options[:cache] || Rails.cache + options[:expire_after] ||= @cache.options[:expires_in] + super + end + + # Get a session from the cache. + def get_session(env, sid) + sid ||= generate_sid + session = @cache.read(cache_key(sid)) + session ||= {} + [sid, session] + end + + # Set a session in the cache. + def set_session(env, sid, session, options) + key = cache_key(sid) + if session + @cache.write(key, session, :expires_in => options[:expire_after]) + else + @cache.delete(key) + end + sid + end + + # Remove a session from the cache. + def destroy_session(env, sid, options) + @cache.delete(cache_key(sid)) + generate_sid + end + + private + # Turn the session id into a cache key. + def cache_key(sid) + "_session_id:#{sid}" + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ef31d1e004..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) @@ -696,7 +696,7 @@ module ActionDispatch # Allows you to constrain the nested routes based on a set of rules. # For instance, in order to change the routes to allow for a dot character in the +id+ parameter: # - # constraints(:id => /\d+\.\d+) do + # constraints(:id => /\d+\.\d+/) do # resources :posts # end # @@ -706,7 +706,7 @@ module ActionDispatch # You may use this to also restrict other parameters: # # resources :posts do - # constraints(:post_id => /\d+\.\d+) do + # constraints(:post_id => /\d+\.\d+/) do # resources :comments # end # end @@ -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. |