aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/assertions/selector_assertions.rb24
-rw-r--r--actionpack/lib/action_controller/base.rb68
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb14
-rw-r--r--actionpack/lib/action_controller/flash.rb12
-rw-r--r--actionpack/lib/action_controller/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/integration.rb25
-rw-r--r--actionpack/lib/action_controller/lock.rb16
-rw-r--r--actionpack/lib/action_controller/middleware_stack.rb28
-rw-r--r--actionpack/lib/action_controller/middlewares.rb8
-rw-r--r--actionpack/lib/action_controller/mime_responds.rb23
-rw-r--r--actionpack/lib/action_controller/params_parser.rb71
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb18
-rw-r--r--actionpack/lib/action_controller/rack_ext.rb3
-rw-r--r--actionpack/lib/action_controller/rack_ext/lock.rb21
-rw-r--r--actionpack/lib/action_controller/rack_ext/multipart.rb22
-rw-r--r--actionpack/lib/action_controller/rack_ext/parse_query.rb18
-rwxr-xr-xactionpack/lib/action_controller/request.rb488
-rw-r--r--actionpack/lib/action_controller/request_profiler.rb168
-rw-r--r--actionpack/lib/action_controller/rescue.rb8
-rw-r--r--actionpack/lib/action_controller/response.rb9
-rw-r--r--actionpack/lib/action_controller/rewindable_input.rb28
-rw-r--r--actionpack/lib/action_controller/routing.rb5
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb67
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb10
-rw-r--r--actionpack/lib/action_controller/session_management.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb22
-rw-r--r--actionpack/lib/action_controller/test_process.rb121
-rw-r--r--actionpack/lib/action_controller/uploaded_file.rb44
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb155
-rw-r--r--actionpack/lib/action_controller/verb_piggybacking.rb24
30 files changed, 679 insertions, 849 deletions
diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb
index 248ca85994..0d56ea5ef7 100644
--- a/actionpack/lib/action_controller/assertions/selector_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -109,20 +109,27 @@ module ActionController
# starting from (and including) that element and all its children in
# depth-first order.
#
- # If no element if specified, calling +assert_select+ will select from the
- # response HTML. Calling #assert_select inside an +assert_select+ block will
- # run the assertion for each element selected by the enclosing assertion.
+ # If no element if specified, calling +assert_select+ selects from the
+ # response HTML unless +assert_select+ is called from within an +assert_select+ block.
+ #
+ # When called with a block +assert_select+ passes an array of selected elements
+ # to the block. Calling +assert_select+ from the block, with no element specified,
+ # runs the assertion on the complete set of elements selected by the enclosing assertion.
+ # Alternatively the array may be iterated through so that +assert_select+ can be called
+ # separately for each element.
+ #
#
# ==== Example
- # assert_select "ol>li" do |elements|
+ # If the response contains two ordered lists, each with four list elements then:
+ # assert_select "ol" do |elements|
# elements.each do |element|
- # assert_select element, "li"
+ # assert_select element, "li", 4
# end
# end
#
- # Or for short:
- # assert_select "ol>li" do
- # assert_select "li"
+ # will pass, as will:
+ # assert_select "ol" do
+ # assert_select "li", 8
# end
#
# The selector may be a CSS selector expression (String), an expression
@@ -402,6 +409,7 @@ module ActionController
if rjs_type
if rjs_type == :insert
position = args.shift
+ id = args.shift
insertion = "insert_#{position}".to_sym
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
statement = "(#{RJS_STATEMENTS[insertion]})"
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 4d4793c4e3..50b965ce4c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -301,10 +301,7 @@ module ActionController #:nodoc:
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
- @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
- Mime::URL_ENCODED_FORM => :url_encoded_form,
- Mime::XML => :xml_simple,
- Mime::JSON => :json }
+ @@param_parsers = {}
cattr_accessor :param_parsers
# Controls the default charset for all renders.
@@ -382,6 +379,13 @@ module ActionController #:nodoc:
attr_accessor :action_name
class << self
+ def call(env)
+ # HACK: For global rescue to have access to the original request and response
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
+ process(request, response)
+ end
+
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
@@ -640,7 +644,7 @@ module ActionController #:nodoc:
end
def session_enabled?
- request.session_options && request.session_options[:disabled] != false
+ ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller)
end
self.view_paths = []
@@ -859,16 +863,23 @@ module ActionController #:nodoc:
def render(options = nil, extra_options = {}, &block) #:doc:
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
+ validate_render_arguments(options, extra_options, block_given?)
+
if options.nil?
- return render(:file => default_template, :layout => true)
- elsif !extra_options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
- else
- if options == :update
- options = extra_options.merge({ :update => true })
- elsif !options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}"
+ options = { :template => default_template, :layout => true }
+ elsif options == :update
+ options = extra_options.merge({ :update => true })
+ elsif options.is_a?(String) || options.is_a?(Symbol)
+ case options.to_s.index('/')
+ when 0
+ extra_options[:file] = options
+ when nil
+ extra_options[:action] = options
+ else
+ extra_options[:template] = options
end
+
+ options = extra_options
end
layout = pick_layout(options)
@@ -1111,7 +1122,7 @@ module ActionController #:nodoc:
end
# Sets the etag, last_modified, or both on the response and renders a
- # "304 Not Modified" response if the request is already fresh.
+ # "304 Not Modified" response if the request is already fresh.
#
# Example:
#
@@ -1119,8 +1130,8 @@ module ActionController #:nodoc:
# @article = Article.find(params[:id])
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
# end
- #
- # This will render the show template if the request isn't sending a matching etag or
+ #
+ # This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified)
@@ -1186,6 +1197,16 @@ module ActionController #:nodoc:
end
end
+ def validate_render_arguments(options, extra_options, has_block)
+ if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol)
+ raise RenderError, "You called render with invalid options : #{options.inspect}"
+ end
+
+ if !extra_options.is_a?(Hash)
+ raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
+ end
+ end
+
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.helpers.send :include, self.class.master_helper_module
@@ -1215,7 +1236,7 @@ module ActionController #:nodoc:
log_processing_for_parameters
end
end
-
+
def log_processing_for_request_id
request_id = "\n\nProcessing #{self.class.name}\##{action_name} "
request_id << "to #{params[:format]} " if params[:format]
@@ -1227,7 +1248,7 @@ module ActionController #:nodoc:
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
-
+
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
end
@@ -1326,9 +1347,12 @@ module ActionController #:nodoc:
end
Base.class_eval do
- include Flash, Filters, Layout, Benchmarking, Rescue, MimeResponds, Helpers
- include Cookies, Caching, Verification, Streaming
- include SessionManagement, HttpAuthentication::Basic::ControllerMethods
- include RecordIdentifier, RequestForgeryProtection, Translation
+ [ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
+ Cookies, Caching, Verification, Streaming, SessionManagement,
+ HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
+ RequestForgeryProtection, Translation
+ ].each do |mod|
+ include mod
+ end
end
end
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 4dc76e1b49..781bc48887 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -8,6 +8,8 @@ module ActionController
# Development mode callbacks
before_dispatch :reload_application
after_dispatch :cleanup_application
+
+ ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
if defined?(ActiveRecord)
@@ -60,11 +62,10 @@ module ActionController
def dispatch
begin
run_callbacks :before_dispatch
- controller = Routing::Routes.recognize(@request)
- controller.process(@request, @response).to_a
+ Routing::Routes.call(@env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
- controller.process_with_exception(@request, @response, exception).to_a
+ controller.call_with_exception(@env, exception).to_a
else
raise exception
end
@@ -83,8 +84,7 @@ module ActionController
end
def _call(env)
- @request = Request.new(env)
- @response = Response.new
+ @env = env
dispatch
end
@@ -93,7 +93,6 @@ module ActionController
run_callbacks :prepare_dispatch
Routing::Routes.reload
- ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end
# Cleanup the application by clearing out loaded classes so they can
@@ -110,8 +109,7 @@ module ActionController
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- # TODO: This callback should have direct access to env
- return if @request.key?("rack.test")
+ return if @env.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
end
diff --git a/actionpack/lib/action_controller/flash.rb b/actionpack/lib/action_controller/flash.rb
index 9856dbed2a..56ee9c67e2 100644
--- a/actionpack/lib/action_controller/flash.rb
+++ b/actionpack/lib/action_controller/flash.rb
@@ -4,20 +4,22 @@ module ActionController #:nodoc:
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
#
- # class WeblogController < ActionController::Base
+ # class PostsController < ActionController::Base
# def create
# # save post
# flash[:notice] = "Successfully created post"
- # redirect_to :action => "display", :params => { :id => post.id }
+ # redirect_to posts_path(@post)
# end
#
- # def display
+ # def show
# # doesn't need to assign the flash notice to the template, that's done automatically
# end
# end
#
- # display.erb
- # <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
+ # show.html.erb
+ # <% if flash[:notice] %>
+ # <div class="notice"><%= flash[:notice] %></div>
+ # <% end %>
#
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb
index 402750c57d..ba65032f6a 100644
--- a/actionpack/lib/action_controller/helpers.rb
+++ b/actionpack/lib/action_controller/helpers.rb
@@ -163,9 +163,9 @@ module ActionController #:nodoc:
def helper_method(*methods)
methods.flatten.each do |method|
master_helper_module.module_eval <<-end_eval
- def #{method}(*args, &block)
- controller.send(%(#{method}), *args, &block)
- end
+ def #{method}(*args, &block) # def current_user(*args, &block)
+ controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block)
+ end # end
end_eval
end
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 71e2524e81..163ba84a3e 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -2,17 +2,6 @@ require 'stringio'
require 'uri'
require 'active_support/test_case'
-# Monkey patch Rack::Lint to support rewind
-module Rack
- class Lint
- class InputWrapper
- def rewind
- @input.rewind
- end
- end
- end
-end
-
module ActionController
module Integration #:nodoc:
# An integration Session instance represents a set of requests and responses
@@ -72,8 +61,8 @@ module ActionController
end
# Create and initialize a new Session instance.
- def initialize(app)
- @application = app
+ def initialize(app = nil)
+ @application = app || ActionController::Dispatcher.new
reset!
end
@@ -311,8 +300,10 @@ module ActionController
env[key] = value
end
- unless ActionController::Base.respond_to?(:clear_last_instantiation!)
- ActionController::Base.module_eval { include ControllerCapture }
+ [ControllerCapture, ActionController::ProcessWithTest].each do |mod|
+ unless ActionController::Base < mod
+ ActionController::Base.class_eval { include mod }
+ end
end
ActionController::Base.clear_last_instantiation!
@@ -340,6 +331,7 @@ module ActionController
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
+ @controller.send(:set_test_assigns)
else
# Decorate responses from Rack Middleware and Rails Metal
# as an Response for the purposes of integration testing
@@ -428,7 +420,7 @@ module ActionController
def multipart_body(params, boundary)
multipart_requestify(params).map do |key, value|
if value.respond_to?(:original_filename)
- File.open(value.path) do |f|
+ File.open(value.path, "rb") do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF
@@ -509,7 +501,6 @@ EOF
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session(application = nil)
- application ||= ActionController::Dispatcher.new
session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
diff --git a/actionpack/lib/action_controller/lock.rb b/actionpack/lib/action_controller/lock.rb
deleted file mode 100644
index c50762216e..0000000000
--- a/actionpack/lib/action_controller/lock.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActionController
- class Lock
- FLAG = 'rack.multithread'.freeze
-
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
- end
-
- def call(env)
- old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
- ensure
- env[FLAG] = old
- end
- end
-end
diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb
index 74f28565c0..dbc2fda41e 100644
--- a/actionpack/lib/action_controller/middleware_stack.rb
+++ b/actionpack/lib/action_controller/middleware_stack.rb
@@ -1,6 +1,14 @@
module ActionController
class MiddlewareStack < Array
class Middleware
+ def self.new(klass, *args, &block)
+ if klass.is_a?(self)
+ klass
+ else
+ super
+ end
+ end
+
attr_reader :args, :block
def initialize(klass, *args, &block)
@@ -24,6 +32,8 @@ module ActionController
else
@klass.to_s.constantize
end
+ rescue NameError
+ @klass
end
def active?
@@ -65,6 +75,24 @@ module ActionController
block.call(self) if block_given?
end
+ def insert(index, *args, &block)
+ index = self.index(index) unless index.is_a?(Integer)
+ middleware = Middleware.new(*args, &block)
+ super(index, middleware)
+ end
+
+ alias_method :insert_before, :insert
+
+ def insert_after(index, *args, &block)
+ index = self.index(index) unless index.is_a?(Integer)
+ insert(index + 1, *args, &block)
+ end
+
+ def swap(target, *args, &block)
+ insert_before(target, *args, &block)
+ delete(target)
+ end
+
def use(*args, &block)
middleware = Middleware.new(*args, &block)
push(middleware)
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
index 793739723f..f9cfc2b18e 100644
--- a/actionpack/lib/action_controller/middlewares.rb
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -1,11 +1,9 @@
-use "ActionController::Lock", :if => lambda {
+use "Rack::Lock", :if => lambda {
!ActionController::Base.allow_concurrency
}
use "ActionController::Failsafe"
-use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
-
["ActionController::Session::CookieStore",
"ActionController::Session::MemCacheStore",
"ActiveRecord::SessionStore"].each do |store|
@@ -18,4 +16,6 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
)
end
-use ActionController::VerbPiggybacking
+use "ActionController::RewindableInput"
+use "ActionController::ParamsParser"
+use "Rack::MethodOverride"
diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb
index 29294476f7..b755363873 100644
--- a/actionpack/lib/action_controller/mime_responds.rb
+++ b/actionpack/lib/action_controller/mime_responds.rb
@@ -143,12 +143,27 @@ module ActionController #:nodoc:
custom(@mime_type_priority.first, &block)
end
end
+
+ def self.generate_method_for_mime(mime)
+ sym = mime.is_a?(Symbol) ? mime : mime.to_sym
+ const = sym.to_s.upcase
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{sym}(&block) # def html(&block)
+ custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
+ end # end
+ RUBY
+ end
- def method_missing(symbol, &block)
- mime_constant = symbol.to_s.upcase
+ Mime::SET.each do |mime|
+ generate_method_for_mime(mime)
+ end
- if Mime::SET.include?(Mime.const_get(mime_constant))
- custom(Mime.const_get(mime_constant), &block)
+ def method_missing(symbol, &block)
+ mime_constant = Mime.const_get(symbol.to_s.upcase)
+
+ if Mime::SET.include?(mime_constant)
+ self.class.generate_method_for_mime(mime_constant)
+ send(symbol, &block)
else
super
end
diff --git a/actionpack/lib/action_controller/params_parser.rb b/actionpack/lib/action_controller/params_parser.rb
new file mode 100644
index 0000000000..d269fe07fa
--- /dev/null
+++ b/actionpack/lib/action_controller/params_parser.rb
@@ -0,0 +1,71 @@
+module ActionController
+ class ParamsParser
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ ActionController::Base.param_parsers[Mime::JSON] = :json
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if params = parse_formatted_parameters(env)
+ env["action_controller.request.request_parameters"] = params
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def parse_formatted_parameters(env)
+ request = Request.new(env)
+
+ return false if request.content_length.zero?
+
+ mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ return false unless strategy
+
+ case strategy
+ when Proc
+ strategy.call(request.raw_post)
+ when :xml_simple, :xml_node
+ body = request.raw_post
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(request.raw_post)
+ when :json
+ body = request.raw_post
+ if body.blank?
+ {}
+ else
+ data = ActiveSupport::JSON.decode(body)
+ data = {:_json => data} unless data.is_a?(Hash)
+ data.with_indifferent_access
+ end
+ else
+ false
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => request.raw_post,
+ "content_type" => request.content_type,
+ "content_length" => request.content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def content_type_from_legacy_post_data_format_header(env)
+ if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'
+ return Mime::YAML
+ when 'xml'
+ return Mime::XML
+ end
+ end
+
+ nil
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index dce50c6c3b..924d1aa6bd 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -118,13 +118,17 @@ module ActionController
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
- def #{action}_polymorphic_url(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
- end
-
- def #{action}_polymorphic_path(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
- end
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
EOT
end
diff --git a/actionpack/lib/action_controller/rack_ext.rb b/actionpack/lib/action_controller/rack_ext.rb
new file mode 100644
index 0000000000..2ba6654e3d
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext.rb
@@ -0,0 +1,3 @@
+require 'action_controller/rack_ext/lock'
+require 'action_controller/rack_ext/multipart'
+require 'action_controller/rack_ext/parse_query'
diff --git a/actionpack/lib/action_controller/rack_ext/lock.rb b/actionpack/lib/action_controller/rack_ext/lock.rb
new file mode 100644
index 0000000000..9bf1889065
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/lock.rb
@@ -0,0 +1,21 @@
+module Rack
+ # Rack::Lock was commited to Rack core
+ # http://github.com/rack/rack/commit/7409b0c
+ # Remove this when Rack 1.0 is released
+ unless defined? Lock
+ class Lock
+ FLAG = 'rack.multithread'.freeze
+
+ def initialize(app, lock = Mutex.new)
+ @app, @lock = app, lock
+ end
+
+ def call(env)
+ old, env[FLAG] = env[FLAG], false
+ @lock.synchronize { @app.call(env) }
+ ensure
+ env[FLAG] = old
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/multipart.rb b/actionpack/lib/action_controller/rack_ext/multipart.rb
new file mode 100644
index 0000000000..3b142307e9
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/multipart.rb
@@ -0,0 +1,22 @@
+module Rack
+ module Utils
+ module Multipart
+ class << self
+ def parse_multipart_with_rewind(env)
+ result = parse_multipart_without_rewind(env)
+
+ begin
+ env['rack.input'].rewind if env['rack.input'].respond_to?(:rewind)
+ rescue Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ end
+
+ result
+ end
+
+ alias_method_chain :parse_multipart, :rewind
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/parse_query.rb b/actionpack/lib/action_controller/rack_ext/parse_query.rb
new file mode 100644
index 0000000000..2f21a57770
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -0,0 +1,18 @@
+# Rack does not automatically cleanup Safari 2 AJAX POST body
+# This has not yet been commited to Rack, please +1 this ticket:
+# http://rack.lighthouseapp.com/projects/22435/tickets/19
+
+module Rack
+ module Utils
+ alias_method :parse_query_without_ajax_body_cleanup, :parse_query
+ module_function :parse_query_without_ajax_body_cleanup
+
+ def parse_query(qs, d = '&;')
+ qs = qs.dup
+ qs.chop! if qs[-1] == 0
+ qs.gsub!(/&_=$/, '')
+ parse_query_without_ajax_body_cleanup(qs, d)
+ end
+ module_function :parse_query
+ end
+end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index d9eb5af849..09dcd684e8 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -6,24 +6,12 @@ require 'active_support/memoizable'
require 'action_controller/cgi_ext'
module ActionController
- # CgiRequest and TestRequest provide concrete implementations.
- class Request
+ class Request < Rack::Request
extend ActiveSupport::Memoizable
- class SessionFixationAttempt < StandardError #:nodoc:
- end
-
- # The hash of environment variables for this request,
- # such as { 'RAILS_ENV' => 'production' }.
- attr_reader :env
-
- def initialize(env)
- @env = env
- end
-
- %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
+ %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
- REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
@@ -41,17 +29,18 @@ module ActionController
HTTP_METHODS = %w(get head put post delete options)
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
- # The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
+ # Returns the true HTTP request \method as a lowercase symbol, such as
+ # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
+ # constant above, an UnknownHttpMethod exception is raised.
def request_method
- method = @env['REQUEST_METHOD']
- HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
+ HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
- # The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
- # equivalent from the application's perspective.
+ # Returns the HTTP request \method used for action processing as a
+ # lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
+ # method returns <tt>:get</tt> for a HEAD request because the two are
+ # functionally equivalent from the application's perspective.)
def method
request_method == :head ? :get : request_method
end
@@ -92,16 +81,19 @@ module ActionController
# Returns the content length of the request as an integer.
def content_length
- @env['CONTENT_LENGTH'].to_i
+ super.to_i
end
- memoize :content_length
# 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.
def content_type
- Mime::Type.lookup(content_type_without_parameters)
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
end
memoize :content_type
@@ -286,7 +278,7 @@ EOM
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
- env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
@@ -389,20 +381,21 @@ EOM
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- unless env.include? 'RAW_POST_DATA'
- env['RAW_POST_DATA'] = body.read(content_length)
+ unless @env.include? 'RAW_POST_DATA'
+ @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
body.rewind if body.respond_to?(:rewind)
end
- env['RAW_POST_DATA']
+ @env['RAW_POST_DATA']
end
# Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
+ alias_method :params, :parameters
def path_parameters=(parameters) #:nodoc:
- @path_parameters = parameters
+ @env["rack.routing_args"] = parameters
@symbolized_path_parameters = @parameters = nil
end
@@ -418,51 +411,46 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
- @path_parameters ||= {}
+ @env["rack.routing_args"] ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
- if raw_post = env['RAW_POST_DATA']
+ if raw_post = @env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
- body_stream
+ @env['rack.input']
end
end
- def remote_addr
- @env['REMOTE_ADDR']
+ def form_data?
+ FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
- def referrer
- @env['HTTP_REFERER']
+ # Override Rack's GET method to support nested query strings
+ def GET
+ @env["action_controller.request.query_parameters"] ||= UrlEncodedPairParser.parse_query_parameters(query_string)
end
- alias referer referrer
+ alias_method :query_parameters, :GET
- def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
- end
-
- def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
+ # Override Rack's POST method to support nested query strings
+ def POST
+ @env["action_controller.request.request_parameters"] ||= UrlEncodedPairParser.parse_hash_parameters(super)
end
+ alias_method :request_parameters, :POST
def body_stream #:nodoc:
@env['rack.input']
end
- def cookies
- Rack::Request.new(@env).cookies
- end
-
def session
@env['rack.session'] ||= {}
end
def session=(session) #:nodoc:
- @session = session
+ @env['rack.session'] = session
end
def reset_session
@@ -481,411 +469,9 @@ EOM
@env['SERVER_PORT'].to_i
end
- protected
- # The raw content type string. Use when you need parameters such as
- # charset or boundary which aren't included in the content_type MIME type.
- # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
- def content_type_with_parameters
- content_type_from_legacy_post_data_format_header ||
- env['CONTENT_TYPE'].to_s
- end
-
- # The raw content type string with its parameters stripped off.
- def content_type_without_parameters
- self.class.extract_content_type_without_parameters(content_type_with_parameters)
- end
- memoize :content_type_without_parameters
-
private
- def content_type_from_legacy_post_data_format_header
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml'; 'application/x-yaml'
- when 'xml'; 'application/xml'
- end
- end
- end
-
- def parse_formatted_request_parameters
- return {} if content_length.zero?
-
- content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
-
- # Don't parse params for unknown requests.
- return {} if content_type.blank?
-
- mime_type = Mime::Type.lookup(content_type)
- strategy = ActionController::Base.param_parsers[mime_type]
-
- # Only multipart form parsing expects a stream.
- body = (strategy && strategy != :multipart_form) ? raw_post : self.body
-
- case strategy
- when Proc
- strategy.call(body)
- when :url_encoded_form
- self.class.clean_up_ajax_request_body! body
- self.class.parse_query_parameters(body)
- when :multipart_form
- self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
- when :xml_simple, :xml_node
- body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
- when :yaml
- YAML.load(body)
- when :json
- if body.blank?
- {}
- else
- data = ActiveSupport::JSON.decode(body)
- data = {:_json => data} unless data.is_a?(Hash)
- data.with_indifferent_access
- end
- else
- {}
- end
- rescue Exception => e # YAML, XML or Ruby code block errors
- raise
- { "body" => body,
- "content_type" => content_type_with_parameters,
- "content_length" => content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
- end
-
def named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
-
- class << self
- def parse_query_parameters(query_string)
- return {} if query_string.blank?
-
- pairs = query_string.split('&').collect do |chunk|
- next if chunk.empty?
- key, value = chunk.split('=', 2)
- next if key.empty?
- value = value.nil? ? nil : CGI.unescape(value)
- [ CGI.unescape(key), value ]
- end.compact
-
- UrlEncodedPairParser.new(pairs).result
- end
-
- def parse_request_parameters(params)
- parser = UrlEncodedPairParser.new
-
- params = params.dup
- until params.empty?
- for key, value in params
- if key.blank?
- params.delete key
- elsif !key.include?('[')
- # much faster to test for the most common case first (GET)
- # and avoid the call to build_deep_hash
- parser.result[key] = get_typed_value(value[0])
- params.delete key
- elsif value.is_a?(Array)
- parser.parse(key, get_typed_value(value.shift))
- params.delete key if value.empty?
- else
- raise TypeError, "Expected array, found #{value.inspect}"
- end
- end
- end
-
- parser.result
- end
-
- def parse_multipart_form_parameters(body, boundary, body_size, env)
- parse_request_parameters(read_multipart(body, boundary, body_size, env))
- end
-
- def extract_multipart_boundary(content_type_with_parameters)
- if content_type_with_parameters =~ MULTIPART_BOUNDARY
- ['multipart/form-data', $1.dup]
- else
- extract_content_type_without_parameters(content_type_with_parameters)
- end
- end
-
- def extract_content_type_without_parameters(content_type_with_parameters)
- $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
- end
-
- def clean_up_ajax_request_body!(body)
- body.chop! if body[-1] == 0
- body.gsub!(/&_=$/, '')
- end
-
-
- private
- def get_typed_value(value)
- case value
- when String
- value
- when NilClass
- ''
- when Array
- value.map { |v| get_typed_value(v) }
- else
- if value.respond_to? :original_filename
- # Uploaded file
- if value.original_filename
- value
- # Multipart param
- else
- result = value.read
- value.rewind
- result
- end
- # Unknown value, neither string nor multipart.
- else
- raise "Unknown form value: #{value.inspect}"
- end
- end
- end
-
- MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
-
- EOL = "\015\012"
-
- def read_multipart(body, boundary, body_size, env)
- params = Hash.new([])
- boundary = "--" + boundary
- quoted_boundary = Regexp.quote(boundary)
- buf = ""
- bufsize = 10 * 1024
- boundary_end=""
-
- # start multipart/form-data
- body.binmode if defined? body.binmode
- case body
- when File
- body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
- when StringIO
- body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
- end
- boundary_size = boundary.size + EOL.size
- body_size -= boundary_size
- status = body.read(boundary_size)
- if nil == status
- raise EOFError, "no content body"
- elsif boundary + EOL != status
- raise EOFError, "bad content body"
- end
-
- loop do
- head = nil
- content =
- if 10240 < body_size
- UploadedTempfile.new("CGI")
- else
- UploadedStringIO.new
- end
- content.binmode if defined? content.binmode
-
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
-
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
- head = $1.dup
- ""
- end
- next
- end
-
- if head and ( (EOL + boundary + EOL).size < buf.size )
- content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
- end
-
- c = if bufsize < body_size
- body.read(bufsize)
- else
- body.read(body_size)
- end
- if c.nil? || c.empty?
- raise EOFError, "bad content body"
- end
- buf.concat(c)
- body_size -= c.size
- end
-
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
- content.print $1
- if "--" == $2
- body_size = -1
- end
- boundary_end = $2.dup
- ""
- end
-
- content.rewind
-
- head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
- if filename = $1 || $2
- if /Mac/ni.match(env['HTTP_USER_AGENT']) and
- /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
- (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
- filename = CGI.unescape(filename)
- end
- content.original_path = filename.dup
- end
-
- head =~ /Content-Type: ([^\r]*)/ni
- content.content_type = $1.dup if $1
-
- head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
- name = $1.dup if $1
-
- if params.has_key?(name)
- params[name].push(content)
- else
- params[name] = [content]
- end
- break if body_size == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
-
- begin
- body.rewind if body.respond_to?(:rewind)
- rescue Errno::ESPIPE
- # Handles exceptions raised by input streams that cannot be rewound
- # such as when using plain CGI under Apache
- end
-
- params
- end
- end
- end
-
- class UrlEncodedPairParser < StringScanner #:nodoc:
- attr_reader :top, :parent, :result
-
- def initialize(pairs = [])
- super('')
- @result = {}
- pairs.each { |key, value| parse(key, value) }
- end
-
- KEY_REGEXP = %r{([^\[\]=&]+)}
- BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
-
- # Parse the query string
- def parse(key, value)
- self.string = key
- @top, @parent = result, nil
-
- # First scan the bare key
- key = scan(KEY_REGEXP) or return
- key = post_key_check(key)
-
- # Then scan as many nestings as present
- until eos?
- r = scan(BRACKETED_KEY_REGEXP) or return
- key = self[1]
- key = post_key_check(key)
- end
-
- bind(key, value)
- end
-
- private
- # After we see a key, we must look ahead to determine our next action. Cases:
- #
- # [] follows the key. Then the value must be an array.
- # = follows the key. (A value comes next)
- # & or the end of string follows the key. Then the key is a flag.
- # otherwise, a hash follows the key.
- def post_key_check(key)
- if scan(/\[\]/) # a[b][] indicates that b is an array
- container(key, Array)
- nil
- elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
- container(key, Hash)
- nil
- else # End of key? We do nothing.
- key
- end
- end
-
- # Add a container to the stack.
- def container(key, klass)
- type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
- value = bind(key, klass.new)
- type_conflict! klass, value unless value.is_a?(klass)
- push(value)
- end
-
- # Push a value onto the 'stack', which is actually only the top 2 items.
- def push(value)
- @parent, @top = @top, value
- end
-
- # Bind a key (which may be nil for items in an array) to the provided value.
- def bind(key, value)
- if top.is_a? Array
- if key
- if top[-1].is_a?(Hash) && ! top[-1].key?(key)
- top[-1][key] = value
- else
- top << {key => value}.with_indifferent_access
- push top.last
- value = top[key]
- end
- else
- top << value
- end
- elsif top.is_a? Hash
- key = CGI.unescape(key)
- parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
- top[key] ||= value
- return top[key]
- else
- raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
- end
-
- return value
- end
-
- def type_conflict!(klass, value)
- raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
- end
- end
-
- module UploadedFile
- def self.included(base)
- base.class_eval do
- attr_accessor :original_path, :content_type
- alias_method :local_path, :path
- end
- end
-
- # Take the basename of the upload's original filename.
- # This handles the full Windows paths given by Internet Explorer
- # (and perhaps other broken user agents) without affecting
- # those which give the lone filename.
- # The Windows regexp is adapted from Perl's File::Basename.
- def original_filename
- unless defined? @original_filename
- @original_filename =
- unless original_path.blank?
- if original_path =~ /^(?:.*[:\\\/])?(.*)/m
- $1
- else
- File.basename original_path
- end
- end
- end
- @original_filename
- end
- end
-
- class UploadedStringIO < StringIO
- include UploadedFile
- end
-
- class UploadedTempfile < Tempfile
- include UploadedFile
end
end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
deleted file mode 100644
index 80cd55334f..0000000000
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ /dev/null
@@ -1,168 +0,0 @@
-require 'optparse'
-
-module ActionController
- class RequestProfiler
- # Wrap up the integration session runner.
- class Sandbox
- include Integration::Runner
-
- def self.benchmark(n, script)
- new(script).benchmark(n)
- end
-
- def initialize(script_path)
- @quiet = false
- define_run_method(script_path)
- reset!
- end
-
- def benchmark(n, profiling = false)
- @quiet = true
- print ' '
-
- ms = Benchmark.ms do
- n.times do |i|
- run(profiling)
- print_progress(i)
- end
- end
-
- puts
- ms
- ensure
- @quiet = false
- end
-
- def say(message)
- puts " #{message}" unless @quiet
- end
-
- private
- def define_run_method(script_path)
- script = File.read(script_path)
-
- source = <<-end_source
- def run(profiling = false)
- if profiling
- RubyProf.resume do
- #{script}
- end
- else
- #{script}
- end
-
- old_request_count = request_count
- reset!
- self.request_count = old_request_count
- end
- end_source
-
- instance_eval source, script_path, 1
- end
-
- def print_progress(i)
- print "\n " if i % 60 == 0
- print ' ' if i % 10 == 0
- print '.'
- $stdout.flush
- end
- end
-
-
- attr_reader :options
-
- def initialize(options = {})
- @options = default_options.merge(options)
- end
-
-
- def self.run(args = nil, options = {})
- profiler = new(options)
- profiler.parse_options(args) if args
- profiler.run
- end
-
- def run
- sandbox = Sandbox.new(options[:script])
-
- puts 'Warming up once'
-
- elapsed = warmup(sandbox)
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, sandbox.request_count, 1000 * sandbox.request_count / elapsed]
- puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
-
- options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
- end
-
- def profile(sandbox)
- load_ruby_prof
-
- benchmark(sandbox, true)
- results = RubyProf.stop
-
- show_profile_results results
- results
- end
-
- def benchmark(sandbox, profiling = false)
- sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n], profiling)
- count = sandbox.request_count.to_i
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, count, 1000 * count / elapsed]
- end
-
- def warmup(sandbox)
- Benchmark.ms { sandbox.run(false) }
- end
-
- def default_options
- { :n => 100, :open => 'open %s &' }
- end
-
- # Parse command-line options
- def parse_options(args)
- OptionParser.new do |opt|
- opt.banner = "USAGE: #{$0} [options] [session script path]"
-
- opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
- opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
- opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
- opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
- opt.on('-h', '--help', 'Show this help') { puts opt; exit }
-
- opt.parse args
-
- if args.empty?
- puts opt
- exit
- end
- options[:script] = args.pop
- end
- end
-
- protected
- def load_ruby_prof
- begin
- gem 'ruby-prof', '>= 0.6.1'
- require 'ruby-prof'
- if mode = options[:measure]
- RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
- end
- rescue LoadError
- abort '`gem install ruby-prof` to use the profiler'
- end
- end
-
- def show_profile_results(results)
- File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
- RubyProf::GraphHtmlPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
-
- File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
- RubyProf::FlatPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 5ef79a36ce..4b7d1e32fd 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
'ActionView::TemplateError' => 'template_error'
}
- RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
- File.join(File.dirname(__FILE__), "templates"), true)
+ RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new(
+ File.join(File.dirname(__FILE__), "templates"))
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
@@ -59,7 +59,9 @@ module ActionController #:nodoc:
end
module ClassMethods
- def process_with_exception(request, response, exception) #:nodoc:
+ def call_with_exception(env, exception) #:nodoc:
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
new.process(request, response, :rescue_action, exception)
end
end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 64319fe102..27860a6207 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -231,10 +231,13 @@ module ActionController # :nodoc:
# Don't set the Content-Length for block-based bodies as that would mean
# reading it all into memory. Not nice for, say, a 2GB streaming file.
def set_content_length!
- unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304')
- self.headers["Content-Length"] ||= body.size
+ if status && status.to_s[0..2] == '204'
+ headers.delete('Content-Length')
+ elsif length = headers['Content-Length']
+ headers['Content-Length'] = length.to_s
+ elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
+ headers["Content-Length"] = body.size.to_s
end
- headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
end
def convert_language!
diff --git a/actionpack/lib/action_controller/rewindable_input.rb b/actionpack/lib/action_controller/rewindable_input.rb
new file mode 100644
index 0000000000..36f655c51e
--- /dev/null
+++ b/actionpack/lib/action_controller/rewindable_input.rb
@@ -0,0 +1,28 @@
+module ActionController
+ class RewindableInput
+ class RewindableIO < ActiveSupport::BasicObject
+ def initialize(io)
+ @io = io
+ @rewindable = io.is_a?(StringIO)
+ end
+
+ def method_missing(method, *args, &block)
+ unless @rewindable
+ @io = StringIO.new(@io.read)
+ @rewindable = true
+ end
+
+ @io.__send__(method, *args, &block)
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env['rack.input'] = RewindableIO.new(env['rack.input'])
+ @app.call(env)
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index da9b56fdf9..a2141a77dc 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -193,9 +193,8 @@ module ActionController
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
#
- # will glob all remaining parts of the route that were not recognized earlier. This idiom
- # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
- # this case.
+ # will glob all remaining parts of the route that were not recognized earlier.
+ # The globbed values are in <tt>params[:path]</tt> as an array of path segments.
#
# == Route conditions
#
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 13646aef61..044ace7de1 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -145,10 +145,10 @@ module ActionController
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(options = nil)
- options ? #{options.inspect}.merge(options) : #{options.inspect}
- end
- protected :#{selector}
+ def #{selector}(options = nil) # def hash_for_users_url(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
+ end # end
+ protected :#{selector} # protected :hash_for_users_url
end_eval
helpers << selector
end
@@ -173,32 +173,33 @@ module ActionController
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args)
-
- #{generate_optimisation_block(route, kind)}
-
- opts = if args.empty? || Hash === args.first
- args.first || {}
- else
- options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
- h[k] = v
- h
- end
- options.merge(args)
- end
-
- url_for(#{hash_access_method}(opts))
-
- end
- #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args)
- ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. please pass format to the standard" +
- "#{selector}() method instead.", caller)
- #{selector}(*args)
- end
- protected :#{selector}
+ def #{selector}(*args) # def users_url(*args)
+ #
+ #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
+ #
+ opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
+ args.first || {} # args.first || {}
+ else # else
+ options = args.extract_options! # options = args.extract_options!
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
+ h[k] = v # h[k] = v
+ h # h
+ end # end
+ options.merge(args) # options.merge(args)
+ end # end
+ #
+ url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
+ #
+ end # end
+ #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
+ def formatted_#{selector}(*args) # def formatted_users_url(*args)
+ ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
+ "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
+ "Please pass format to the standard " + # "Please pass format to the standard " +
+ "#{selector} method instead.", caller) # "users_url method instead.", caller)
+ #{selector}(*args) # users_url(*args)
+ end # end
+ protected :#{selector} # protected :users_url
end_eval
helpers << selector
end
@@ -426,6 +427,12 @@ module ActionController
end
end
+ def call(env)
+ request = Request.new(env)
+ app = Routing::Routes.recognize(request)
+ app.call(env).to_a
+ end
+
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index 135bedaf50..6ad6369950 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -45,7 +45,7 @@ module ActionController
:domain => nil,
:path => "/",
:expire_after => nil,
- :httponly => false
+ :httponly => true
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,8 +56,6 @@ module ActionController
class CookieOverflow < StandardError; end
def initialize(app, options = {})
- options = options.dup
-
# Process legacy CGI options
options = options.symbolize_keys
if options.has_key?(:session_path)
@@ -163,9 +161,9 @@ module ActionController
def ensure_session_key(key)
if key.blank?
- raise ArgumentError, 'A session_key is required to write a ' +
+ raise ArgumentError, 'A key is required to write a ' +
'cookie containing the session data. Use ' +
- 'config.action_controller.session = { :session_key => ' +
+ 'config.action_controller.session = { :key => ' +
'"_myapp_session", :secret => "some secret phrase" } in ' +
'config/environment.rb'
end
@@ -181,7 +179,7 @@ module ActionController
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
- "config.action_controller.session = { :session_key => " +
+ "config.action_controller.session = { :key => " +
"\"_myapp_session\", :secret => \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\" } " +
"in config/environment.rb"
diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb
index f06a0da75c..b556f04649 100644
--- a/actionpack/lib/action_controller/session_management.rb
+++ b/actionpack/lib/action_controller/session_management.rb
@@ -37,7 +37,7 @@ module ActionController #:nodoc:
# Returns the hash used to configure the session. Example use:
#
- # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
+ # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
def session_options
@session_options ||= {}
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7ed1a3e160..0b0d0c799b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,4 +1,5 @@
require 'active_support/test_case'
+require 'action_controller/test_process'
module ActionController
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -102,6 +103,8 @@ module ActionController
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
+ include TestProcess
+
module Assertions
%w(response selector tag dom routing model).each do |kind|
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
@@ -124,17 +127,18 @@ module ActionController
#
# The exception is stored in the exception accessor for further inspection.
module RaiseActionExceptions
- attr_accessor :exception
+ protected
+ attr_accessor :exception
- def rescue_action_without_handler(e)
- self.exception = e
-
- if request.remote_addr == "0.0.0.0"
- raise(e)
- else
- super(e)
+ def rescue_action_without_handler(e)
+ self.exception = e
+
+ if request.remote_addr == "0.0.0.0"
+ raise(e)
+ else
+ super(e)
+ end
end
- end
end
setup :setup_controller_request_and_response
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 211e22ff58..22b97fc157 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -1,46 +1,17 @@
-require 'action_controller/test_case'
-
module ActionController #:nodoc:
- class Base
- attr_reader :assigns
-
- # Process a test request called with a TestRequest object.
- def self.process_test(request)
- new.process_test(request)
- end
-
- def process_test(request) #:nodoc:
- process(request, TestResponse.new)
- end
-
- def process_with_test(*args)
- returning process_without_test(*args) do
- @assigns = {}
- (instance_variable_names - @@protected_instance_variables).each do |var|
- value = instance_variable_get(var)
- @assigns[var[1..-1]] = value
- response.template.assigns[var[1..-1]] = value if response
- end
- end
- end
-
- alias_method_chain :process, :test
- end
-
class TestRequest < Request #:nodoc:
attr_accessor :cookies, :session_options
- attr_accessor :query_parameters, :request_parameters, :path, :session
- attr_accessor :host, :user_agent
+ attr_accessor :query_parameters, :path, :session
+ attr_accessor :host
def initialize
- super(Rack::MockRequest.env_for('/'))
+ super(Rack::MockRequest.env_for("/"))
@query_parameters = {}
- @request_parameters = {}
@session = TestSession.new
- initialize_containers
initialize_default_values
+ initialize_containers
end
def reset_session
@@ -55,7 +26,11 @@ module ActionController #:nodoc:
# Either the RAW_POST_DATA environment variable or the URL-encoded request
# parameters.
def raw_post
- env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) }
+ @env['RAW_POST_DATA'] ||= begin
+ data = url_encoded_request_parameters
+ data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
+ data
+ end
end
def port=(number)
@@ -125,26 +100,30 @@ module ActionController #:nodoc:
path_parameters[key.to_s] = value
end
end
+ raw_post # populate env['RAW_POST_DATA']
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
def recycle!
- self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
unmemoize_all
end
+ def user_agent=(user_agent)
+ @env['HTTP_USER_AGENT'] = user_agent
+ end
+
private
def initialize_containers
- @env, @cookies = {}, {}
+ @cookies = {}
end
def initialize_default_values
@host = "test.host"
@request_uri = "/"
- @user_agent = "Rails Testing"
- self.remote_addr = "0.0.0.0"
+ @env['HTTP_USER_AGENT'] = "Rails Testing"
+ @env['REMOTE_ADDR'] = "0.0.0.0"
@env["SERVER_PORT"] = 80
@env['REQUEST_METHOD'] = "GET"
end
@@ -377,20 +356,33 @@ module ActionController #:nodoc:
module TestProcess
def self.included(base)
- # execute the request simulating a specific HTTP method and set/volley the response
- # TODO: this should be un-DRY'ed for the sake of API documentation.
- %w( get post put delete head ).each do |method|
- base.class_eval <<-EOV, __FILE__, __LINE__
- def #{method}(action, parameters = nil, session = nil, flash = nil)
- @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
- process(action, parameters, session, flash)
- end
- EOV
+ # Executes a request simulating GET HTTP method and set/volley the response
+ def get(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "GET")
+ end
+
+ # Executes a request simulating POST HTTP method and set/volley the response
+ def post(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "POST")
+ end
+
+ # Executes a request simulating PUT HTTP method and set/volley the response
+ def put(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "PUT")
+ end
+
+ # Executes a request simulating DELETE HTTP method and set/volley the response
+ def delete(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "DELETE")
+ end
+
+ # Executes a request simulating HEAD HTTP method and set/volley the response
+ def head(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "HEAD")
end
end
- # execute the request and set/volley the response
- def process(action, parameters = nil, session = nil, flash = nil)
+ def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@controller @request @response).each do |iv_name|
@@ -403,7 +395,7 @@ module ActionController #:nodoc:
@response.recycle!
@html_document = nil
- @request.env['REQUEST_METHOD'] ||= "GET"
+ @request.env['REQUEST_METHOD'] = http_method
@request.action = action.to_s
@@ -413,7 +405,9 @@ module ActionController #:nodoc:
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
build_request_uri(action, parameters)
- @controller.process(@request, @response)
+
+ Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
+ @controller.process_with_test(@request, @response)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -490,7 +484,8 @@ module ActionController #:nodoc:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
- ActionController::TestUploadedFile.new("#{ActionController::TestCase.try(:fixture_path)}#{path}", mime_type, binary)
+ fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
+ ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
end
# A helper to make it easier to test different route configurations.
@@ -525,12 +520,24 @@ module ActionController #:nodoc:
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
end
end
-end
-module Test
- module Unit
- class TestCase #:nodoc:
- include ActionController::TestProcess
+ module ProcessWithTest #:nodoc:
+ def self.included(base)
+ base.class_eval { attr_reader :assigns }
end
+
+ def process_with_test(*args)
+ process(*args).tap { set_test_assigns }
+ end
+
+ private
+ def set_test_assigns
+ @assigns = {}
+ (instance_variable_names - self.class.protected_instance_variables).each do |var|
+ name, value = var[1..-1], instance_variable_get(var)
+ @assigns[name] = value
+ response.template.assigns[name] = value if response
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb
new file mode 100644
index 0000000000..376ba3621a
--- /dev/null
+++ b/actionpack/lib/action_controller/uploaded_file.rb
@@ -0,0 +1,44 @@
+module ActionController
+ module UploadedFile
+ def self.included(base)
+ base.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ def self.extended(object)
+ object.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ unless defined? @original_filename
+ @original_filename =
+ unless original_path.blank?
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
+ $1
+ else
+ File.basename original_path
+ end
+ end
+ end
+ @original_filename
+ end
+ end
+
+ class UploadedStringIO < StringIO
+ include UploadedFile
+ end
+
+ class UploadedTempfile < Tempfile
+ include UploadedFile
+ end
+end
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
new file mode 100644
index 0000000000..57594c4259
--- /dev/null
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -0,0 +1,155 @@
+module ActionController
+ class UrlEncodedPairParser < StringScanner #:nodoc:
+ class << self
+ def parse_query_parameters(query_string)
+ return {} if query_string.blank?
+
+ pairs = query_string.split('&').collect do |chunk|
+ next if chunk.empty?
+ key, value = chunk.split('=', 2)
+ next if key.empty?
+ value = value.nil? ? nil : CGI.unescape(value)
+ [ CGI.unescape(key), value ]
+ end.compact
+
+ new(pairs).result
+ end
+
+ def parse_hash_parameters(params)
+ parser = new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete(key)
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete(key) if value.empty?
+ else
+ parser.parse(key, get_typed_value(value))
+ params.delete(key)
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ when Hash
+ if value.has_key?(:tempfile) && value[:filename].any?
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ nil
+ end
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
+ attr_reader :top, :parent, :result
+
+ def initialize(pairs = [])
+ super('')
+ @result = {}
+ pairs.each { |key, value| parse(key, value) }
+ end
+
+ KEY_REGEXP = %r{([^\[\]=&]+)}
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
+
+ # Parse the query string
+ def parse(key, value)
+ self.string = key
+ @top, @parent = result, nil
+
+ # First scan the bare key
+ key = scan(KEY_REGEXP) or return
+ key = post_key_check(key)
+
+ # Then scan as many nestings as present
+ until eos?
+ r = scan(BRACKETED_KEY_REGEXP) or return
+ key = self[1]
+ key = post_key_check(key)
+ end
+
+ bind(key, value)
+ end
+
+ private
+ # After we see a key, we must look ahead to determine our next action. Cases:
+ #
+ # [] follows the key. Then the value must be an array.
+ # = follows the key. (A value comes next)
+ # & or the end of string follows the key. Then the key is a flag.
+ # otherwise, a hash follows the key.
+ def post_key_check(key)
+ if scan(/\[\]/) # a[b][] indicates that b is an array
+ container(key, Array)
+ nil
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
+ container(key, Hash)
+ nil
+ else # End of key? We do nothing.
+ key
+ end
+ end
+
+ # Add a container to the stack.
+ def container(key, klass)
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
+ value = bind(key, klass.new)
+ type_conflict! klass, value unless value.is_a?(klass)
+ push(value)
+ end
+
+ # Push a value onto the 'stack', which is actually only the top 2 items.
+ def push(value)
+ @parent, @top = @top, value
+ end
+
+ # Bind a key (which may be nil for items in an array) to the provided value.
+ def bind(key, value)
+ if top.is_a? Array
+ if key
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
+ top[-1][key] = value
+ else
+ top << {key => value}.with_indifferent_access
+ end
+ push top.last
+ return top[key]
+ else
+ top << value
+ return value
+ end
+ elsif top.is_a? Hash
+ key = CGI.unescape(key)
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
+ top[key] ||= value
+ return top[key]
+ else
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
+ end
+ end
+
+ def type_conflict!(klass, value)
+ raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/verb_piggybacking.rb b/actionpack/lib/action_controller/verb_piggybacking.rb
deleted file mode 100644
index 86cde304a0..0000000000
--- a/actionpack/lib/action_controller/verb_piggybacking.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActionController
- # TODO: Use Rack::MethodOverride when it is released
- class VerbPiggybacking
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- if env["REQUEST_METHOD"] == "POST"
- req = Request.new(env)
- if method = (req.parameters[:_method] || env["HTTP_X_HTTP_METHOD_OVERRIDE"])
- method = method.to_s.upcase
- if HTTP_METHODS.include?(method)
- env["REQUEST_METHOD"] = method
- end
- end
- end
-
- @app.call(env)
- end
- end
-end