diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
41 files changed, 493 insertions, 278 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 1d0a6b6eb3..30ade14c26 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -59,7 +59,7 @@ module ActionDispatch end def last_modified? - have_header? LAST_MODIFIED + has_header? LAST_MODIFIED end def last_modified=(utc_time) @@ -73,7 +73,7 @@ module ActionDispatch end def date? - have_header? DATE + has_header? DATE end def date=(utc_time) @@ -82,24 +82,19 @@ module ActionDispatch def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - set_header ETAG, %("#{Digest::MD5.hexdigest(key)}") + super %("#{Digest::MD5.hexdigest(key)}") end - def etag - get_header ETAG - end - alias :etag? :etag + def etag?; etag; end private DATE = 'Date'.freeze LAST_MODIFIED = "Last-Modified".freeze - ETAG = "ETag".freeze - CACHE_CONTROL = "Cache-Control".freeze SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate]) def cache_control_segments - if cache_control = get_header(CACHE_CONTROL) + if cache_control = _cache_control cache_control.delete(' ').split(',') else [] @@ -153,11 +148,11 @@ module ActionDispatch control.merge! cache_control if control.empty? - set_header CACHE_CONTROL, DEFAULT_CACHE_CONTROL + self._cache_control = DEFAULT_CACHE_CONTROL elsif control[:no_cache] - set_header CACHE_CONTROL, NO_CACHE + self._cache_control = NO_CACHE if control[:extras] - set_header(CACHE_CONTROL, get_header(CACHE_CONTROL) + ", #{control[:extras].join(', ')}") + self._cache_control = _cache_control + ", #{control[:extras].join(', ')}" end else extras = control[:extras] @@ -169,7 +164,7 @@ module ActionDispatch options << MUST_REVALIDATE if control[:must_revalidate] options.concat(extras) if extras - set_header CACHE_CONTROL, options.join(", ") + self._cache_control = options.join(", ") end end end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 9c0f39f2e7..9dcab79c3a 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -30,12 +30,12 @@ module ActionDispatch @filtered_path = nil end - # Return a hash of parameters with all sensitive data replaced. + # Returns a hash of parameters with all sensitive data replaced. def filtered_parameters @filtered_parameters ||= parameter_filter.filter(parameters) end - # Return a hash of request.env with all sensitive data replaced. + # Returns a hash of request.env with all sensitive data replaced. def filtered_env @filtered_env ||= env_filter.filter(@env) end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 9a3aaca3f0..12f81dc1a5 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -49,6 +49,11 @@ module ActionDispatch @req.set_header env_name(key), value end + # Add a value to a multivalued header like Vary or Accept-Encoding. + def add(key, value) + @req.add_header env_name(key), value + end + def key?(key) @req.has_header? env_name(key) end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index cab60a508a..7acf91902d 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -10,7 +10,7 @@ module ActionDispatch self.ignore_accept_header = false end - # The MIME type of the HTTP request, such as Mime::XML. + # The MIME type of the HTTP request, such as Mime[:xml]. # # For backward compatibility, the post \format is extracted from the # X-Post-Data-Format HTTP header if present. @@ -29,6 +29,10 @@ module ActionDispatch content_mime_type && content_mime_type.to_s end + def has_content_type? + has_header? 'CONTENT_TYPE' + end + # Returns the accepted MIME type for the request. def accepts fetch_header("action_dispatch.request.accepts") do |k| @@ -45,9 +49,9 @@ module ActionDispatch # Returns the MIME type for the \format used in the request. # - # GET /posts/5.xml | request.format => Mime::XML - # GET /posts/5.xhtml | request.format => Mime::HTML - # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first + # GET /posts/5.xml | request.format => Mime[:xml] + # GET /posts/5.xhtml | request.format => Mime[:html] + # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first # def format(view_path = []) formats.first || Mime::NullType.instance @@ -66,9 +70,9 @@ module ActionDispatch elsif use_accept_header && valid_accept_header accepts elsif xhr? - [Mime::JS] + [Mime[:js]] else - [Mime::HTML] + [Mime[:html]] end set_header k, v end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index a639f8a8f8..b64f660ec5 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,23 +1,31 @@ -require 'set' require 'singleton' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' module Mime - class Mimes < Array - def symbols - @symbols ||= map(&:to_sym) + class Mimes + include Enumerable + + def initialize + @mimes = [] + @symbols = nil end - %w(<< concat shift unshift push pop []= clear compact! collect! - delete delete_at delete_if flatten! map! insert reject! reverse! - replace slice! sort! uniq!).each do |method| - module_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{method}(*) - @symbols = nil - super - end - CODE + def each + @mimes.each { |x| yield x } + end + + def <<(type) + @mimes << type + @symbols = nil + end + + def delete_if + @mimes.delete_if { |x| yield x }.tap { @symbols = nil } + end + + def symbols + @symbols ||= map(&:to_sym) end end @@ -35,6 +43,42 @@ module Mime return type if type.is_a?(Type) EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } end + + def const_missing(sym) + ext = sym.downcase + if Mime[ext] + ActiveSupport::Deprecation.warn <<-eow +Accessing mime types via constants is deprecated. Please change: + + `Mime::#{sym}` + +to: + + `Mime[:#{ext}]` + eow + Mime[ext] + else + super + end + end + + def const_defined?(sym, inherit = true) + ext = sym.downcase + if Mime[ext] + ActiveSupport::Deprecation.warn <<-eow +Accessing mime types via constants is deprecated. Please change: + + `Mime.const_defined?(#{sym})` + +to: + + `Mime[:#{ext}]` + eow + true + else + super + end + end end # Encapsulates the notion of a mime type. Can be used at render time, for example, with: @@ -51,9 +95,6 @@ module Mime # end # end class Type - @@html_types = Set.new [:html, :all] - cattr_reader :html_types - attr_reader :symbol @register_callbacks = [] @@ -66,7 +107,7 @@ module Mime def initialize(index, name, q = nil) @index = index @name = name - q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list + q ||= 0.0 if @name == '*/*'.freeze # default wildcard match to end of list @q = ((q || 1.0).to_f * 100).to_i end @@ -91,7 +132,7 @@ module Mime exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx - text_xml.name = Mime::XML.to_s + text_xml.name = Mime[:xml].to_s end # Look for more specific XML-based types and sort them ahead of app/xml @@ -120,7 +161,7 @@ module Mime end def app_xml_idx - @app_xml_idx ||= index(Mime::XML.to_s) + @app_xml_idx ||= index(Mime[:xml].to_s) end def text_xml @@ -160,17 +201,17 @@ module Mime end def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) - Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms)) + new_mime = Type.new(string, symbol, mime_type_synonyms) - new_mime = Mime.const_get(symbol.upcase) SET << new_mime - ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup - ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last } + ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup + ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime } @register_callbacks.each do |callback| callback.call(new_mime) end + new_mime end def parse(accept_header) @@ -200,13 +241,13 @@ module Mime parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP end - # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS, - # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>. + # For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics], + # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>. # - # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS, - # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>. - def parse_data_with_trailing_star(input) - Mime::SET.select { |m| m =~ input } + # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js], + # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>. + def parse_data_with_trailing_star(type) + Mime::SET.select { |m| m =~ type } end # This method is opposite of register method. @@ -215,13 +256,12 @@ module Mime # # Mime::Type.unregister(:mobile) def unregister(symbol) - symbol = symbol.upcase - mime = Mime.const_get(symbol) - Mime.instance_eval { remove_const(symbol) } - - SET.delete_if { |v| v.eql?(mime) } - LOOKUP.delete_if { |_,v| v.eql?(mime) } - EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) } + symbol = symbol.downcase + if mime = Mime[symbol] + SET.delete_if { |v| v.eql?(mime) } + LOOKUP.delete_if { |_, v| v.eql?(mime) } + EXTENSION_LOOKUP.delete_if { |_, v| v.eql?(mime) } + end end end @@ -243,7 +283,7 @@ module Mime end def ref - to_sym || to_s + symbol || to_s end def ===(list) @@ -255,24 +295,23 @@ module Mime end def ==(mime_type) - return false if mime_type.blank? + return false unless mime_type (@synonyms + [ self ]).any? do |synonym| synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym end end def =~(mime_type) - return false if mime_type.blank? + return false unless mime_type regexp = Regexp.new(Regexp.quote(mime_type.to_s)) - (@synonyms + [ self ]).any? do |synonym| - synonym.to_s =~ regexp - end + @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp end def html? - @@html_types.include?(to_sym) || @string =~ /html/ + symbol == :html || @string =~ /html/ end + def all?; false; end private @@ -292,6 +331,22 @@ module Mime end end + class AllType < Type + include Singleton + + def initialize + super '*/*', :all + end + + def all?; true; end + def html?; true; end + end + + # ALL isn't a real MIME type, so we don't register it for lookup with the + # other concrete types. It's a wildcard match that we use for `respond_to` + # negotiation internals. + ALL = AllType.instance + class NullType include Singleton diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 01a10c693b..87715205d9 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -31,6 +31,3 @@ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonr Mime::Type.register "application/pdf", :pdf, [], %w(pdf) Mime::Type.register "application/zip", :zip, [], %w(zip) - -# Create Mime::ALL but do not add it to the SET. -Mime::ALL = Mime::Type.new("*/*", :all, []) diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 3c9f8cd9e4..c9df787351 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -3,6 +3,20 @@ module ActionDispatch module Parameters PARAMETERS_KEY = 'action_dispatch.request.path_parameters' + DEFAULT_PARSERS = { + Mime[:json] => lambda { |raw_post| + data = ActiveSupport::JSON.decode(raw_post) + data.is_a?(Hash) ? data : {:_json => data} + } + } + + def self.included(klass) + class << klass + attr_accessor :parameter_parsers + end + + klass.parameter_parsers = DEFAULT_PARSERS + end # Returns both GET and POST \parameters in a single hash. def parameters params = get_header("action_dispatch.request.parameters") @@ -31,6 +45,27 @@ module ActionDispatch def path_parameters get_header(PARAMETERS_KEY) || {} end + + private + + def parse_formatted_parameters(parsers) + return yield if content_length.zero? + + strategy = parsers.fetch(content_mime_type) { return yield } + + begin + strategy.call(raw_post) + rescue # JSON or Ruby code block errors + my_logger = logger || ActiveSupport::Logger.new($stderr) + my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" + + raise ParamsParser::ParseError + end + end + + def params_parsers + ActionDispatch::Request.parameter_parsers + end end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 18504eba6d..3280799647 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -36,8 +36,8 @@ module ActionDispatch HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP - HTTP_X_FORWARDED_FOR HTTP_VERSION - HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST + HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION + HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST SERVER_ADDR ].freeze @@ -65,7 +65,7 @@ module ActionDispatch path_parameters.each do |key, value| next unless value.respond_to?(:valid_encoding?) unless value.valid_encoding? - raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" + raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}" end end end @@ -306,10 +306,16 @@ module ActionDispatch end end - # Returns true if the request's content MIME type is - # +application/x-www-form-urlencoded+ or +multipart/form-data+. + # Determine whether the request body contains form-data by checking + # the request Content-Type for one of the media-types: + # "application/x-www-form-urlencoded" or "multipart/form-data". The + # list of form-data media types can be modified through the + # +FORM_DATA_MEDIA_TYPES+ array. + # + # A request body is not assumed to contain form-data when no + # Content-Type header is provided and the request_method is POST. def form_data? - FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s) + FORM_DATA_MEDIA_TYPES.include?(media_type) end def body_stream #:nodoc: @@ -324,7 +330,7 @@ module ActionDispatch else self.session = {} end - set_header('action_dispatch.request.flash_hash', nil) + self.flash = nil end def session=(session) #:nodoc: @@ -338,20 +344,29 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET fetch_header("action_dispatch.request.query_parameters") do |k| - set_header k, Request::Utils.normalize_encode_params(super || {}) + rack_query_params = super || {} + # Check for non UTF-8 parameter values, which would cause errors later + Request::Utils.check_param_encoding(rack_query_params) + set_header k, Request::Utils.normalize_encode_params(rack_query_params) end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e - raise ActionController::BadRequest.new(:query, e) + raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}") end alias :query_parameters :GET # Override Rack's POST method to support indifferent access def POST fetch_header("action_dispatch.request.request_parameters") do - self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + pr = parse_formatted_parameters(params_parsers) do |params| + super || {} + end + self.request_parameters = Request::Utils.normalize_encode_params(pr) end + rescue ParamsParser::ParseError # one of the parse strategies blew up + self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + raise rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e - raise ActionController::BadRequest.new(:request, e) + raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}") end alias :request_parameters :POST @@ -378,6 +393,9 @@ module ActionDispatch get_header("action_dispatch.logger".freeze) end + def commit_flash + end + private def check_method(name) HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index d1e1f1fcf6..f0127aa276 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,6 +32,29 @@ module ActionDispatch # :nodoc: # end # end class Response + class Header < DelegateClass(Hash) # :nodoc: + def initialize(response, header) + @response = response + super(header) + end + + def []=(k,v) + if @response.sending? || @response.sent? + raise ActionDispatch::IllegalStateError, 'header already sent' + end + + super + end + + def merge(other) + self.class.new @response, __getobj__.merge(other) + end + + def to_hash + __getobj__.dup + end + end + # The request that the response is responding to. attr_accessor :request @@ -55,6 +78,10 @@ module ActionDispatch # :nodoc: cattr_accessor(:default_headers) include Rack::Response::Helpers + # Aliasing these off because AD::Http::Cache::Response defines them + alias :_cache_control :cache_control + alias :_cache_control= :cache_control= + include ActionDispatch::Http::FilterRedirect include ActionDispatch::Http::Cache::Response include MonitorMixin @@ -103,18 +130,25 @@ module ActionDispatch # :nodoc: end end + def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers) + header = merge_default_headers(header, default_headers) + new status, header, body + end + + def self.merge_default_headers(original, default) + default.respond_to?(:merge) ? default.merge(original) : original + end + # The underlying body, as a streamable object. attr_reader :stream - def initialize(status = 200, header = {}, body = [], default_headers: self.class.default_headers) + def initialize(status = 200, header = {}, body = []) super() - header = merge_default_headers(header, default_headers) - @header = header + @header = Header.new(self, header) self.body, self.status = body, status - @blank = false @cv = new_cond @committed = false @sending = false @@ -125,7 +159,7 @@ module ActionDispatch # :nodoc: yield self if block_given? end - def have_header?(key); headers.key? key; end + def has_header?(key); headers.key? key; end def get_header(key); headers[key]; end def set_header(key, v); headers[key] = v; end def delete_header(key); headers.delete key; end @@ -248,12 +282,12 @@ module ActionDispatch # :nodoc: @stream.body end - EMPTY = " " + def write(string) + @stream.write string + end # Allows you to manually set or override the response body. def body=(body) - @blank = true if body == EMPTY - if body.respond_to?(:to_path) @stream = body else @@ -263,6 +297,40 @@ module ActionDispatch # :nodoc: end end + # Avoid having to pass an open file handle as the response body. + # Rack::Sendfile will usually intercept the response and uses + # the path directly, so there is no reason to open the file. + class FileBody #:nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + def body + File.binread(to_path) + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, 'rb') do |file| + while chunk = file.read(16384) + yield chunk + end + end + end + end + + # Send the file stored at +path+ as the response body. + def send_file(path) + commit! + @stream = FileBody.new(path) + end + + def reset_body! + @stream = build_buffer(self, []) + end + def body_parts parts = [] @stream.each { |x| parts << x } @@ -340,15 +408,12 @@ module ActionDispatch # :nodoc: return if committed? assign_default_content_type_and_charset! handle_conditional_get! + handle_no_content! end def before_sending end - def merge_default_headers(original, default) - default.respond_to?(:merge) ? default.merge(original) : original - end - def build_buffer(response, body) Buffer.new response, body end @@ -361,7 +426,7 @@ module ActionDispatch # :nodoc: return if content_type ct = parse_content_type - set_content_type(ct.mime_type || Mime::HTML.to_s, + set_content_type(ct.mime_type || Mime[:html].to_s, ct.charset || self.class.default_charset) end @@ -401,10 +466,15 @@ module ActionDispatch # :nodoc: end end - def rack_response(status, header) + def handle_no_content! if NO_CONTENT_CODES.include?(@status) - header.delete CONTENT_TYPE - header.delete 'Content-Length' + @header.delete CONTENT_TYPE + @header.delete 'Content-Length' + end + end + + def rack_response(status, header) + if NO_CONTENT_CODES.include?(status) [status, header, []] else [status, header, RackBody.new(self)] diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 92b10b6d3b..37f41ae988 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -81,7 +81,8 @@ module ActionDispatch def add_params(path, params) params = { params: params } unless params.is_a?(Hash) params.reject! { |_,v| v.to_param.nil? } - path << "?#{params.to_query}" unless params.empty? + query = params.to_query + path << "?#{query}" unless query.empty? end def add_anchor(path, anchor) diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 47bf76bdbf..7063b44bb5 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index d069bf0205..2793c5668d 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -42,6 +42,7 @@ module ActionDispatch def terminal?; false; end def star?; false; end def cat?; false; end + def group?; false; end end class Terminal < Node # :nodoc: @@ -95,6 +96,7 @@ module ActionDispatch class Group < Unary # :nodoc: def type; :GROUP; end + def group?; true; end end class Star < Unary # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index e93970046c..5ee8810066 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -46,7 +46,7 @@ module ActionDispatch end def names - @names ||= spec.grep(Nodes::Symbol).map(&:name) + @names ||= spec.find_all(&:symbol?).map(&:name) end def required_names @@ -54,8 +54,8 @@ module ActionDispatch end def optional_names - @optional_names ||= spec.grep(Nodes::Group).flat_map { |group| - group.grep(Nodes::Symbol) + @optional_names ||= spec.find_all(&:group?).flat_map { |group| + group.find_all(&:symbol?) }.map(&:name).uniq end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index f5c9abf1cc..35c2b1b86e 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -163,7 +163,7 @@ module ActionDispatch end def verb - %r[^#{verbs.join('|')}$] + verbs.join('|') end private diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 537c9b2f5c..306d2e674a 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - module ActionDispatch module Journey # :nodoc: class Format diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index b653e4eacd..65baf117ba 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -77,6 +77,12 @@ module ActionDispatch # # It can be read using the signed method `cookies.signed[:name]` # cookies.signed[:user_id] = current_user.id # + # # Sets an encrypted cookie value before sending it to the client which + # # prevent users from reading and tampering with its value. + # # The cookie is signed by your app's `secrets.secret_key_base` value. + # # It can be read using the encrypted method `cookies.encrypted[:name]` + # cookies.encrypted[:discount] = 45 + # # # Sets a "permanent" cookie (which expires in 20 years from now). # cookies.permanent[:login] = "XJ-122" # @@ -89,6 +95,7 @@ module ActionDispatch # cookies.size # => 2 # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] # cookies.signed[:login] # => "XJ-122" + # cookies.encrypted[:discount] # => 45 # # Example for deleting: # @@ -396,17 +403,32 @@ module ActionDispatch end def write(headers) - @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) } - @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) } + if header = make_set_cookie_header(headers[HTTP_HEADER]) + headers[HTTP_HEADER] = header + end end mattr_accessor :always_write_cookie self.always_write_cookie = false private - def write_cookie?(cookie) - request.ssl? || !cookie[:secure] || always_write_cookie - end + + def make_set_cookie_header(header) + header = @set_cookies.inject(header) { |m, (k, v)| + if write_cookie?(v) + ::Rack::Utils.add_cookie_to_header(m, k, v) + else + m + end + } + @delete_cookies.inject(header) { |m, (k, v)| + ::Rack::Utils.add_remove_cookie_to_header(m, k, v) + } + end + + def write_cookie?(cookie) + request.ssl? || !cookie[:secure] || always_write_cookie + end end class AbstractCookieJar # :nodoc: diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 039efc3af8..3b61824cc9 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -37,7 +37,7 @@ module ActionDispatch @backtrace_cleaner = backtrace_cleaner @exception = original_exception(exception) - expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError) + expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError) end def rescue_template @@ -61,7 +61,7 @@ module ActionDispatch end def traces - appplication_trace_with_ids = [] + application_trace_with_ids = [] framework_trace_with_ids = [] full_trace_with_ids = [] @@ -69,7 +69,7 @@ module ActionDispatch trace_with_id = { id: idx, trace: trace } if application_trace.include?(trace) - appplication_trace_with_ids << trace_with_id + application_trace_with_ids << trace_with_id else framework_trace_with_ids << trace_with_id end @@ -78,7 +78,7 @@ module ActionDispatch end { - "Application Trace" => appplication_trace_with_ids, + "Application Trace" => application_trace_with_ids, "Framework Trace" => framework_trace_with_ids, "Full Trace" => full_trace_with_ids } @@ -106,17 +106,13 @@ module ActionDispatch end def original_exception(exception) - if registered_original_exception?(exception) - exception.original_exception + if @@rescue_responses.has_key?(exception.cause.class.name) + exception.cause else exception end end - def registered_original_exception?(exception) - exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name) - end - def clean_backtrace(*args) if backtrace_cleaner backtrace_cleaner.clean(backtrace, *args) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index c482b1c5e7..c51dcd542a 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,25 +1,6 @@ require 'active_support/core_ext/hash/keys' module ActionDispatch - class Request - # Access the contents of the flash. Use <tt>flash["notice"]</tt> to - # read a notice you put there or <tt>flash["notice"] = "hello"</tt> - # to put a new one. - def flash - flash = flash_hash - return flash if flash - self.flash = Flash::FlashHash.from_session_value(session["flash"]) - end - - def flash=(flash) - set_header Flash::KEY, flash - end - - def flash_hash # :nodoc: - get_header Flash::KEY - end - end - # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can @@ -57,6 +38,40 @@ module ActionDispatch class Flash KEY = 'action_dispatch.request.flash_hash'.freeze + module RequestMethods + # Access the contents of the flash. Use <tt>flash["notice"]</tt> to + # read a notice you put there or <tt>flash["notice"] = "hello"</tt> + # to put a new one. + def flash + flash = flash_hash + return flash if flash + self.flash = Flash::FlashHash.from_session_value(session["flash"]) + end + + def flash=(flash) + set_header Flash::KEY, flash + end + + def flash_hash # :nodoc: + get_header Flash::KEY + end + + def commit_flash # :nodoc: + session = self.session || {} + flash_hash = self.flash_hash + + if flash_hash && (flash_hash.present? || session.key?('flash')) + session["flash"] = flash_hash.to_session_value + self.flash = flash_hash.dup + end + + if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) + session.key?('flash') && session['flash'].nil? + session.delete('flash') + end + end + end + class FlashNow #:nodoc: attr_accessor :flash @@ -268,26 +283,10 @@ module ActionDispatch end end - def initialize(app) - @app = app - end - - def call(env) - req = ActionDispatch::Request.new env - @app.call(env) - ensure - session = Request::Session.find(req) || {} - flash_hash = req.flash_hash - - if flash_hash && (flash_hash.present? || session.key?('flash')) - session["flash"] = flash_hash.to_session_value - req.flash = flash_hash.dup - end + def self.new(app) app; end + end - if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) - session.key?('flash') && session['flash'].nil? - session.delete('flash') - end - end + class Request + prepend Flash::RequestMethods end end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 9cde9c9b98..c2a4f46e67 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -10,56 +10,35 @@ module ActionDispatch # Raised when raw data from the request cannot be parsed by the parser # defined for request's content mime type. class ParseError < StandardError - attr_reader :original_exception - def initialize(message, original_exception) - super(message) - @original_exception = original_exception + def initialize(message = nil, original_exception = nil) + if message + ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \ + "#{self.class} will automatically capture the message " \ + "of the original exception.", caller) + end + + if original_exception + ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ + "Exceptions will automatically capture the original exception.", caller) + end + + super($!.message) end - end - DEFAULT_PARSERS = { - Mime::JSON => lambda { |raw_post| - data = ActiveSupport::JSON.decode(raw_post) - data = {:_json => data} unless data.is_a?(Hash) - Request::Utils.normalize_encode_params(data) - } - } + def original_exception + ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) + cause + end + end # Create a new +ParamsParser+ middleware instance. # # The +parsers+ argument can take Hash of parsers where key is identifying # content mime type, and value is a lambda that is going to process data. - def initialize(app, parsers = {}) - @app, @parsers = app, DEFAULT_PARSERS.merge(parsers) - end - - def call(env) - request = Request.new(env) - - parse_formatted_parameters(request, @parsers) do |params| - request.request_parameters = params - end - - @app.call(env) + def self.new(app, parsers = {}) + ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers) + app end - - private - def parse_formatted_parameters(request, parsers) - return if request.content_length.zero? - - strategy = parsers.fetch(request.content_mime_type) { return nil } - - yield strategy.call(request.raw_post) - - rescue => e # JSON or Ruby code block errors - logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" - - raise ParseError.new(e.message, e) - end - - def logger(request) - request.logger || ActiveSupport::Logger.new($stderr) - end end end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 6c7fba00cb..af9a29eb07 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation/reporting' - module ActionDispatch # ActionDispatch::Reloader provides prepare and cleanup callbacks, # intended to assist with code reloading during development. diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index aee2334da9..31b75498b6 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -43,7 +43,7 @@ module ActionDispatch # Create a new +RemoteIp+ middleware instance. # - # The +check_ip_spoofing+ option is on by default. When on, an exception + # The +ip_spoofing_check+ 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 @@ -57,9 +57,9 @@ module ActionDispatch # with your proxy servers after it. If your proxies aren't removed, pass # them in via the +custom_proxies+ 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) + def initialize(app, ip_spoofing_check = true, custom_proxies = nil) @app = app - @check_ip = check_ip_spoofing + @check_ip = ip_spoofing_check @proxies = if custom_proxies.blank? TRUSTED_PROXIES elsif custom_proxies.respond_to?(:any?) @@ -116,10 +116,18 @@ module ActionDispatch forwarded_ips = ips_from(@req.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. + # If they are both set, it means that either: + # + # 1) This request passed through two proxies with incompatible IP header + # conventions. + # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+ + # (whichever the proxy servers weren't using) themselves. + # + # Either way, there is no way for us to determine which header is the + # right one after the fact. Since we have no idea, if we are concerned + # about IP spoofing we need to give up and explode. (If you're not + # concerned about IP spoofing you can turn the +ip_spoofing_check+ + # option off.) 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 diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 9e50fea3fc..5fb5953811 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -7,14 +7,22 @@ require 'action_dispatch/request/session' module ActionDispatch module Session class SessionRestoreError < StandardError #:nodoc: - attr_reader :original_exception - def initialize(const_error) - @original_exception = const_error + def initialize(const_error = nil) + if const_error + ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ + "Exceptions will automatically capture the original exception.", caller) + end super("Session contains objects whose class definition isn't available.\n" + "Remember to require the classes for all objects kept in the session.\n" + - "(Original exception: #{const_error.message} [#{const_error.class}])\n") + "(Original exception: #{$!.message} [#{$!.class}])\n") + set_backtrace $!.backtrace + end + + def original_exception + ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) + cause end end @@ -59,8 +67,8 @@ module ActionDispatch begin # Note that the regexp does not allow $1 to end with a ':' $1.constantize - rescue LoadError, NameError => e - raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace + rescue LoadError, NameError + raise ActionDispatch::Session::SessionRestoreError end retry else diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 02b6cfe727..0e636b8257 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -62,11 +62,7 @@ module ActionDispatch # would set the session cookie to expire automatically 14 days after creation. # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and # <tt>:httponly</tt>. - class CookieStore < Rack::Session::Abstract::Persisted - include Compatibility - include StaleSessionCheck - include SessionObject - + class CookieStore < AbstractStore def initialize(app, options={}) super(app, options.merge!(:cookie_only => true)) end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index b72953f1d1..47f475559a 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -15,7 +15,8 @@ module ActionDispatch # # Configure HSTS with `hsts: { … }`: # * `expires`: How long, in seconds, these settings will stick. Defaults to - # `18.weeks`, the minimum required to qualify for browser preload lists. + # `180.days` (recommended). The minimum required to qualify for browser + # preload lists is `18.weeks`. # * `subdomains`: Set to `true` to tell the browser to apply these settings # to all subdomains. This protects your cookies from interception by a # vulnerable site on a subdomain. Defaults to `false`. diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 90e2ae6802..44fc1ee736 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -15,7 +15,11 @@ module ActionDispatch def name; klass.name; end def inspect - klass.to_s + if klass.is_a?(Class) + klass.to_s + else + klass.class.to_s + end end def build(app) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index c4344c9609..ea9ab3821d 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/uri' module ActionDispatch # This middleware returns a file's contents from disk in the body response. - # When initialized, it can accept an optional 'Cache-Control' header, which - # will be set when a response containing a file's contents is delivered. + # When initialized, it can accept optional HTTP headers, which will be set + # when a response containing a file's contents is delivered. # # This middleware will render the file specified in `env["PATH_INFO"]` # where the base path is in the +root+ directory. For example, if the +root+ @@ -13,12 +13,10 @@ module ActionDispatch # located at `public/assets/application.js` if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler - def initialize(root, cache_control, index: 'index') + def initialize(root, index: 'index', headers: {}) @root = root.chomp('/') - @compiled_root = /^#{Regexp.escape(root)}/ - headers = cache_control && { 'Cache-Control' => cache_control } - @file_server = ::Rack::File.new(@root, headers) - @index = index + @file_server = ::Rack::File.new(@root, headers) + @index = index end # Takes a path to a file. If the file is found, has valid encoding, and has @@ -108,9 +106,16 @@ module ActionDispatch # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' # requests will result in a file being returned. class Static - def initialize(app, path, cache_control = nil, index: 'index') + def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {}) + if deprecated_cache_control != :not_set + ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \ + "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \ + " and will be removed in Rails 5.1.") + headers['Cache-Control'.freeze] = deprecated_cache_control + end + @app = app - @file_handler = FileHandler.new(path, cache_control, index: index) + @file_handler = FileHandler.new(path, index: index, headers: headers) end def call(env) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb index e7b913bbe4..e7b913bbe4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb new file mode 100644 index 0000000000..23a9c7ba3f --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb @@ -0,0 +1,8 @@ +<% @source_extracts.first(3).each do |source_extract| %> +<% if source_extract[:code] %> +Extracted source (around line #<%= source_extract[:line_number] %>): + +<% source_extract[:code].each do |line, source| -%> +<%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%> +<% end %> +<% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb index c1e8b6cae3..5060da9369 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb @@ -1,6 +1,6 @@ <header> <h1> - <%= @exception.original_exception.class.to_s %> in + <%= @exception.cause.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> </h1> </header> 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 index 77bcd26726..78d52acd96 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb @@ -1,4 +1,4 @@ -<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> +<%= @exception.cause.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 %> diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index a8151a8224..bb3df3c311 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -13,6 +13,21 @@ module ActionDispatch end end + def self.check_param_encoding(params) + case params + when Array + params.each { |element| check_param_encoding(element) } + when Hash + params.each_value { |value| check_param_encoding(value) } + when String + unless params.valid_encoding? + # Raise Rack::Utils::InvalidParameterError for consistency with Rack. + # ActionDispatch::Request#GET will re-raise as a BadRequest error. + raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}" + end + end + end + class ParamEncoder # :nodoc: # Convert nested Hash to HashWithIndifferentAccess. # diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 8757c9ea7f..59c3f9248f 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module ActionDispatch # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces @@ -55,7 +53,7 @@ module ActionDispatch # resources :posts, :comments # end # - # Alternately, you can add prefixes to your path without using a separate + # Alternatively, you can add prefixes to your path without using a separate # directory by using +scope+. +scope+ takes additional options which # apply to all enclosed routes. # @@ -148,6 +146,7 @@ module ActionDispatch # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /\d{5}(-\d{4})?/ # } + # end # # Constraints can include the 'ignorecase' and 'extended syntax' regular # expression modifiers: diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 48c10a7d4c..f3a5268d2e 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -16,10 +16,6 @@ module ActionDispatch app.app end - def verb - super.source.gsub(/[$^]/, '') - end - def path super.spec.to_s end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 87b826f7d0..e676c837b4 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -3,7 +3,6 @@ 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/regexp' -require 'active_support/deprecation' require 'action_dispatch/routing/redirection' require 'action_dispatch/routing/endpoint' @@ -402,7 +401,8 @@ module ActionDispatch # because this means it will be matched first. As this is the most popular route # of most Rails applications, this is beneficial. def root(options = {}) - match '/', { :as => :root, :via => :get }.merge!(options) + name = has_named_route?(:root) ? nil : :root + match '/', { as: name, via: :get }.merge!(options) end # Matches a url pattern to one or more routes. @@ -482,7 +482,7 @@ module ActionDispatch # resources :user, param: :name # # You can override <tt>ActiveRecord::Base#to_param</tt> of a related - # model to construct an URL: + # model to construct a URL: # # class User < ActiveRecord::Base # def to_param @@ -600,17 +600,20 @@ module ActionDispatch def mount(app, options = nil) if options path = options.delete(:at) - else - unless Hash === app - raise ArgumentError, "must be called with mount point" - end - + elsif Hash === app options = app app, path = options.find { |k, _| k.respond_to?(:call) } options.delete(app) if app end - raise "A rack application must be specified" unless path + raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call) + raise ArgumentError, <<-MSG.strip_heredoc unless path + Must be called with mount point + + mount SomeRackApp, at: "some_route" + or + mount(SomeRackApp => "some_route") + MSG rails_app = rails_app? app options[:as] ||= app_name(app, rails_app) @@ -665,6 +668,7 @@ module ActionDispatch super(options) else prefix_options = options.slice(*_route.segment_keys) + prefix_options[:relative_url_root] = ''.freeze # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _routes.url_helpers.send("#{name}_path", prefix_options) @@ -1866,7 +1870,7 @@ to this: # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. if as.nil? - candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate) + candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate) else candidate end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index e4b8d5993e..c4228df925 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,4 @@ require 'action_dispatch/journey' -require 'forwardable' require 'active_support/concern' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' @@ -372,10 +371,6 @@ module ActionDispatch end def eval_block(block) - if block.arity == 1 - raise "You are using the old router DSL which has been removed in Rails 3.1. " << - "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" - end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) @@ -656,14 +651,18 @@ module ActionDispatch RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, :trailing_slash, :anchor, :params, :only_path, :script_name, - :original_script_name] + :original_script_name, :relative_url_root] def optimize_routes_generation? default_url_options.empty? end def find_script_name(options) - options.delete(:script_name) || relative_url_root || '' + options.delete(:script_name) || find_relative_url_root(options) || '' + end + + def find_relative_url_root(options) + options.delete(:relative_url_root) || relative_url_root end def path_for(options, route_name = nil) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 883cd9c2c3..b6c031dcf4 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -1,7 +1,7 @@ module ActionDispatch module Routing # In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse - # is also possible: an URL can be generated from one of your routing definitions. + # is also possible: a URL can be generated from one of your routing definitions. # URL generation functionality is centralized in this module. # # See ActionDispatch::Routing for general information about routing and routes.rb. diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 21b3b89d22..fae266273e 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -12,7 +12,7 @@ module ActionDispatch include Rails::Dom::Testing::Assertions def html_document - @html_document ||= if @response.content_type === Mime::XML + @html_document ||= if @response.content_type.to_s =~ /xml\z/ Nokogiri::XML::Document.parse(@response.body) else Nokogiri::HTML::Document.parse(@response.body) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index b6e21b0d28..eab20b075d 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -21,10 +21,10 @@ module ActionDispatch # or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>. # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list. # - # # assert that the response was a redirection + # # Asserts that the response was a redirection # assert_response :redirect # - # # assert that the response code was status code 401 (unauthorized) + # # Asserts that the response code was status code 401 (unauthorized) # assert_response 401 def assert_response(type, message = nil) if Symbol === type @@ -42,20 +42,20 @@ module ActionDispatch end end - # Assert that the redirection options passed in match those of the redirect called in the latest action. + # Asserts that the redirection options passed in match those of the redirect called in the latest action. # This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also # match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on. # - # # assert that the redirection was to the "index" action on the WeblogController + # # Asserts that the redirection was to the "index" action on the WeblogController # assert_redirected_to controller: "weblog", action: "index" # - # # assert that the redirection was to the named route login_url + # # Asserts that the redirection was to the named route login_url # assert_redirected_to login_url # - # # assert that the redirection was to the url for @customer + # # Asserts that the redirection was to the url for @customer # assert_redirected_to @customer # - # # asserts that the redirection matches the regular expression + # # Asserts that the redirection matches the regular expression # assert_redirected_to %r(\Ahttp://example.org) def assert_redirected_to(options = {}, message=nil) assert_response(:redirect, message) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 54e24ed6bf..78ef860548 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -14,14 +14,14 @@ module ActionDispatch # requiring a specific HTTP method. The hash should contain a :path with the incoming request path # and a :method containing the required HTTP verb. # - # # assert that POSTing to /items will call the create action on ItemsController + # # Asserts that POSTing to /items will call the create action on ItemsController # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) # # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used - # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the + # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the # extras argument, appending the query string on the path directly will not work. For example: # - # # assert that a path of '/items/list/1?view=print' returns the correct options + # # Asserts that a path of '/items/list/1?view=print' returns the correct options # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) # # The +message+ parameter allows you to pass in an error message that is displayed upon failure. @@ -104,13 +104,13 @@ module ActionDispatch # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The # +message+ parameter allows you to specify a custom error message to display upon failure. # - # # Assert a basic route: a controller with the default action (index) + # # Asserts a basic route: a controller with the default action (index) # assert_routing '/home', controller: 'home', action: 'index' # # # Test a route generated with a specific controller, action, and parameter (id) # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 # - # # Assert a basic route (controller + default action), with an error message if it fails + # # Asserts a basic route (controller + default action), with an error message if it fails # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' # # # Tests a route, providing a defaults hash diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 4dfd4f3f71..790f9ea5d2 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -131,35 +131,35 @@ module ActionDispatch # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:delete, path, *args) end end @@ -354,7 +354,7 @@ module ActionDispatch if xhr headers ||= {} headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') end # this modifies the passed request_env directly diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 6a31d6243f..4b79a90242 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -7,7 +7,7 @@ module ActionDispatch # See Response for more information on controller response objects. class TestResponse < Response def self.from_response(response) - new response.status, response.headers, response.body, default_headers: nil + new response.status, response.headers, response.body end # Was the response successful? |