diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
8 files changed, 181 insertions, 102 deletions
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index fd86966c50..f86ae26b8a 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,10 +1,11 @@ require 'set' require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/string/starts_ends_with' module Mime class Mimes < Array def symbols - @symbols ||= map {|m| m.to_sym } + @symbols ||= map { |m| m.to_sym } end %w(<< concat shift unshift push pop []= clear compact! collect! @@ -23,14 +24,16 @@ module Mime EXTENSION_LOOKUP = {} LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? } - def self.[](type) - return type if type.is_a?(Type) - Type.lookup_by_extension(type.to_s) - end + class << self + def [](type) + return type if type.is_a?(Type) + Type.lookup_by_extension(type) + end - def self.fetch(type) - return type if type.is_a?(Type) - EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + def fetch(type) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + end end # Encapsulates the notion of a mime type. Can be used at render time, for example, with: @@ -61,32 +64,84 @@ module Mime # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: - attr_accessor :order, :name, :q + attr_accessor :index, :name, :q + alias :to_s :name - def initialize(order, name, q=nil) - @order = order - @name = name.strip - q ||= 0.0 if @name == Mime::ALL # default wildcard match to end of list + 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 = ((q || 1.0).to_f * 100).to_i end - def to_s - @name - end - def <=>(item) - result = item.q <=> q - result = order <=> item.order if result == 0 + result = item.q <=> @q + result = @index <=> item.index if result == 0 result end def ==(item) - name == (item.respond_to?(:name) ? item.name : item) + @name == item.to_s end end - class << self + class AcceptList < Array #:nodoc: + def assort! + sort! + + # Take care of the broken text/xml entry by renaming or deleting it + if text_xml_idx && app_xml_idx + app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two + 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 + end + + # Look for more specific XML-based types and sort them ahead of app/xml + if app_xml_idx + idx = app_xml_idx + + while idx < length + type = self[idx] + break if type.q < app_xml.q + + if type.name.ends_with? '+xml' + self[app_xml_idx], self[idx] = self[idx], app_xml + @app_xml_idx = idx + end + idx += 1 + end + end + map! { |i| Mime::Type.lookup(i.name) }.uniq! + to_a + end + + private + def text_xml_idx + @text_xml_idx ||= index('text/xml') + end + + def app_xml_idx + @app_xml_idx ||= index(Mime::XML.to_s) + end + + def text_xml + self[text_xml_idx] + end + + def app_xml + self[app_xml_idx] + end + + def exchange_xml_items + self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml + @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx + end + end + + class << self TRAILING_STAR_REGEXP = /(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ @@ -125,75 +180,30 @@ module Mime def parse(accept_header) if accept_header !~ /,/ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first - if accept_header =~ TRAILING_STAR_REGEXP - parse_data_with_trailing_star($1) - else - [Mime::Type.lookup(accept_header)] - end + parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)] else - # keep track of creation order to keep the subsequent sort stable - list, index = [], 0 - accept_header.split(/,/).each do |header| + list, index = AcceptList.new, 0 + accept_header.split(',').each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) if params.present? params.strip! - if params =~ TRAILING_STAR_REGEXP - parse_data_with_trailing_star($1).each do |m| - list << AcceptItem.new(index, m.to_s, q) - index += 1 - end - else - list << AcceptItem.new(index, params, q) - index += 1 - end - end - end - list.sort! - - # Take care of the broken text/xml entry by renaming or deleting it - text_xml = list.index("text/xml") - app_xml = list.index(Mime::XML.to_s) - - if text_xml && app_xml - # set the q value to the max of the two - list[app_xml].q = [list[text_xml].q, list[app_xml].q].max - - # make sure app_xml is ahead of text_xml in the list - if app_xml > text_xml - list[app_xml], list[text_xml] = list[text_xml], list[app_xml] - app_xml, text_xml = text_xml, app_xml - end - - # delete text_xml from the list - list.delete_at(text_xml) + params = parse_trailing_star(params) || [params] - elsif text_xml - list[text_xml].name = Mime::XML.to_s - end - - # Look for more specific XML-based types and sort them ahead of app/xml - - if app_xml - idx = app_xml - app_xml_type = list[app_xml] - - while(idx < list.length) - type = list[idx] - break if type.q < app_xml_type.q - if type.name =~ /\+xml$/ - list[app_xml], list[idx] = list[idx], list[app_xml] - app_xml = idx + params.each do |m| + list << AcceptItem.new(index, m.to_s, q) + index += 1 end - idx += 1 end end - - list.map! { |i| Mime::Type.lookup(i.name) }.uniq! - list + list.assort! end end + def parse_trailing_star(accept_header) + 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>. # @@ -273,18 +283,18 @@ module Mime @@html_types.include?(to_sym) || @string =~ /html/ end - def respond_to?(method, include_private = false) #:nodoc: - super || method.to_s =~ /(\w+)\?$/ - end - private def method_missing(method, *args) - if method.to_s =~ /(\w+)\?$/ - $1.downcase.to_sym == to_sym + if method.to_s.ends_with? '?' + method[0..-2].downcase.to_sym == to_sym else super end end + + def respond_to_missing?(method, include_private = false) #:nodoc: + method.to_s.ends_with? '?' + end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index d24c7c7f3f..b8ebeb408f 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -227,8 +227,11 @@ module ActionDispatch # TODO This should be broken apart into AD::Request::Session and probably # be included by the session middleware. def reset_session - session.destroy if session && session.respond_to?(:destroy) - self.session = {} + if session && session.respond_to?(:destroy) + session.destroy + else + self.session = {} + end @env['action_dispatch.request.flash_hash'] = nil end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 9b159b2caf..019849ef95 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -44,6 +44,14 @@ module ActionDispatch include StaleSessionCheck include SessionObject + # Override rack's method + def destroy_session(env, session_id, options) + new_sid = super + # Reset hash and Assign the new session id + env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} + new_sid + end + private def unpacked_cookie_data(env) diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index 38a737cd2b..b4d6629c35 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,15 +1,19 @@ require 'action_dispatch/middleware/session/abstract_store' -require 'rack/session/memcache' +begin + require 'rack/session/dalli' +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end module ActionDispatch module Session - class MemCacheStore < Rack::Session::Memcache + class MemCacheStore < Rack::Session::Dalli include Compatibility include StaleSessionCheck include SessionObject def initialize(app, options = {}) - require 'memcache' options[:expire_after] ||= options[:expires] super end diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 35d694b4a1..a05a23d953 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -70,9 +70,12 @@ module ActionDispatch def destroy clear options = self.options || {} - @by.send(:destroy_session, @env, options[:id], options) - options[:id] = nil + new_sid = @by.send(:destroy_session, @env, options[:id], options) + options[:id] = new_sid # Reset session id with a new value or nil + + # Load the new sid to be written with the response @loaded = false + load_for_write! end def [](key) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index b52f66faf1..ddb34a2394 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1585,7 +1585,7 @@ module ActionDispatch end end - # Routing Concerns allows you to declare common routes that can be reused + # Routing Concerns allow you to declare common routes that can be reused # inside others resources and routes. # # concern :commentable do @@ -1608,13 +1608,63 @@ module ActionDispatch module Concerns # Define a routing concern using a name. # - # concern :commentable do - # resources :comments + # Concerns may be defined inline, using a block, or handled by + # another object, by passing that object as the second parameter. + # + # The concern object, if supplied, should respond to <tt>call</tt>, + # which will receive two parameters: + # + # * The current mapper + # * A hash of options which the concern object may use + # + # Options may also be used by concerns defined in a block by accepting + # a block parameter. So, using a block, you might do something as + # simple as limit the actions available on certain resources, passing + # standard resource options through the concern: + # + # concern :commentable do |options| + # resources :comments, options # end # - # Any routing helpers can be used inside a concern. - def concern(name, &block) - @concerns[name] = block + # resources :posts, concerns: :commentable + # resources :archived_posts do + # # Don't allow comments on archived posts + # concerns :commentable, only: [:index, :show] + # end + # + # Or, using a callable object, you might implement something more + # specific to your application, which would be out of place in your + # routes file. + # + # # purchasable.rb + # class Purchasable + # def initialize(defaults = {}) + # @defaults = defaults + # end + # + # def call(mapper, options = {}) + # options = @defaults.merge(options) + # mapper.resources :purchases + # mapper.resources :receipts + # mapper.resources :returns if options[:returnable] + # end + # end + # + # # routes.rb + # concern :purchasable, Purchasable.new(returnable: true) + # + # resources :toys, concerns: :purchasable + # resources :electronics, concerns: :purchasable + # resources :pets do + # concerns :purchasable, returnable: false + # end + # + # Any routing helpers can be used inside a concern. If using a + # callable, they're accessible from the Mapper that's passed to + # <tt>call</tt>. + def concern(name, callable = nil, &block) + callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) } + @concerns[name] = callable end # Use the named concerns @@ -1628,10 +1678,11 @@ module ActionDispatch # namespace :posts do # concerns :commentable # end - def concerns(*names) - names.flatten.each do |name| + def concerns(*args) + options = args.extract_options! + args.flatten.each do |name| if concern = @concerns[name] - instance_eval(&concern) + concern.call(self, options) else raise ArgumentError, "No concern named #{name} was found!" end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 1fe40d8458..060d0bfa2f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -319,7 +319,7 @@ module ActionDispatch MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{name} - @#{name} ||= _#{name} + @_#{name} ||= _#{name} end RUBY end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index ab584abf68..a8b27ffafd 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -19,7 +19,7 @@ module ActionDispatch # - +headers+: Additional headers to pass, as a Hash. The headers will be # merged into the Rack env hash. # - # This method returns an Response object, which one can use to + # This method returns a Response object, which one can use to # inspect the details of the response. Furthermore, if this method was # called from an ActionDispatch::IntegrationTest object, then that # object's <tt>@response</tt> instance variable will point to the same |