diff options
23 files changed, 272 insertions, 178 deletions
@@ -40,10 +40,10 @@ task :install => :gem do version = File.read("RAILS_VERSION").strip (PROJECTS - ["railties"]).each do |project| puts "INSTALLING #{project}" - system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc") + system("gem install #{project}/pkg/#{project}-#{version}.gem --local --no-ri --no-rdoc") end - system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc") - system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc") + system("gem install railties/pkg/railties-#{version}.gem --local --no-ri --no-rdoc") + system("gem install pkg/rails-#{version}.gem --local --no-ri --no-rdoc") end desc "Generate documentation for the Rails framework" diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index c43906612c..3e8441343f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,6 +1,30 @@ ## Rails 4.0.0 (unreleased) ## -* Fix a bug in `content_tag_for` that prevents it for work without a block. +* Return the last valid, non-private IP address from the X-Forwarded-For, + Client-IP and Remote-Addr headers, in that order. Document the rationale + for that decision, and describe the options that can be passed to the + RemoteIp middleware to change it. + Fix #7979 + + *André Arko*, *Steve Klabnik*, *Alexey Gaziev* + +* Do not append second slash to `root_url` when using `trailing_slash: true` + Fix #8700 + + Example: + # before + root_url # => http://test.host// + + # after + root_url # => http://test.host/ + + *Yves Senn* + +* Allow to toggle dumps on error pages. + + *Gosha Arinich* + +* Fix a bug in `content_tag_for` that prevents it from working without a block. *Jasl* diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index bced7d84c0..43f26d696d 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -32,7 +32,11 @@ module ActionDispatch params.reject! { |_,v| v.to_param.nil? } result = build_host_url(options) - result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + if options[:trailing_slash] && !path.ends_with?('/') + result << path.sub(/(\?|\z)/) { "/" + $& } + else + result << path + end result << "?#{params.to_query}" unless params.empty? result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] result diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 4a344f71af..cf755bfbeb 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -37,19 +37,16 @@ module ActionDispatch private def extract_parameterized_parts(route, options, recall, parameterize = nil) - constraints = recall.merge(options) - data = constraints.dup + parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts - (data.keys - keys_to_keep).each do |bad_key| - data.delete(bad_key) + (parameterized_parts.keys - keys_to_keep).each do |bad_key| + parameterized_parts.delete(bad_key) end - parameterized_parts = data.dup - if parameterize parameterized_parts.each do |k, v| parameterized_parts[k] = parameterize.call(k, v) diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 5abf8f2802..4e36c9bb49 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,23 +1,59 @@ module ActionDispatch + # This middleware calculates the IP address of the remote client that is + # making the request. It does this by checking various headers that could + # contain the address, and then picking the last-set address that is not + # on the list of trusted IPs. This follows the precendent set by e.g. + # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453], + # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection] + # by @gingerlime. A more detailed explanation of the algorithm is given + # at GetIp#calculate_ip. + # + # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] + # requires. Some Rack servers simply drop preceeding headers, and only report + # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. + # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn) + # then you should test your Rack server to make sure your data is good. + # + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. + # This middleware assumes that there is at least one proxy sitting around + # and setting headers with the client's remote IP address. If you don't use + # a proxy, because you are hosted on e.g. Heroku without SSL, any client can + # claim to have any IP address by setting the X-Forwarded-For header. If you + # care about that, then you need to explicitly drop or ignore those headers + # sometime before this middleware runs. class RemoteIp - class IpSpoofAttackError < StandardError ; end + class IpSpoofAttackError < StandardError; end - # 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 - # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses. + # The default trusted IPs list simply includes IP addresses that are + # guaranteed by the IP specification to be private addresses. Those will + # not be the ultimate client IP in production, and so are discarded. See + # http://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^::1$ | - ^(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 - fc00:: # private IP fc00 - )\. + ^127\.0\.0\.1$ | # localhost IPv4 + ^::1$ | # localhost IPv6 + ^fc00: | # private IPv6 range fc00 + ^10\. | # private IPv4 range 10.x.x.x + ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255 + ^192\.168\. # private IPv4 range 192.168.x.x }x attr_reader :check_ip, :proxies + # Create a new +RemoteIp+ middleware instance. + # + # The +check_ip_spoofing+ option is on by default. When on, an exception + # is raised if it looks like the client is trying to lie about its own IP + # address. It makes sense to turn off this check on sites aimed at non-IP + # clients (like WAP devices), or behind proxies that set headers in an + # incorrect or confusing way (like AWS ELB). + # + # The +custom_trusted+ argument can take a regex, which will be used + # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition + # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the + # middle (or at the beginning) of the X-Forwarded-For list, with your proxy + # servers after it. If your proxies aren't removed, pass them in via the + # +custom_trusted+ parameter. That way, the middleware will ignore those + # IP addresses, and return the one that you want. def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app @check_ip = check_ip_spoofing @@ -31,15 +67,23 @@ module ActionDispatch end end + # Since the IP address may not be needed, we store the object here + # without calculating the IP to keep from slowing down the majority of + # requests. For those requests that do need to know the IP, the + # GetIp#calculate_ip method will calculate the memoized client IP address. def call(env) env["action_dispatch.remote_ip"] = GetIp.new(env, self) @app.call(env) end + # The GetIp class exists as a way to defer processing of the request data + # into an actual IP address. If the ActionDispatch::Request#remote_ip method + # is called, this class will calculate the value and then memoize it. class GetIp - # IP v4 and v6 (with compression) validation regexp - # https://gist.github.com/1289635 + # This constant contains a regular expression that validates every known + # form of IP v4 and v6 address, with or without abbreviations, adapted + # from {this gist}[https://gist.github.com/1289635]. VALID_IP = %r{ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4 (^( @@ -63,62 +107,78 @@ module ActionDispatch }x def initialize(env, middleware) - @env = env - @middleware = middleware - @ip = nil + @env = env + @check_ip = middleware.check_ip + @proxies = middleware.proxies 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 first address which is in this list - # if it's not a known proxy will be the originating IP. - # Format of HTTP_X_FORWARDED_FOR: - # client_ip, proxy_ip1, proxy_ip2... - # http://en.wikipedia.org/wiki/X-Forwarded-For + # Sort through the various IP address headers, looking for the IP most + # likely to be the address of the actual remote client making this + # request. + # + # REMOTE_ADDR will be correct if the request is made directly against the + # Ruby process, on e.g. Heroku. When the request is proxied by another + # server like HAProxy or Nginx, the IP address that made the original + # request will be put in an X-Forwarded-For header. If there are multiple + # proxies, that header may contain a list of IPs. Other proxy services + # set the Client-Ip header instead, so we check that too. + # + # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], + # while the first IP in the list is likely to be the "originating" IP, + # it could also have been set by the client maliciously. + # + # In order to find the first address that is (probably) accurate, we + # take the list of IPs, remove known and trusted proxies, and then take + # the last address left, which was presumably set by one of those proxies. def calculate_ip - client_ip = @env['HTTP_CLIENT_IP'] - forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first - remote_addrs = ips_from('REMOTE_ADDR') - - check_ip = client_ip && @middleware.check_ip - if check_ip && forwarded_ip != client_ip + # Set by the Rack web server, this is a single value. + remote_addr = ips_from('REMOTE_ADDR').last + + # Could be a CSV list and/or repeated headers that were concatenated. + client_ips = ips_from('HTTP_CLIENT_IP').reverse + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse + + # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. + # If they are both set, it means that this request passed through two + # proxies with incompatible IP header conventions, and there is no way + # for us to determine which header is the right one after the fact. + # Since we have no idea, we give up and explode. + should_check_ip = @check_ip && client_ips.last + if should_check_ip && !forwarded_ips.include?(client_ips.last) # 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}" \ + raise IpSpoofAttackError, "IP spoofing attack?! " + + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten - if client_ips.present? - client_ips.first - else - # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR - remote_addrs.find { |ip| valid_ip? ip } - end + # We assume these things about the IP headers: + # + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack + ips = [forwarded_ips, client_ips, remote_addr].flatten.compact + + # If every single IP option is in the trusted list, just return REMOTE_ADDR + filter_proxies(ips).first || remote_addr end + # Memoizes the value returned by #calculate_ip and returns it for + # ActionDispatch::Request to use. def to_s @ip ||= calculate_ip end - private + protected def ips_from(header) - @env[header] ? @env[header].strip.split(/[,\s]+/) : [] - end - - def valid_ip?(ip) - ip =~ VALID_IP - end - - def not_a_proxy?(ip) - ip !~ @middleware.proxies + # Split the comma-separated list into an array of strings + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + # Only return IPs that are valid according to the regex + ips.select{ |ip| ip =~ VALID_IP } end - def remove_proxies(ips) - ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) } + def filter_proxies(ips) + ips.reject { |ip| ip =~ @proxies } end end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 55079848bd..fbb47f60a2 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -1,6 +1,6 @@ <% unless @exception.blamed_files.blank? %> <% if (hide = @exception.blamed_files.length > 8) %> - <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a> + <a href="#" onclick="s = document.getElementById('blame_trace').style; s.display = s.display == 'none' ? 'block' : 'none'; return false;">Toggle blamed files</a> <% end %> <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre> <% end %> @@ -18,17 +18,17 @@ %> <h2 style="margin-top: 30px">Request</h2> -<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p> +<p><b>Parameters</b>:</p> <pre><%=h request_dump %></pre> <div class="details"> - <div class="summary"><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></div> - <div id="session_dump" style="display:none"><p><pre><%= debug_hash @request.session %></pre></p></div> + <div class="summary"><a href="#" onclick="s = document.getElementById('session_dump').style; s.display = s.display == 'none' ? 'block' : 'none'; return false;">Toggle session dump</a></div> + <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> </div> <div class="details"> - <div class="summary"><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></div> - <div id="env_dump" style="display:none"><p><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></p></div> + <div class="summary"><a href="#" onclick="s = document.getElementById('env_dump').style; s.display = s.display == 'none' ? 'block' : 'none'; return false;">Toggle env dump</a></div> + <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> </div> <h2 style="margin-top: 30px">Response</h2> -<p><b>Headers</b>: <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p> +<p><b>Headers</b>:</p> <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 9be2b65ef4..ff33430532 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -5,7 +5,7 @@ <title>Action Controller: Exception caught</title> <style> body { - background-color: #FFF; + background-color: #FAFAFA; color: #333; margin: 0px; } @@ -29,21 +29,18 @@ } header { - background: #980905; - background: -webkit-linear-gradient(270deg, #980905, #C52F24); - background: linear-gradient(270deg, #980905, #C52F24); - color: #FFF; - padding: 0.5em; + color: #F0F0F0; + background: #C52F24; + padding: 0.5em 1.5em; } h2 { color: #C52F24; - padding: 2px; line-height: 25px; } .details { - border: 1px solid #E5E5E5; + border: 1px solid #D0D0D0; border-radius: 4px; margin: 1em 0px; display: block; @@ -52,7 +49,7 @@ .summary { padding: 8px 15px; - border-bottom: 1px solid #E5E5E5; + border-bottom: 1px solid #D0D0D0; display: block; } @@ -62,8 +59,9 @@ } #container { - margin: auto; - width: 98%; + box-sizing: border-box; + width: 100%; + padding: 0 1.5em; } .source * { diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb index 01faf5a475..9f3816bf40 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -9,12 +9,12 @@ <div id="container"> <p> Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised: - <pre><code><%=h @exception.message %></code></pre> - </p> + </p> + <pre><code><%=h @exception.message %></code></pre> <div class="source"> <div class="info"> - <p>Extracted source (around line <strong>#<%=h @exception.line_number %></strong>): + <p>Extracted source (around line <strong>#<%=h @exception.line_number %></strong>):</p> </div> <div class="data"> <table cellpadding="0" cellspacing="0" class="lines"> @@ -40,5 +40,4 @@ <%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %> - </div> </div> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0dc6403ec0..a21383e091 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -26,15 +26,10 @@ module ActionDispatch def matches?(env) req = @request.new(env) - @constraints.each { |constraint| - if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return false - elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)) - return false - end - } - - return true + @constraints.none? do |constraint| + (constraint.respond_to?(:matches?) && !constraint.matches?(req)) || + (constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))) + end ensure req.reset_parameters end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 5fb742157c..50d3abd2fb 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -435,10 +435,8 @@ module ActionView builder = options[:parent_builder] = instantiate_builder(object_name, object, options) fields_for = fields_for(object_name, object, options, &proc) - default_options = builder.multipart? ? { multipart: true } : {} - default_options.merge!(options.delete(:html)) - - form_tag(options.delete(:url) || {}, default_options) { fields_for } + default_options = builder.form_tag_attributes + form_tag(options[:url] || {}, default_options) { fields_for } end def apply_form_for_options!(record, object, options) #:nodoc: @@ -1201,6 +1199,7 @@ module ActionView @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options + @form_tag_attributes = options.fetch(:html, {}) @parent_builder = options[:parent_builder] @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) @@ -1214,6 +1213,11 @@ module ActionView @index = options[:index] || options[:child_index] end + def form_tag_attributes + options = multipart? ? { multipart: true } : {} + options.merge! @form_tag_attributes + end + (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) # def text_field(method, options = {}) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index ff83ef3ca1..479739bebd 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -184,7 +184,7 @@ module ActionView # # => <label for="name">Name</label> # # label_tag 'name', 'Your name' - # # => <label for="name">Your Name</label> + # # => <label for="name">Your name</label> # # label_tag 'name', nil, class: 'small_label' # # => <label for="name" class="small_label">Name</label> diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f0430e516f..5e821046db 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -57,13 +57,13 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase end class MockController - def self.build(helpers) + def self.build(helpers, additional_options = {}) Class.new do - def url_options - options = super + define_method :url_options do + options = super() options[:protocol] ||= "http" options[:host] ||= "test.host" - options + options.merge(additional_options) end include helpers @@ -428,8 +428,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase routes.send(:pages_url) end - def setup_for_named_route - MockController.build(rs.url_helpers).new + def setup_for_named_route(options = {}) + MockController.build(rs.url_helpers, options).new end def test_named_route_without_hash @@ -456,6 +456,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_trailing_slash + rs.draw do + root "hello#index" + end + + routes = setup_for_named_route(trailing_slash: true) + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("http://test.host/?foo=bar", routes.send(:root_url, foo: :bar)) + end + def test_named_route_with_regexps rs.draw do get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 4e59e214c6..02675c7f8c 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -34,7 +34,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal '1.2.3.4', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' - assert_equal '1.2.3.4', request.remote_ip + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -47,30 +47,32 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1' + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -79,26 +81,20 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message) assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message) + end - # turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip with spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2', :ip_spoofing_check => false - assert_equal '2.2.2.2', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8' - assert_equal '9.9.9.9', request.remote_ip + assert_equal '1.1.1.1', request.remote_ip end test "remote ip v6" do request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', @@ -109,30 +105,26 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip v6 spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -141,26 +133,15 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message) assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message) + end - # Turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip v6 spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', :ip_spoofing_check => false - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip end - test "remote ip when the remote ip middleware returns nil" do - request = stub_request 'REMOTE_ADDR' => '127.0.0.1' - assert_equal '127.0.0.1', request.remote_ip - end - test "remote ip with user specified trusted proxies String" do @trusted_proxies = "67.205.106.73" @@ -170,16 +151,16 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '172.16.0.1', request.remote_ip + assert_equal '67.205.106.73', request.remote_ip request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip end @@ -196,13 +177,13 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1', 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + assert_equal '::1', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip end test "remote ip with user specified trusted proxies Regexp" do @@ -212,8 +193,8 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73' + assert_equal '3.4.5.6', request.remote_ip end test "remote ip v6 with user specified trusted proxies Regexp" do @@ -223,8 +204,13 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + end + + test "remote ip middleware not present still returns an IP" do + request = ActionDispatch::Request.new({'REMOTE_ADDR' => '127.0.0.1'}) + assert_equal '127.0.0.1', request.remote_ip end test "domains" do @@ -602,7 +588,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal request.format.xml?, false assert_equal request.format.json?, false end - + test "formats with xhr request" do request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0cbf664853..db8fa303be 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -25,7 +25,8 @@ If migrating down, the given migration / block is run normally. See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) - Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now raise an IrreversibleMigration instead of actually executing them without any output. + Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now + raise an `IrreversibleMigration` instead of actually executing them without any output. *Marc-André Lafortune* diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7db7953313..a9fa107749 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -15,6 +15,18 @@ require 'models/toy' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations + def test_find_by_id_with_hash + assert_raises(ActiveRecord::StatementInvalid) do + Post.find_by_id(:limit => 1) + end + end + + def test_find_by_title_and_id_with_hash + assert_raises(ActiveRecord::StatementInvalid) do + Post.find_by_title_and_id('foo', :limit => 1) + end + end + def test_find assert_equal(topics(:first).title, Topic.find(1).title) end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index fb975dcfc3..6917be7efc 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,6 +1,10 @@ ## Rails 4.0.0 (unreleased) ## -* Introduce assert_not to replace warty 'assert !foo'. *Jeremy Kemper* +* Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`. + + *Akira Matsuda* + +* Introduce `assert_not` to replace warty `assert !foo`. *Jeremy Kemper* * Prevent `Callbacks#set_callback` from setting the same callback twice. @@ -11,7 +15,7 @@ *Dmitriy Kiriyenko* -* Add ActiveSupport::Logger#silence that works the same as the old Logger#silence extension. +* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension. *DHH* diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 6e93932d49..5fe8e2fba6 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -197,7 +197,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `Rails::Rack::Logger` notifies the logs that the request has began. After request is complete, flushes all the logs. * `ActionDispatch::ShowExceptions` rescues any exception returned by the application and renders nice exception pages if the request is local or if `config.consider_all_requests_local` is set to `true`. If `config.action_dispatch.show_exceptions` is set to `false`, exceptions will be raised regardless. * `ActionDispatch::RequestId` makes a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. -* `ActionDispatch::RemoteIp` checks for IP spoofing attacks. Configurable with the `config.action_dispatch.ip_spoofing_check` and `config.action_dispatch.trusted_proxies` settings. +* `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options. * `Rack::Sendfile` intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with `config.action_dispatch.x_sendfile_header`. * `ActionDispatch::Callbacks` runs the prepare callbacks before serving the request. * `ActiveRecord::ConnectionAdapters::ConnectionManagement` cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. diff --git a/install.rb b/install.rb index b87b008c2e..bf843e251e 100644 --- a/install.rb +++ b/install.rb @@ -2,10 +2,10 @@ version = ARGV.pop %w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework| puts "Installing #{framework}..." - `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem` + `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --local --no-ri --no-rdoc && rm #{framework}-#{version}.gem` end puts "Installing Rails..." `gem build rails.gemspec` -`gem install rails-#{version}.gem --no-ri --no-rdoc ` +`gem install rails-#{version}.gem --local --no-ri --no-rdoc ` `rm rails-#{version}.gem` diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 30e1650555..18bd1ece9d 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -36,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase end test "should update <%= singular_table_name %>" do - put :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> + patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 8af4130e87..e52d1a8b90 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -56,8 +56,8 @@ module Rails end def add(path, options={}) - with = options[:with] || path - @root[path] = Path.new(self, path, [with].flatten, options) + with = Array(options[:with] || path) + @root[path] = Path.new(self, path, with, options) end def [](path) diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index fde13eeb94..f0d3438aa4 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -40,7 +40,7 @@ module ApplicationTests end assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do - assert_equal "1.1.1.2", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") end end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 290e4b57cc..ab00586a64 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -101,7 +101,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) - assert_match(/put :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) + assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) end end @@ -112,7 +112,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ \}/, content) - assert_match(/put :update, id: @user, user: \{ \}/, content) + assert_match(/patch :update, id: @user, user: \{ \}/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index fbc9cd1321..431b23b014 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -61,7 +61,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) - assert_match(/put :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) + assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) end # Views @@ -87,7 +87,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, product_line: \{ \}/, content) - assert_match(/put :update, id: @product_line, product_line: \{ \}/, content) + assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content) end end |