aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb29
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb37
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb41
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb32
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb13
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb43
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb12
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb80
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb34
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb)0
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb)0
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb)2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb)2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb)2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb)2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb5
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb12
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb30
-rw-r--r--actionpack/lib/action_dispatch/routing.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb87
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb63
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb74
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb23
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb20
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb10
54 files changed, 567 insertions, 311 deletions
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 40bb060d52..c33ba201e1 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,6 +10,8 @@ module ActionDispatch
self.ignore_accept_header = false
end
+ attr_reader :variant
+
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
@@ -48,7 +50,7 @@ module ActionDispatch
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
#
def format(view_path = [])
- formats.first
+ formats.first || Mime::NullType.instance
end
def formats
@@ -64,6 +66,18 @@ module ActionDispatch
end
end
+ # Sets the \variant for template.
+ def variant=(variant)
+ if variant.is_a? Symbol
+ @variant = variant
+ else
+ raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
+ "For security reasons, never directly set the variant to a user-provided value, " \
+ "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
+ "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
+ end
+ end
+
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index f29ad359ac..3d2dd2d632 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,5 +1,6 @@
require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'singleton'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
module Mime
@@ -27,7 +28,7 @@ module Mime
class << self
def [](type)
return type if type.is_a?(Type)
- Type.lookup_by_extension(type) || NullType.new
+ Type.lookup_by_extension(type)
end
def fetch(type)
@@ -53,10 +54,6 @@ module Mime
@@html_types = Set.new [:html, :all]
cattr_reader :html_types
- # These are the content types which browsers can generate without using ajax, flash, etc
- # i.e. following a link, getting an image or posting a form. CSRF protection
- # only needs to protect against these types.
- @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
attr_reader :symbol
@register_callbacks = []
@@ -179,7 +176,7 @@ module Mime
def parse(accept_header)
if accept_header !~ /,/
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
- parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)]
+ parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
list, index = AcceptList.new, 0
accept_header.split(',').each do |header|
@@ -272,18 +269,6 @@ module Mime
end
end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
- # ActionController::RequestForgeryProtection.
- def verify_request?
- ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types.include?(to_sym)
- end
-
- def self.browser_generated_types
- ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types
- end
-
def html?
@@html_types.include?(to_sym) || @string =~ /html/
end
@@ -308,13 +293,13 @@ module Mime
end
class NullType
+ include Singleton
+
def nil?
true
end
- def ref
- nil
- end
+ def ref; end
def respond_to_missing?(method, include_private = false)
method.to_s.ends_with? '?'
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index a6b3aee5e7..0e4da36038 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -7,6 +7,7 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
+Mime::Type.register "text/vcard", :vcf
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 20c24ddd85..dcb299ed03 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -57,26 +57,25 @@ module ActionDispatch
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
def normalize_encode_params(params)
- if params.is_a?(String)
- return params.force_encoding(Encoding::UTF_8).encode!
- elsif !params.is_a?(Hash)
- return params
- end
-
- new_hash = {}
- params.each do |k, v|
- new_key = k.is_a?(String) ? k.dup.force_encoding(Encoding::UTF_8).encode! : k
- new_hash[new_key] =
- case v
- when Hash
- normalize_encode_params(v)
- when Array
- v.map! {|el| normalize_encode_params(el) }
- else
- normalize_encode_params(v)
- end
+ case params
+ when String
+ params.force_encoding(Encoding::UTF_8).encode!
+ when Hash
+ if params.has_key?(:tempfile)
+ UploadedFile.new(params)
+ else
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
+ new_hash[new_key] = if val.is_a?(Array)
+ val.map! { |el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(val)
+ end
+ end.with_indifferent_access
+ end
+ else
+ params
end
- new_hash.with_indifferent_access
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index ebd87c40b5..1318c62fbe 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -18,10 +18,10 @@ module ActionDispatch
include ActionDispatch::Http::MimeNegotiation
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
- include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
autoload :Session, 'action_dispatch/request/session'
+ autoload :Utils, 'action_dispatch/request/utils'
LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
@@ -225,7 +225,7 @@ module ActionDispatch
def raw_post
unless @env.include? 'RAW_POST_DATA'
raw_post_body = body
- @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i)
+ @env['RAW_POST_DATA'] = raw_post_body.read(content_length)
raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
@@ -271,7 +271,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -279,7 +279,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
@@ -299,33 +299,24 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- # Remove nils from the params hash
+ # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility.
def deep_munge(hash)
- hash.each do |k, v|
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x) }
- v.compact!
- hash[k] = nil if v.empty?
- when Hash
- deep_munge(v)
- end
- end
+ ActiveSupport::Deprecation.warn(
+ "This method has been extracted into ActionDispatch::Request::Utils.deep_munge. Please start using that instead."
+ )
- hash
+ Utils.deep_munge(hash)
end
protected
-
- def parse_query(qs)
- deep_munge(super)
- end
+ def parse_query(qs)
+ Utils.deep_munge(super)
+ end
private
-
- def check_method(name)
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
- name
- end
+ def check_method(name)
+ HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
+ name
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 60a2cccdc5..7b2655b2d8 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'monitor'
module ActionDispatch # :nodoc:
@@ -31,10 +31,17 @@ module ActionDispatch # :nodoc:
# end
# end
class Response
- attr_accessor :request, :header
+ # The request that the response is responding to.
+ attr_accessor :request
+
+ # The HTTP status code.
attr_reader :status
+
attr_writer :sending_file
+ # Get and set headers for this response.
+ attr_accessor :header
+
alias_method :headers=, :header=
alias_method :headers, :header
@@ -49,9 +56,12 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
- attr_accessor :charset
attr_reader :content_type
+ # The charset of the response. HTML wants to know the encoding of the
+ # content you're giving them, so we need to send that along.
+ attr_accessor :charset
+
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
@@ -93,6 +103,7 @@ module ActionDispatch # :nodoc:
end
end
+ # The underlying body, as a streamable object.
attr_reader :stream
def initialize(status = 200, header = {}, body = [])
@@ -142,6 +153,7 @@ module ActionDispatch # :nodoc:
@status = Rack::Utils.status_code(status)
end
+ # Sets the HTTP content type.
def content_type=(content_type)
@content_type = content_type.to_s
end
@@ -169,9 +181,9 @@ module ActionDispatch # :nodoc:
end
alias_method :status_message, :message
- def respond_to?(method)
+ def respond_to?(method, include_private = false)
if method.to_s == 'to_path'
- stream.respond_to?(:to_path)
+ stream.respond_to?(method)
else
super
end
@@ -198,7 +210,9 @@ module ActionDispatch # :nodoc:
if body.respond_to?(:to_path)
@stream = body
else
- @stream = build_buffer self, munge_body_object(body)
+ synchronize do
+ @stream = build_buffer self, munge_body_object(body)
+ end
end
end
@@ -216,11 +230,13 @@ module ActionDispatch # :nodoc:
::Rack::Utils.delete_cookie_header!(header, key, value)
end
+ # The location header we'll be responding with.
def location
headers[LOCATION]
end
alias_method :redirect_url, :location
+ # Sets the location header we'll be responding with.
def location=(url)
headers[LOCATION] = url
end
@@ -229,11 +245,13 @@ module ActionDispatch # :nodoc:
stream.close if stream.respond_to?(:close)
end
+ # Turns the Response into a Rack-compatible array of the status, headers,
+ # and body.
def to_a
rack_response @status, @header.to_hash
end
alias prepare! to_a
- alias to_ary to_a # For implicit splat on 1.9.2
+ alias to_ary to_a
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index b57c84dec8..a8d2dc3950 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -73,18 +73,5 @@ module ActionDispatch
filename.force_encoding(Encoding::UTF_8).encode! if filename
end
end
-
- module Upload # :nodoc:
- # Replace file upload hash with UploadedFile objects
- # when normalize and encode parameters.
- def normalize_encode_params(value)
- if Hash === value && value.has_key?(:tempfile)
- UploadedFile.new(value)
- else
- super
- end
- end
- private :normalize_encode_params
- end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index e288f026c7..4410c1b5d5 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -18,7 +18,11 @@ module ActionDispatch
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
- next if !name && route.requirements.empty? && route.parts.empty?
+
+ # Skip this route unless a name has been provided or it is a
+ # standard Rails route since we can't determine whether an options
+ # hash passed to url_for matches a Rack application or a redirect.
+ next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts)
next unless missing_keys.empty?
@@ -29,8 +33,8 @@ module ActionDispatch
return [route.format(parameterized_parts), params]
end
- message = "No route matches #{constraints.inspect}"
- message << " missing required keys: #{missing_keys.inspect}" if name
+ message = "No route matches #{Hash[constraints.sort].inspect}"
+ message << " missing required keys: #{missing_keys.sort.inspect}" if name
raise ActionController::UrlGenerationError, message
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index da0cddd93c..5a79059ed6 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -9,8 +9,8 @@ module ActionDispatch
attr_reader :memos
def initialize
- @regexp_states = Hash.new { |h,k| h[k] = {} }
- @string_states = Hash.new { |h,k| h[k] = {} }
+ @regexp_states = {}
+ @string_states = {}
@accepting = {}
@memos = Hash.new { |h,k| h[k] = [] }
end
@@ -43,9 +43,7 @@ module ActionDispatch
move_string(t, a).concat(move_regexp(t, a))
end
- def to_json
- require 'json'
-
+ def as_json(options = nil)
simple_regexp = Hash.new { |h,k| h[k] = {} }
@regexp_states.each do |from, hash|
@@ -54,11 +52,11 @@ module ActionDispatch
end
end
- JSON.dump({
+ {
regexp_states: simple_regexp,
string_states: @string_states,
accepting: @accepting
- })
+ }
end
def to_svg
@@ -111,14 +109,8 @@ module ActionDispatch
end
def []=(from, to, sym)
- case sym
- when String
- @string_states[from][sym] = to
- when Regexp
- @regexp_states[from][sym] = to
- else
- raise ArgumentError, 'unknown symbol: %s' % sym.class
- end
+ to_mappings = states_hash_for(sym)[from] ||= {}
+ to_mappings[sym] = to
end
def states
@@ -137,18 +129,35 @@ module ActionDispatch
private
+ def states_hash_for(sym)
+ case sym
+ when String
+ @string_states
+ when Regexp
+ @regexp_states
+ else
+ raise ArgumentError, 'unknown symbol: %s' % sym.class
+ end
+ end
+
def move_regexp(t, a)
return [] if t.empty?
t.map { |s|
- @regexp_states[s].map { |re, v| re === a ? v : nil }
+ if states = @regexp_states[s]
+ states.map { |re, v| re === a ? v : nil }
+ end
}.flatten.compact.uniq
end
def move_string(t, a)
return [] if t.empty?
- t.map { |s| @string_states[s][a] }.compact
+ t.map do |s|
+ if states = @string_states[s]
+ states[a]
+ end
+ end.compact
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index bb4cbb00e2..430812fafe 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -1,7 +1,7 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by Racc 1.4.9
-# from Racc grammer file "".
+# from Racc grammar file "".
#
require 'racc/parser.rb'
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 50e1853094..c8eb0f6f2d 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -16,6 +16,14 @@ module ActionDispatch
@app = app
@path = path
+ # Unwrap any constraints so we can see what's inside for route generation.
+ # This allows the formatter to skip over any mounted applications or redirects
+ # that shouldn't be matched when using a url_for without a route name.
+ while app.is_a?(Routing::Mapper::Constraints) do
+ app = app.app
+ end
+ @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher)
+
@constraints = constraints
@defaults = defaults
@required_defaults = nil
@@ -93,6 +101,10 @@ module ActionDispatch
end
end
+ def dispatcher?
+ @dispatcher
+ end
+
def matches?(request)
constraints.all? do |method, value|
next true unless request.respond_to?(method)
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 419e665d12..da32f1bfe7 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -54,7 +54,7 @@ module ActionDispatch
end
def call(env)
- env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
+ env['PATH_INFO'] = normalize_path(env['PATH_INFO'])
find_routes(env).each do |match, parameters, route|
script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
@@ -103,6 +103,12 @@ module ActionDispatch
private
+ def normalize_path(path)
+ path = "/#{path}"
+ path.squeeze!('/')
+ path
+ end
+
def partitioned_routes
routes.partitioned_routes
end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index 462f1a122d..d1a004af50 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -7,15 +7,18 @@ module ActionDispatch
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
+ # Also converts downcase url encoded string to uppercase.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
+ # normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/')
path.sub!(%r{/+\Z}, '')
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == ''
path
end
@@ -35,7 +38,7 @@ module ActionDispatch
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
end
- Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
+ Parser = URI::Parser.new
def self.escape_path(path)
Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index a99d6d0d6a..80e3818ccd 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -30,6 +30,7 @@ module ActionDispatch
def clear
routes.clear
+ named_routes.clear
end
def partitioned_routes
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 2964d80d9f..daade5bb74 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,10 +1,13 @@
# encoding: utf-8
+
+require 'thread_safe'
+
module ActionDispatch
module Journey # :nodoc:
module Visitors # :nodoc:
class Visitor # :nodoc:
- DISPATCH_CACHE = Hash.new { |h,k|
- h[k] = "visit_#{k}"
+ DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
+ h[k] = :"visit_#{k}"
}
def accept(node)
@@ -74,54 +77,73 @@ module ActionDispatch
end
end
- class OptimizedPath < String # :nodoc:
+ class OptimizedPath < Visitor # :nodoc:
+ def accept(node)
+ Array(visit(node))
+ end
+
private
- def visit_GROUP(node)
- ""
- end
+ def visit_CAT(node)
+ [visit(node.left), visit(node.right)].flatten
+ end
+
+ def visit_SYMBOL(node)
+ node.left[1..-1].to_sym
+ end
+
+ def visit_STAR(node)
+ visit(node.left)
+ end
+
+ def visit_GROUP(node)
+ []
+ end
+
+ %w{ LITERAL SLASH DOT }.each do |t|
+ class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
+ end
end
# Used for formatting urls (url_for)
class Formatter < Visitor # :nodoc:
- attr_reader :options, :consumed
+ attr_reader :options
def initialize(options)
@options = options
- @consumed = {}
end
private
- def visit_GROUP(node)
- if consumed == options
- nil
- else
- route = visit(node.left)
- route.include?("\0") ? nil : route
+ def visit(node, optional = false)
+ case node.type
+ when :LITERAL, :SLASH, :DOT
+ node.left
+ when :STAR
+ visit(node.left)
+ when :GROUP
+ visit(node.left, true)
+ when :CAT
+ visit_CAT(node, optional)
+ when :SYMBOL
+ visit_SYMBOL(node)
end
end
- def terminal(node)
- node.left
- end
-
- def binary(node)
- [visit(node.left), visit(node.right)].join
- end
+ def visit_CAT(node, optional)
+ left = visit(node.left, optional)
+ right = visit(node.right, optional)
- def nary(node)
- node.children.map { |c| visit(c) }.join
+ if optional && !(right && left)
+ ""
+ else
+ [left, right].join
+ end
end
def visit_SYMBOL(node)
- key = node.to_sym
-
- if value = options[key]
- consumed[key] = value
+ if value = options[node.to_sym]
Router::Utils.escape_path(value)
- else
- "\0"
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 852f1cf6f5..baf9d5779e 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -8,14 +8,14 @@ module ActionDispatch
class << self
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
- end
- def self.before(*args, &block)
- set_callback(:call, :before, *args, &block)
- end
+ def before(*args, &block)
+ set_callback(:call, :before, *args, &block)
+ end
- def self.after(*args, &block)
- set_callback(:call, :after, *args, &block)
+ def after(*args, &block)
+ set_callback(:call, :after, *args, &block)
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 5b914f293d..fe110d7938 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -30,7 +30,7 @@ module ActionDispatch
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
+ # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
# # It can be read using the signed method <tt>cookies.signed[:name]</tt>
# cookies.signed[:user_id] = current_user.id
#
@@ -77,7 +77,7 @@ module ActionDispatch
# domain and subdomains.
#
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
+ # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
@@ -117,10 +117,10 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
#
@@ -140,10 +140,10 @@ module ActionDispatch
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
#
@@ -160,7 +160,7 @@ module ActionDispatch
end
end
- # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set.
+ # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
def signed_or_encrypted
@signed_or_encrypted ||=
@@ -409,7 +409,7 @@ module ActionDispatch
end
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # config.secret_token and config.secret_key_base are both set. It reads
+ # config.secret_token and secrets.secret_key_base are both set. It reads
# legacy cookies signed with the old dummy key generator and re-saves
# them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
@@ -427,7 +427,7 @@ module ActionDispatch
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
@@ -465,7 +465,7 @@ module ActionDispatch
end
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
# are both set. It reads legacy cookies signed with the old dummy key generator and
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 64230ff1ae..0ca1a87645 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -34,27 +34,35 @@ module ActionDispatch
log_error(env, wrapper)
if env['action_dispatch.show_detailed_exceptions']
+ request = Request.new(env)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => Request.new(env),
- :exception => wrapper.exception,
- :application_trace => wrapper.application_trace,
- :framework_trace => wrapper.framework_trace,
- :full_trace => wrapper.full_trace,
- :routes_inspector => routes_inspector(exception),
- :source_extract => wrapper.source_extract,
- :line_number => wrapper.line_number,
- :file => wrapper.file
+ request: request,
+ exception: wrapper.exception,
+ application_trace: wrapper.application_trace,
+ framework_trace: wrapper.framework_trace,
+ full_trace: wrapper.full_trace,
+ routes_inspector: routes_inspector(exception),
+ source_extract: wrapper.source_extract,
+ line_number: wrapper.line_number,
+ file: wrapper.file
)
file = "rescues/#{wrapper.rescue_template}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(wrapper.status_code, body)
+
+ if request.xhr?
+ body = template.render(template: file, layout: false, formats: [:text])
+ format = "text/plain"
+ else
+ body = template.render(template: file, layout: 'rescues/layout')
+ format = "text/html"
+ end
+ render(wrapper.status_code, body, format)
else
raise exception
end
end
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ def render(status, body, format)
+ [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
def log_error(env, wrapper)
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 1de3d14530..377f05c982 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,5 @@
require 'action_controller/metal/exceptions'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionDispatch
class ExceptionWrapper
@@ -96,7 +96,7 @@ module ActionDispatch
def source_fragment(path, line)
return unless Rails.respond_to?(:root) && Rails.root
full_path = Rails.root.join(path)
- if File.exists?(full_path)
+ if File.exist?(full_path)
File.open(full_path, "r") do |file|
start = [line - 3, 0].max
lines = file.each_line.drop(start).take(6)
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 0fa1e9b859..b426183488 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -41,9 +41,9 @@ module ActionDispatch
when Proc
strategy.call(request.raw_post)
when :json
- data = ActiveSupport::JSON.decode(request.body)
+ data = ActiveSupport::JSON.decode(request.raw_post)
data = {:_json => data} unless data.is_a?(Hash)
- request.deep_munge(data).with_indifferent_access
+ Request::Utils.deep_munge(data).with_indifferent_access
else
false
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 8879291dbd..57bc6d5cd0 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -143,7 +143,7 @@ module ActionDispatch
# 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
+ should_check_ip = @check_ip && client_ips.last && forwarded_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?! " +
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index b9eb8036e9..1ebc189c28 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -15,8 +15,8 @@ module ActionDispatch
# best possible option given your application's configuration.
#
# If you only have secret_token set, your cookies will be signed, but
- # not encrypted. This means a user cannot alter his +user_id+ without
- # knowing your app's secret key, but can easily read his +user_id+. This
+ # not encrypted. This means a user cannot alter their +user_id+ without
+ # knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
# If you have secret_key_base set, your cookies will be encrypted. This
@@ -31,9 +31,10 @@ module ActionDispatch
#
# Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # Configure your secret key in config/initializers/secret_token.rb:
+ # Configure your secret key in config/secrets.yml:
#
- # Myapp::Application.config.secret_key_base 'secret key'
+ # development:
+ # secret_key_base: 'secret key'
#
# To generate a secret key for an existing application, run `rake secret`.
#
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index fcc5bc12c4..1d4f0f89a6 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -29,8 +29,11 @@ module ActionDispatch
def call(env)
@app.call(env)
rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
+ if env['action_dispatch.show_exceptions'] == false
+ raise exception
+ else
+ render_exception(env, exception)
+ end
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 9e03cbf2b7..999c022535 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -36,8 +36,7 @@ module ActionDispatch
url.scheme = "https"
url.host = @host if @host
url.port = @port if @port
- headers = hsts_headers.merge('Content-Type' => 'text/html',
- 'Location' => url.to_s)
+ headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s }
[301, headers, []]
end
@@ -58,7 +57,7 @@ module ActionDispatch
cookies = cookies.split("\n")
headers['Set-Cookie'] = cookies.map { |cookie|
- if cookie !~ /;\s+secure(;|$)/
+ if cookie !~ /;\s*secure\s*(;|$)/i
"#{cookie}; secure"
else
cookie
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c6a7d9c415..2764584fe9 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -11,9 +11,10 @@ module ActionDispatch
end
def match?(path)
- path = path.dup
+ path = unescape_path(path)
+ return false unless path.valid_encoding?
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
@@ -40,7 +41,6 @@ module ActionDispatch
end
def escape_glob_chars(path)
- path.force_encoding('binary') if path.respond_to? :force_encoding
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
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.html.erb
index db219c8fa9..db219c8fa9 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
new file mode 100644
index 0000000000..396768ecee
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
@@ -0,0 +1,23 @@
+<%
+ clean_params = @request.filtered_parameters.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end unless self.class.method_defined?(:debug_hash)
+%>
+
+Request parameters
+<%= request_dump %>
+
+Session dump
+<%= debug_hash @request.session %>
+
+Env dump
+<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %>
+
+Response headers
+<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index b181909bff..b181909bff 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
new file mode 100644
index 0000000000..d4af5c9b06
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
@@ -0,0 +1,15 @@
+<%
+ traces = { "Application Trace" => @application_trace,
+ "Framework Trace" => @framework_trace,
+ "Full Trace" => @full_trace }
+%>
+
+Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
+
+<% traces.each do |name, trace| %>
+<% if trace.any? %>
+<%= name %>
+<%= trace.join("\n") %>
+
+<% end %>
+<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 57a2940802..f154021ae6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -8,7 +8,7 @@
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
<%= render template: "rescues/_source" %>
<%= render template: "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
index ca14215946..5c016e544e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -3,5 +3,5 @@
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb
new file mode 100644
index 0000000000..ae62d9eb02
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb
@@ -0,0 +1,3 @@
+Template is missing
+
+<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
index cd3daff065..7e9cedb95e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -2,7 +2,7 @@
<h1>Routing Error</h1>
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
<% unless @exception.failures.empty? %>
<p>
<h2>Failure reasons:</h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb
new file mode 100644
index 0000000000..f6e4dac1f3
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb
@@ -0,0 +1,11 @@
+Routing Error
+
+<%= @exception.message %>
+<% unless @exception.failures.empty? %>
+Failure reasons:
+<% @exception.failures.each do |route, reason| %>
+ - <%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %>
+<% end %>
+<% end %>
+
+<%= render template: "rescues/_trace", format: :text %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index 31f46ee340..027a0f5b3e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -10,7 +10,7 @@
<p>
Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised:
</p>
- <pre><code><%= @exception.message %></code></pre>
+ <pre><code><%= h @exception.message %></code></pre>
<div class="source">
<div class="info">
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
new file mode 100644
index 0000000000..5da21d9784
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
@@ -0,0 +1,8 @@
+<% @source_extract = @exception.source_extract(0, :html) %>
+<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
+
+Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
+<%= @exception.message %>
+<%= @exception.sub_template_message %>
+<%= render template: "rescues/_trace", format: :text %>
+<%= render template: "rescues/_request_and_response", format: :text %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb
index c1fbf67eed..259fb2bb3b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb
@@ -2,5 +2,5 @@
<h1>Unknown action</h1>
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb
new file mode 100644
index 0000000000..83973addcb
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb
@@ -0,0 +1,3 @@
+Unknown action
+
+<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 95461fa693..323873ba4b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -89,8 +89,8 @@
}
// takes an array of elements with a data-regexp attribute and
- // passes their their parent <tr> into the callback function
- // if the regexp matchs a given path
+ // passes their parent <tr> into the callback function
+ // if the regexp matches a given path
function eachElemsForPath(elems, path, func) {
each(elems, function(e){
var reg = e.getAttribute("data-regexp");
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index edf37bb9a5..ddeea24bb3 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -16,12 +16,12 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = 'signed cookie'
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
+ config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
- 'X-Content-Type-Options' => 'nosniff',
- 'X-UA-Compatible' => 'chrome=1'
+ 'X-Content-Type-Options' => 'nosniff'
}
config.eager_load_namespaces << ActionDispatch
@@ -29,6 +29,7 @@ module ActionDispatch
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 7bc812fd22..973627f106 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -7,6 +7,9 @@ module ActionDispatch
ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+ # Singleton object used to determine if an optional param wasn't specified
+ Unspecified = Object.new
+
def self.create(store, env, default_options)
session_was = find env
session = Request::Session.new(store, env)
@@ -127,6 +130,15 @@ module ActionDispatch
@delegate.delete key.to_s
end
+ def fetch(key, default=Unspecified, &block)
+ load_for_read!
+ if default == Unspecified
+ @delegate.fetch(key.to_s, &block)
+ else
+ @delegate.fetch(key.to_s, default, &block)
+ end
+ end
+
def inspect
if loaded?
super
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
new file mode 100644
index 0000000000..a6dca9741c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -0,0 +1,30 @@
+module ActionDispatch
+ class Request < Rack::Request
+ class Utils # :nodoc:
+
+ mattr_accessor :perform_deep_munge
+ self.perform_deep_munge = true
+
+ class << self
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ return hash unless perform_deep_munge
+
+ hash.each do |k, v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ hash[k] = nil if v.empty?
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+ end
+ end
+ end
+end
+
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 550c7d0e7b..a9ac2bce1d 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -246,11 +246,13 @@ module ActionDispatch
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
#
module Routing
- autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :RouteSet, 'action_dispatch/routing/route_set'
- autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
- autoload :UrlFor, 'action_dispatch/routing/url_for'
- autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
+ extend ActiveSupport::Autoload
+
+ autoload :Mapper
+ autoload :RouteSet
+ autoload :RoutesProxy
+ autoload :UrlFor
+ autoload :PolymorphicRoutes
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index d251de33df..f612e91aef 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -69,7 +69,7 @@ module ActionDispatch
end
def internal?
- controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
end
def engine?
@@ -179,7 +179,8 @@ module ActionDispatch
private
def draw_section(routes)
- name_width, verb_width, path_width = widths(routes)
+ header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
+ name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
routes.map do |r|
"#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index c3fd0c18ec..4bf2dc6e23 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/module/remove_method'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -11,8 +12,8 @@ module ActionDispatch
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
- :controller, :path_names, :constraints, :defaults,
- :shallow, :blocks, :options]
+ :controller, :action, :path_names, :constraints,
+ :shallow, :blocks, :defaults, :options]
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
@@ -147,14 +148,16 @@ module ActionDispatch
@defaults.merge!(options[:defaults]) if options[:defaults]
options.each do |key, default|
- next if Regexp === default || IGNORE_OPTIONS.include?(key)
- @defaults[key] = default
+ unless Regexp === default || IGNORE_OPTIONS.include?(key)
+ @defaults[key] = default
+ end
end
if options[:constraints].is_a?(Hash)
options[:constraints].each do |key, default|
- next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
+ if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
end
end
@@ -166,19 +169,21 @@ module ActionDispatch
end
def normalize_conditions!
- @conditions.merge!(:path_info => path)
+ @conditions[:path_info] = path
constraints.each do |key, condition|
- next if segment_keys.include?(key) || key == :controller
- @conditions[key] = condition
+ unless segment_keys.include?(key) || key == :controller
+ @conditions[key] = condition
+ end
end
- @conditions[:required_defaults] = []
+ required_defaults = []
options.each do |key, required_default|
- next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
- next if Regexp === required_default
- @conditions[:required_defaults] << key
+ unless segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default
+ required_defaults << key
+ end
end
+ @conditions[:required_defaults] = required_defaults
via_all = options.delete(:via) if options[:via] == :all
@@ -192,8 +197,7 @@ module ActionDispatch
end
if via = options[:via]
- list = Array(via).map { |m| m.to_s.dasherize.upcase }
- @conditions.merge!(:request_method => list)
+ @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
end
end
@@ -226,11 +230,13 @@ module ActionDispatch
action = action.to_s unless action.is_a?(Regexp)
if controller.blank? && segment_keys.exclude?(:controller)
- raise ArgumentError, "missing :controller"
+ message = "Missing :controller key on routes definition, please check your routes."
+ raise ArgumentError, message
end
if action.blank? && segment_keys.exclude?(:action)
- raise ArgumentError, "missing :action"
+ message = "Missing :action key on routes definition, please check your routes."
+ raise ArgumentError, message
end
if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
@@ -362,8 +368,9 @@ module ActionDispatch
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show)
#
- # Because request various HTTP verbs with a single action has security
- # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
+ # Because requesting various HTTP verbs with a single action has security
+ # implications, you must either specify the actions in
+ # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
# instead +match+
#
# === Options
@@ -383,7 +390,7 @@ module ActionDispatch
# The namespace for :controller.
#
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
- # #=> Sekret::PostsController
+ # # => Sekret::PostsController
#
# See <tt>Scoping#namespace</tt> for its scope equivalent.
#
@@ -432,10 +439,10 @@ module ActionDispatch
#
# match 'json_only', constraints: { format: 'json' }
#
- # class Blacklist
+ # class Whitelist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path', to: 'c#a', constraints: Blacklist.new
+ # match 'path', to: 'c#a', constraints: Whitelist.new
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
@@ -496,11 +503,12 @@ module ActionDispatch
raise "A rack application must be specified" unless path
options[:as] ||= app_name(app)
+ target_as = name_for_action(options[:as], path)
options[:via] ||= :all
match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, options[:as])
+ define_generate_prefix(app, target_as)
self
end
@@ -515,6 +523,11 @@ module ActionDispatch
end
end
+ # Query if the following named route was already defined.
+ def has_named_route?(name)
+ @set.named_routes.routes[name.to_sym]
+ end
+
private
def app_name(app)
return unless app.respond_to?(:routes)
@@ -534,11 +547,11 @@ module ActionDispatch
_routes = @set
app.routes.define_mounted_helper(name)
app.routes.singleton_class.class_eval do
- define_method :mounted? do
+ redefine_method :mounted? do
true
end
- define_method :_generate_prefix do |options|
+ redefine_method :_generate_prefix do |options|
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
@@ -869,6 +882,10 @@ module ActionDispatch
child
end
+ def merge_action_scope(parent, child) #:nodoc:
+ child
+ end
+
def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
@@ -1057,18 +1074,18 @@ module ActionDispatch
# a singular resource to map /profile (rather than /profile/:id) to
# the show action:
#
- # resource :geocoder
+ # resource :profile
#
# creates six different routes in your application, all mapping to
- # the +GeoCoders+ controller (note that the controller is named after
+ # the +Profiles+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PATCH/PUT /geocoder
- # DELETE /geocoder
+ # GET /profile/new
+ # POST /profile
+ # GET /profile
+ # GET /profile/edit
+ # PATCH/PUT /profile
+ # DELETE /profile
#
# === Options
# Takes same options as +resources+.
@@ -1378,6 +1395,10 @@ module ActionDispatch
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
+ if @scope[:controller] && @scope[:action]
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
+ end
+
paths.each do |_path|
route_options = options.dup
route_options[:path] ||= _path if _path.is_a?(String)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 6d3f8da932..2fb03f2712 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -74,6 +74,19 @@ module ActionDispatch
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
+ # Also includes all the options from <tt>url_for</tt>. These include such
+ # things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage
+ # is given below:
+ #
+ # polymorphic_url([blog, post], anchor: 'my_anchor')
+ # # => "http://example.com/blogs/1/posts/1#my_anchor"
+ # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app")
+ # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor"
+ #
+ # For all of these options, see the documentation for <tt>url_for</tt>.
+ #
+ # ==== Functionality
+ #
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index d751e04e6a..b08e62543b 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -17,7 +17,7 @@ module ActionDispatch
def call(env)
req = Request.new(env)
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
req.symbolized_path_parameters.each do |key, value|
unless value.valid_encoding?
@@ -26,6 +26,15 @@ module ActionDispatch
end
uri = URI.parse(path(req.symbolized_path_parameters, req))
+
+ unless uri.host
+ if relative_path?(uri.path)
+ uri.path = "#{req.script_name}/#{uri.path}"
+ elsif uri.path.empty?
+ uri.path = req.script_name.empty? ? "/" : req.script_name
+ end
+ end
+
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
@@ -48,11 +57,38 @@ module ActionDispatch
def inspect
"redirect(#{status})"
end
+
+ private
+ def relative_path?(path)
+ path && !path.empty? && path[0] != '/'
+ end
+
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
+
+ def escape_fragment(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }]
+ end
+
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }]
+ end
end
class PathRedirect < Redirect
+ URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/
+
def path(params, request)
- (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ if block.match(URL_PARTS)
+ path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1
+ query = interpolation_required?($2, params) ? $2 % escape(params) : $2
+ fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3
+
+ "#{path}#{query}#{fragment}"
+ else
+ interpolation_required?(block, params) ? block % escape(params) : block
+ end
end
def inspect
@@ -60,8 +96,8 @@ module ActionDispatch
end
private
- def escape(params)
- Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ def interpolation_required?(string, params)
+ !params.empty? && string && string.match(/%\{\w*\}/)
end
end
@@ -81,17 +117,22 @@ module ActionDispatch
url_options[:path] = (url_options[:path] % escape_path(params))
end
+ unless options[:host] || options[:domain]
+ if relative_path?(url_options[:path])
+ url_options[:path] = "/#{url_options[:path]}"
+ url_options[:script_name] = request.script_name
+ elsif url_options[:path].empty?
+ url_options[:path] = request.script_name.empty? ? "/" : ""
+ url_options[:script_name] = request.script_name
+ end
+ end
+
ActionDispatch::Http::URL.url_for url_options
end
def inspect
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
end
-
- private
- def escape_path(params)
- Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
- end
end
module Redirection
@@ -104,6 +145,10 @@ module ActionDispatch
#
# get 'docs/:article', to: redirect('/wiki/%{article}')
#
+ # Note that if you return a path without a leading slash then the url is prefixed with the
+ # current SCRIPT_NAME environment variable. This is typically '/' but may be different in
+ # a mounted engine or where the application is deployed to a subdirectory of a website.
+ #
# Alternatively you can use one of the other syntaxes:
#
# The block version of redirect allows for the easy encapsulation of any logic associated with
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 342b6ec23d..a03fb4cee7 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -28,7 +28,7 @@ module ActionDispatch
def call(env)
params = env[PARAMETERS_KEY]
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
params.each do |key, value|
next unless value.respond_to?(:valid_encoding?)
@@ -163,9 +163,10 @@ module ActionDispatch
def initialize(route, options)
super
- @path_parts = @route.required_parts
- @arg_size = @path_parts.size
- @string_route = @route.optimized_path
+ @klass = Journey::Router::Utils
+ @required_parts = @route.required_parts
+ @arg_size = @required_parts.size
+ @optimized_path = @route.optimized_path
end
def call(t, args)
@@ -182,26 +183,44 @@ module ActionDispatch
private
def optimized_helper(args)
- path = @string_route.dup
- klass = Journey::Router::Utils
-
- @path_parts.zip(args) do |part, arg|
- # Replace each route parameter
- # e.g. :id for regular parameter or *path for globbing
- # with ruby string interpolation code
- path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(arg.to_param))
+ params = Hash[parameterize_args(args)]
+ missing_keys = missing_keys(params)
+
+ unless missing_keys.empty?
+ raise_generation_error(params, missing_keys)
end
- path
+
+ @optimized_path.map{ |segment| replace_segment(params, segment) }.join
+ end
+
+ def replace_segment(params, segment)
+ Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
end
def optimize_routes_generation?(t)
t.send(:optimize_routes_generation?)
end
+
+ def parameterize_args(args)
+ @required_parts.zip(args.map(&:to_param))
+ end
+
+ def missing_keys(args)
+ args.select{ |part, arg| arg.nil? || arg.empty? }.keys
+ end
+
+ def raise_generation_error(args, missing_keys)
+ constraints = Hash[@route.requirements.merge(args).sort]
+ message = "No route matches #{constraints.inspect}"
+ message << " missing required keys: #{missing_keys.sort.inspect}"
+
+ raise ActionController::UrlGenerationError, message
+ end
end
def initialize(route, options)
@options = options
- @segment_keys = route.segment_keys
+ @segment_keys = route.segment_keys.uniq
@route = route
end
@@ -218,6 +237,7 @@ module ActionDispatch
keys -= t.url_options.keys if t.respond_to?(:url_options)
keys -= options.keys
end
+ keys -= inner_options.keys
result.merge!(Hash[keys.zip(args)])
end
@@ -335,7 +355,7 @@ module ActionDispatch
include UrlFor
end
- # Contains all the mounted helpers accross different
+ # Contains all the mounted helpers across different
# engines and the `main_app` helper for the application.
# You can include this in your classes if you want to
# access routes for other engines.
@@ -488,11 +508,12 @@ module ActionDispatch
@recall = recall.dup
@set = set
+ normalize_recall!
normalize_options!
normalize_controller_action_id!
use_relative_controller!
normalize_controller!
- handle_nil_action!
+ normalize_action!
end
def controller
@@ -511,6 +532,11 @@ module ActionDispatch
end
end
+ # Set 'index' as default action for recall
+ def normalize_recall!
+ @recall[:action] ||= 'index'
+ end
+
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -526,8 +552,8 @@ module ActionDispatch
options[:controller] = options[:controller].to_s
end
- if options[:action]
- options[:action] = options[:action].to_s
+ if options.key?(:action)
+ options[:action] = (options[:action] || 'index').to_s
end
end
@@ -537,8 +563,6 @@ module ActionDispatch
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
- @recall[:action] ||= 'index' if current_controller
-
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
@@ -560,13 +584,11 @@ module ActionDispatch
@options[:controller] = controller.sub(%r{^/}, '') if controller
end
- # This handles the case of action: nil being explicitly passed.
- # It is identical to action: "index"
- def handle_nil_action!
- if options.has_key?(:action) && options[:action].nil?
- options[:action] = 'index'
+ # Move 'index' action from options to recall
+ def normalize_action!
+ if @options[:action] == 'index'
+ @recall[:action] = @options.delete(:action)
end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
end
# Generates a path from routes, returns [path, params].
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8e19025722..4a0ef40873 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -20,7 +20,7 @@ module ActionDispatch
#
# <%= link_to('Click here', controller: 'users',
# action: 'new', message: 'Welcome!') %>
- # # => "/users/new?message=Welcome%21"
+ # # => <a href="/users/new?message=Welcome%21">Click here</a>
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
@@ -155,6 +155,8 @@ module ActionDispatch
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
+ when Array
+ polymorphic_url(options, options.extract_options!)
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index 8f90a1223e..241a39393a 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -7,20 +7,20 @@ module ActionDispatch
#
# # assert that the referenced method generates the appropriate HTML string
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
- def assert_dom_equal(expected, actual, message = "")
+ def assert_dom_equal(expected, actual, message = nil)
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- assert_equal expected_dom, actual_dom
+ assert_equal expected_dom, actual_dom, message
end
# The negated form of +assert_dom_equivalent+.
#
# # assert that the referenced method does not generate the specified HTML string
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
- def assert_dom_not_equal(expected, actual, message = "")
+ def assert_dom_not_equal(expected, actual, message = nil)
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- assert_not_equal expected_dom, actual_dom
+ assert_not_equal expected_dom, actual_dom, message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 44ed0ac1f3..68feb26936 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -27,6 +27,9 @@ module ActionDispatch
assert @response.send("#{type}?"), message
else
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
+ if code.nil?
+ raise ArgumentError, "Invalid response type :#{type}"
+ end
assert_equal code, @response.response_code, message
end
else
@@ -67,21 +70,11 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- normalized = case fragment
- when Regexp
- fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end
-
- normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
+ if Regexp === fragment
+ fragment
+ else
+ @controller._compute_redirect_to_location(fragment)
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 496682e8bd..f1f998d932 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -211,7 +211,7 @@ module ActionDispatch
def fail_on(exception_class)
yield
rescue exception_class => e
- raise MiniTest::Assertion, e.message
+ raise Minitest::Assertion, e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 56c31255f3..cc6b763093 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,7 +3,7 @@ require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
require 'rack/test'
-require 'minitest/unit'
+require 'minitest'
module ActionDispatch
module Integration #:nodoc:
@@ -62,12 +62,6 @@ module ActionDispatch
process :head, path, parameters, headers_or_env
end
- # Performs a OPTIONS request with the given parameters. See +#get+ for
- # more details.
- def options(path, parameters = nil, headers_or_env = nil)
- process :options, path, parameters, headers_or_env
- end
-
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
@@ -143,7 +137,7 @@ module ActionDispatch
class Session
DEFAULT_HOST = "www.example.com"
- include MiniTest::Assertions
+ include Minitest::Assertions
include TestProcess, RequestHelpers, Assertions
%w( status status_message headers body redirect? ).each do |method|
@@ -248,7 +242,7 @@ module ActionDispatch
@https = flag
end
- # Return +true+ if the session is mimicking a secure HTTPS request.
+ # Returns +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...
@@ -304,8 +298,6 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
- env.merge!(env)
-
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
uri = URI.parse('/')
@@ -342,7 +334,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post patch put head delete options cookies assigns
+ %w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
@@ -495,10 +487,6 @@ module ActionDispatch
@@app = nil
def self.app
- if !@@app && !ActionDispatch.test_app
- ActiveSupport::Deprecation.warn "Rails application fallback is deprecated and no longer works, please set ActionDispatch.test_app"
- end
-
@@app || ActionDispatch.test_app
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index c63778f870..57c678843b 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -3,7 +3,11 @@ require 'rack/utils'
module ActionDispatch
class TestRequest < Request
- DEFAULT_ENV = Rack::MockRequest.env_for('/')
+ DEFAULT_ENV = Rack::MockRequest.env_for('/',
+ 'HTTP_HOST' => 'test.host',
+ 'REMOTE_ADDR' => '0.0.0.0',
+ 'HTTP_USER_AGENT' => 'Rails Testing'
+ )
def self.new(env = {})
super
@@ -12,10 +16,6 @@ module ActionDispatch
def initialize(env = {})
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
super(default_env.merge(env))
-
- self.host = 'test.host'
- self.remote_addr = '0.0.0.0'
- self.user_agent = 'Rails Testing'
end
def request_method=(method)