From ec94c2550dae463e646a18316bfcdaded9d140c9 Mon Sep 17 00:00:00 2001 From: Sava Chankov Date: Sat, 1 Aug 2009 19:38:05 -0700 Subject: Ruby 1.9: fix Content-Length for multibyte send_data streaming [#2661 state:resolved] Signed-off-by: Jeremy Kemper --- actionpack/lib/action_controller/base/streaming.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb index 9ff4f25f43..f52810ff3a 100644 --- a/actionpack/lib/action_controller/base/streaming.rb +++ b/actionpack/lib/action_controller/base/streaming.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/bytesize' + module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. @@ -142,7 +144,7 @@ module ActionController #:nodoc: # instead. See ActionController::Base#render for more information. def send_data(data, options = {}) #:doc: logger.info "Sending data #{options[:filename]}" if logger - send_file_headers! options.merge(:length => data.size) + send_file_headers! options.merge(:length => data.bytesize) @performed_render = false render :status => options[:status], :text => data end -- cgit v1.2.3 From 503ce1d01ce6c8eee9818f4e76a9f880bb1a291d Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 30 Jul 2009 21:00:39 -0700 Subject: Update cache_control to be a Hash of options that is used to build the header. * Significantly simplifies setting and modifying cache control in other areas --- .../lib/action_controller/base/conditional_get.rb | 23 ++++------------------ actionpack/lib/action_controller/base/streaming.rb | 2 +- .../lib/action_controller/testing/process.rb | 2 +- 3 files changed, 6 insertions(+), 21 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/base/conditional_get.rb b/actionpack/lib/action_controller/base/conditional_get.rb index d287ec4994..6d35137428 100644 --- a/actionpack/lib/action_controller/base/conditional_get.rb +++ b/actionpack/lib/action_controller/base/conditional_get.rb @@ -29,11 +29,7 @@ module ActionController response.last_modified = options[:last_modified] if options[:last_modified] if options[:public] - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } - cache_control.delete("private") - cache_control.delete("no-cache") - cache_control << "public" - response.headers["Cache-Control"] = cache_control.join(', ') + response.cache_control[:public] = true end if request.fresh?(response) @@ -107,21 +103,10 @@ module ActionController # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. def expires_in(seconds, options = {}) #:doc: - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public)) + options.delete(:private) - cache_control << "max-age=#{seconds}" - cache_control.delete("no-cache") - if options[:public] - cache_control.delete("private") - cache_control << "public" - else - cache_control << "private" - end - - # This allows for additional headers to be passed through like 'max-stale' => 5.hours - cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} - - response.headers["Cache-Control"] = cache_control.join(', ') + response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} end # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb index f52810ff3a..f0317c6e99 100644 --- a/actionpack/lib/action_controller/base/streaming.rb +++ b/actionpack/lib/action_controller/base/streaming.rb @@ -181,7 +181,7 @@ module ActionController #:nodoc: # after it displays the "open/save" dialog, which means that if you # hit "open" the file isn't there anymore when the application that # is called for handling the download is run, so let's workaround that - headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache' + response.cache_control[:public] ||= false end end end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index e7c64d0942..d32d5562e8 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -52,7 +52,7 @@ module ActionController #:nodoc: class TestResponse < ActionDispatch::TestResponse def recycle! @status = 200 - @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS) + @header = Rack::Utils::HeaderHash.new @writer = lambda { |x| @body << x } @block = nil @length = 0 -- cgit v1.2.3 From f0945409d935cdd3cb783a728d68414e7ca02dfc Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 19:43:28 -0300 Subject: replace _render_*_from_controller with render_* as they are intended to be public --- actionpack/lib/action_controller/abstract/renderer.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb index 41b7d47458..f3e3903a3b 100644 --- a/actionpack/lib/action_controller/abstract/renderer.rb +++ b/actionpack/lib/action_controller/abstract/renderer.rb @@ -19,11 +19,11 @@ module AbstractController # The view class must have the following methods: # View.for_controller[controller] Create a new ActionView instance for a # controller - # View#_render_partial_from_controller[options] + # View#render_partial[options] # - responsible for setting options[:_template] # - Returns String with the rendered partial # options:: see _render_partial in ActionView::Base - # View#_render_template_from_controller[template, layout, options, partial] + # View#render_template[template, layout, options, partial] # - Returns String with the rendered template # template:: The template to render # layout:: The layout to render around the template @@ -55,7 +55,7 @@ module AbstractController def render_to_body(options = {}) # TODO: Refactor so we can just use the normal template logic for this if options[:_partial_object] - _action_view._render_partial_from_controller(options) + _action_view.render_partial(options) else _determine_template(options) _render_template(options) @@ -77,7 +77,7 @@ module AbstractController # _layout:: The layout to wrap the template in (optional) # _partial:: Whether or not the template to be rendered is a partial def _render_template(options) - _action_view._render_template_from_controller(options[:_template], options[:_layout], options, options[:_partial]) + _action_view.render_template(options[:_template], options[:_layout], options, options[:_partial]) end # The list of view paths for this controller. See ActionView::ViewPathSet for -- cgit v1.2.3 From af375a5eb3aba149590be1636480e1c3976c124f Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 19:45:40 -0300 Subject: Replace _action_view with view_context to reflect that it is public and that it does not need to be an ActionView instance --- actionpack/lib/action_controller/abstract/renderer.rb | 8 ++++---- actionpack/lib/action_controller/base/compatibility.rb | 4 ++-- actionpack/lib/action_controller/base/render_options.rb | 2 +- actionpack/lib/action_controller/caching/actions.rb | 3 +-- 4 files changed, 8 insertions(+), 9 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb index f3e3903a3b..fe556281ab 100644 --- a/actionpack/lib/action_controller/abstract/renderer.rb +++ b/actionpack/lib/action_controller/abstract/renderer.rb @@ -31,8 +31,8 @@ module AbstractController # partial:: Whether or not the template to render is a partial # # Override this method in a to change the default behavior. - def _action_view - @_action_view ||= ActionView::Base.for_controller(self) + def view_context + @_view_context ||= ActionView::Base.for_controller(self) end # Mostly abstracts the fact that calling render twice is a DoubleRenderError. @@ -55,7 +55,7 @@ module AbstractController def render_to_body(options = {}) # TODO: Refactor so we can just use the normal template logic for this if options[:_partial_object] - _action_view.render_partial(options) + view_context.render_partial(options) else _determine_template(options) _render_template(options) @@ -77,7 +77,7 @@ module AbstractController # _layout:: The layout to wrap the template in (optional) # _partial:: Whether or not the template to be rendered is a partial def _render_template(options) - _action_view.render_template(options[:_template], options[:_layout], options, options[:_partial]) + view_context.render_template(options[:_template], options[:_layout], options, options[:_partial]) end # The list of view paths for this controller. See ActionView::ViewPathSet for diff --git a/actionpack/lib/action_controller/base/compatibility.rb b/actionpack/lib/action_controller/base/compatibility.rb index 13813ffd17..23e7b1b3af 100644 --- a/actionpack/lib/action_controller/base/compatibility.rb +++ b/actionpack/lib/action_controller/base/compatibility.rb @@ -72,7 +72,7 @@ module ActionController # TODO: Remove this after we flip def template - @template ||= _action_view + @template ||= view_context end def process_action(*) @@ -141,7 +141,7 @@ module ActionController end def view_paths - _action_view.view_paths + view_context.view_paths end end end diff --git a/actionpack/lib/action_controller/base/render_options.rb b/actionpack/lib/action_controller/base/render_options.rb index fc9a02626f..65ee09883e 100644 --- a/actionpack/lib/action_controller/base/render_options.rb +++ b/actionpack/lib/action_controller/base/render_options.rb @@ -85,7 +85,7 @@ module ActionController register_renderer :update def _render_update(proc, options) - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(_action_view, &proc) + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) self.content_type = Mime::JS self.response_body = generator.to_s end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index d8a1662acc..cb0c3a1384 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -129,8 +129,7 @@ module ActionController #:nodoc: end def content_for_layout(controller) - # TODO: Remove this when new base is merged in - template = controller.respond_to?(:template) ? controller.template : controller._action_view + template = controller.view_context template.layout && template.instance_variable_get('@cached_content_for_layout') end end -- cgit v1.2.3 From 52798fd479d4acbf823d093b03bdd1acf8e86b62 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 19:50:22 -0300 Subject: rename ActionController::Http to ActionController::Metal at Josh's suggestion --- actionpack/lib/action_controller/abstract/base.rb | 2 +- actionpack/lib/action_controller/base/base.rb | 2 +- actionpack/lib/action_controller/base/http.rb | 99 ----------------------- actionpack/lib/action_controller/base/metal.rb | 99 +++++++++++++++++++++++ 4 files changed, 101 insertions(+), 101 deletions(-) delete mode 100644 actionpack/lib/action_controller/base/http.rb create mode 100644 actionpack/lib/action_controller/base/metal.rb (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb index ca00e66349..b93e6ce634 100644 --- a/actionpack/lib/action_controller/abstract/base.rb +++ b/actionpack/lib/action_controller/abstract/base.rb @@ -30,7 +30,7 @@ module AbstractController # instance methods on that abstract class. Public instance methods of # a controller would normally be considered action methods, so we # are removing those methods on classes declared as abstract - # (ActionController::Http and ActionController::Base are defined + # (ActionController::Metal and ActionController::Base are defined # as abstract) def internal_methods controller = self diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base/base.rb index e541d24e31..9d9f735e27 100644 --- a/actionpack/lib/action_controller/base/base.rb +++ b/actionpack/lib/action_controller/base/base.rb @@ -1,5 +1,5 @@ module ActionController - class Base < Http + class Base < Metal abstract! include AbstractController::Benchmarker diff --git a/actionpack/lib/action_controller/base/http.rb b/actionpack/lib/action_controller/base/http.rb deleted file mode 100644 index 3efd1b656f..0000000000 --- a/actionpack/lib/action_controller/base/http.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'action_controller/abstract' - -module ActionController - # ActionController::Http provides a way to get a valid Rack application from a controller. - # - # In AbstractController, dispatching is triggered directly by calling #process on a new controller. - # ActionController::Http provides an #action method that returns a valid Rack application for a - # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router, - # can dispatch directly to the action returned by FooController.action(:index). - class Http < AbstractController::Base - abstract! - - # :api: public - attr_internal :params, :env - - # Returns the last part of the controller's name, underscored, without the ending - # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for - # controller_name - # - # ==== Returns - # String - def self.controller_name - @controller_name ||= controller_path.split("/").last - end - - # Delegates to the class' #controller_name - def controller_name - self.class.controller_name - end - - # Returns the full controller name, underscored, without the ending Controller. - # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_name. - # - # ==== Returns - # String - def self.controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore - end - - # Delegates to the class' #controller_path - def controller_path - self.class.controller_path - end - - # The details below can be overridden to support a specific - # Request and Response object. The default ActionController::Base - # implementation includes RackConvenience, which makes a request - # and response object available. You might wish to control the - # environment and response manually for performance reasons. - - attr_internal :status, :headers, :content_type - - def initialize(*) - @_headers = {} - super - end - - # Basic implementations for content_type=, location=, and headers are - # provided to reduce the dependency on the RackConvenience module - # in Renderer and Redirector. - - def content_type=(type) - headers["Content-Type"] = type.to_s - end - - def location=(url) - headers["Location"] = url - end - - # :api: private - def call(name, env) - @_env = env - process(name) - to_rack - end - - # :api: private - def to_rack - [status, headers, response_body] - end - - # Return a rack endpoint for the given action. Memoize the endpoint, so - # multiple calls into MyController.action will return the same object - # for the same action. - # - # ==== Parameters - # action<#to_s>:: An action name - # - # ==== Returns - # Proc:: A rack application - def self.action(name) - @actions ||= {} - @actions[name.to_s] ||= proc do |env| - new.call(name, env) - end - end - end -end diff --git a/actionpack/lib/action_controller/base/metal.rb b/actionpack/lib/action_controller/base/metal.rb new file mode 100644 index 0000000000..e7d776b63e --- /dev/null +++ b/actionpack/lib/action_controller/base/metal.rb @@ -0,0 +1,99 @@ +require 'action_controller/abstract' + +module ActionController + # ActionController::Metal provides a way to get a valid Rack application from a controller. + # + # In AbstractController, dispatching is triggered directly by calling #process on a new controller. + # ActionController::Metal provides an #action method that returns a valid Rack application for a + # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router, + # can dispatch directly to the action returned by FooController.action(:index). + class Metal < AbstractController::Base + abstract! + + # :api: public + attr_internal :params, :env + + # Returns the last part of the controller's name, underscored, without the ending + # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for + # controller_name + # + # ==== Returns + # String + def self.controller_name + @controller_name ||= controller_path.split("/").last + end + + # Delegates to the class' #controller_name + def controller_name + self.class.controller_name + end + + # Returns the full controller name, underscored, without the ending Controller. + # For instance, MyApp::MyPostsController would return "my_app/my_posts" for + # controller_name. + # + # ==== Returns + # String + def self.controller_path + @controller_path ||= name && name.sub(/Controller$/, '').underscore + end + + # Delegates to the class' #controller_path + def controller_path + self.class.controller_path + end + + # The details below can be overridden to support a specific + # Request and Response object. The default ActionController::Base + # implementation includes RackConvenience, which makes a request + # and response object available. You might wish to control the + # environment and response manually for performance reasons. + + attr_internal :status, :headers, :content_type + + def initialize(*) + @_headers = {} + super + end + + # Basic implementations for content_type=, location=, and headers are + # provided to reduce the dependency on the RackConvenience module + # in Renderer and Redirector. + + def content_type=(type) + headers["Content-Type"] = type.to_s + end + + def location=(url) + headers["Location"] = url + end + + # :api: private + def call(name, env) + @_env = env + process(name) + to_rack + end + + # :api: private + def to_rack + [status, headers, response_body] + end + + # Return a rack endpoint for the given action. Memoize the endpoint, so + # multiple calls into MyController.action will return the same object + # for the same action. + # + # ==== Parameters + # action<#to_s>:: An action name + # + # ==== Returns + # Proc:: A rack application + def self.action(name) + @actions ||= {} + @actions[name.to_s] ||= proc do |env| + new.call(name, env) + end + end + end +end -- cgit v1.2.3 From bd6b61be88dfe6eb1ff1dcc5c17542d804a842c7 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 19:52:11 -0300 Subject: Rename /base to /metal and make base.rb and metal.rb top-level to reflect their module locations --- actionpack/lib/action_controller/base.rb | 167 ++++++++++ actionpack/lib/action_controller/base/base.rb | 167 ---------- .../lib/action_controller/base/compatibility.rb | 147 --------- .../lib/action_controller/base/conditional_get.rb | 118 ------- actionpack/lib/action_controller/base/cookies.rb | 94 ------ .../lib/action_controller/base/exceptions.rb | 58 ---- .../base/filter_parameter_logging.rb | 96 ------ actionpack/lib/action_controller/base/flash.rb | 164 ---------- actionpack/lib/action_controller/base/helpers.rb | 176 ---------- .../lib/action_controller/base/hide_actions.rb | 35 -- .../action_controller/base/http_authentication.rb | 309 ------------------ actionpack/lib/action_controller/base/layouts.rb | 192 ----------- actionpack/lib/action_controller/base/metal.rb | 99 ------ .../lib/action_controller/base/mime_responds.rb | 353 --------------------- .../lib/action_controller/base/rack_convenience.rb | 33 -- .../lib/action_controller/base/redirector.rb | 19 -- .../lib/action_controller/base/render_options.rb | 103 ------ actionpack/lib/action_controller/base/renderer.rb | 77 ----- .../base/request_forgery_protection.rb | 118 ------- actionpack/lib/action_controller/base/rescuable.rb | 52 --- actionpack/lib/action_controller/base/session.rb | 15 - .../action_controller/base/session_management.rb | 54 ---- actionpack/lib/action_controller/base/streaming.rb | 187 ----------- actionpack/lib/action_controller/base/testing.rb | 39 --- actionpack/lib/action_controller/base/url_for.rb | 49 --- .../lib/action_controller/base/verification.rb | 130 -------- actionpack/lib/action_controller/metal.rb | 99 ++++++ .../lib/action_controller/metal/compatibility.rb | 147 +++++++++ .../lib/action_controller/metal/conditional_get.rb | 118 +++++++ actionpack/lib/action_controller/metal/cookies.rb | 94 ++++++ .../lib/action_controller/metal/exceptions.rb | 58 ++++ .../metal/filter_parameter_logging.rb | 96 ++++++ actionpack/lib/action_controller/metal/flash.rb | 164 ++++++++++ actionpack/lib/action_controller/metal/helpers.rb | 176 ++++++++++ .../lib/action_controller/metal/hide_actions.rb | 35 ++ .../action_controller/metal/http_authentication.rb | 309 ++++++++++++++++++ actionpack/lib/action_controller/metal/layouts.rb | 192 +++++++++++ .../lib/action_controller/metal/mime_responds.rb | 353 +++++++++++++++++++++ .../action_controller/metal/rack_convenience.rb | 33 ++ .../lib/action_controller/metal/redirector.rb | 19 ++ .../lib/action_controller/metal/render_options.rb | 103 ++++++ actionpack/lib/action_controller/metal/renderer.rb | 77 +++++ .../metal/request_forgery_protection.rb | 118 +++++++ .../lib/action_controller/metal/rescuable.rb | 52 +++ actionpack/lib/action_controller/metal/session.rb | 15 + .../action_controller/metal/session_management.rb | 54 ++++ .../lib/action_controller/metal/streaming.rb | 187 +++++++++++ actionpack/lib/action_controller/metal/testing.rb | 39 +++ actionpack/lib/action_controller/metal/url_for.rb | 49 +++ .../lib/action_controller/metal/verification.rb | 130 ++++++++ 50 files changed, 2884 insertions(+), 2884 deletions(-) create mode 100644 actionpack/lib/action_controller/base.rb delete mode 100644 actionpack/lib/action_controller/base/base.rb delete mode 100644 actionpack/lib/action_controller/base/compatibility.rb delete mode 100644 actionpack/lib/action_controller/base/conditional_get.rb delete mode 100644 actionpack/lib/action_controller/base/cookies.rb delete mode 100644 actionpack/lib/action_controller/base/exceptions.rb delete mode 100644 actionpack/lib/action_controller/base/filter_parameter_logging.rb delete mode 100644 actionpack/lib/action_controller/base/flash.rb delete mode 100644 actionpack/lib/action_controller/base/helpers.rb delete mode 100644 actionpack/lib/action_controller/base/hide_actions.rb delete mode 100644 actionpack/lib/action_controller/base/http_authentication.rb delete mode 100644 actionpack/lib/action_controller/base/layouts.rb delete mode 100644 actionpack/lib/action_controller/base/metal.rb delete mode 100644 actionpack/lib/action_controller/base/mime_responds.rb delete mode 100644 actionpack/lib/action_controller/base/rack_convenience.rb delete mode 100644 actionpack/lib/action_controller/base/redirector.rb delete mode 100644 actionpack/lib/action_controller/base/render_options.rb delete mode 100644 actionpack/lib/action_controller/base/renderer.rb delete mode 100644 actionpack/lib/action_controller/base/request_forgery_protection.rb delete mode 100644 actionpack/lib/action_controller/base/rescuable.rb delete mode 100644 actionpack/lib/action_controller/base/session.rb delete mode 100644 actionpack/lib/action_controller/base/session_management.rb delete mode 100644 actionpack/lib/action_controller/base/streaming.rb delete mode 100644 actionpack/lib/action_controller/base/testing.rb delete mode 100644 actionpack/lib/action_controller/base/url_for.rb delete mode 100644 actionpack/lib/action_controller/base/verification.rb create mode 100644 actionpack/lib/action_controller/metal.rb create mode 100644 actionpack/lib/action_controller/metal/compatibility.rb create mode 100644 actionpack/lib/action_controller/metal/conditional_get.rb create mode 100644 actionpack/lib/action_controller/metal/cookies.rb create mode 100644 actionpack/lib/action_controller/metal/exceptions.rb create mode 100644 actionpack/lib/action_controller/metal/filter_parameter_logging.rb create mode 100644 actionpack/lib/action_controller/metal/flash.rb create mode 100644 actionpack/lib/action_controller/metal/helpers.rb create mode 100644 actionpack/lib/action_controller/metal/hide_actions.rb create mode 100644 actionpack/lib/action_controller/metal/http_authentication.rb create mode 100644 actionpack/lib/action_controller/metal/layouts.rb create mode 100644 actionpack/lib/action_controller/metal/mime_responds.rb create mode 100644 actionpack/lib/action_controller/metal/rack_convenience.rb create mode 100644 actionpack/lib/action_controller/metal/redirector.rb create mode 100644 actionpack/lib/action_controller/metal/render_options.rb create mode 100644 actionpack/lib/action_controller/metal/renderer.rb create mode 100644 actionpack/lib/action_controller/metal/request_forgery_protection.rb create mode 100644 actionpack/lib/action_controller/metal/rescuable.rb create mode 100644 actionpack/lib/action_controller/metal/session.rb create mode 100644 actionpack/lib/action_controller/metal/session_management.rb create mode 100644 actionpack/lib/action_controller/metal/streaming.rb create mode 100644 actionpack/lib/action_controller/metal/testing.rb create mode 100644 actionpack/lib/action_controller/metal/url_for.rb create mode 100644 actionpack/lib/action_controller/metal/verification.rb (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb new file mode 100644 index 0000000000..9d9f735e27 --- /dev/null +++ b/actionpack/lib/action_controller/base.rb @@ -0,0 +1,167 @@ +module ActionController + class Base < Metal + abstract! + + include AbstractController::Benchmarker + include AbstractController::Callbacks + include AbstractController::Logger + + include ActionController::Helpers + include ActionController::HideActions + include ActionController::UrlFor + include ActionController::Redirector + include ActionController::Renderer + include ActionController::Renderers::All + include ActionController::Layouts + include ActionController::ConditionalGet + include ActionController::RackConvenience + + # Legacy modules + include SessionManagement + include ActionDispatch::StatusCodes + include ActionController::Caching + include ActionController::MimeResponds + + # Rails 2.x compatibility + include ActionController::Rails2Compatibility + + include ActionController::Cookies + include ActionController::Session + include ActionController::Flash + include ActionController::Verification + include ActionController::RequestForgeryProtection + include ActionController::Streaming + include ActionController::HttpAuthentication::Basic::ControllerMethods + include ActionController::HttpAuthentication::Digest::ControllerMethods + include ActionController::FilterParameterLogging + include ActionController::Translation + + # TODO: Extract into its own module + # This should be moved together with other normalizing behavior + module ImplicitRender + def send_action(*) + ret = super + default_render unless performed? + ret + end + + def default_render + render + end + + def method_for_action(action_name) + super || begin + if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) + "default_render" + end + end + end + end + + include ImplicitRender + + include ActionController::Rescue + + def self.inherited(klass) + ::ActionController::Base.subclasses << klass.to_s + super + end + + def self.subclasses + @subclasses ||= [] + end + + def _normalize_options(action = nil, options = {}, &blk) + if action.is_a?(Hash) + options, action = action, nil + elsif action.is_a?(String) || action.is_a?(Symbol) + key = case action = action.to_s + when %r{^/} then :file + when %r{/} then :template + else :action + end + options.merge! key => action + elsif action + options.merge! :partial => action + end + + if options.key?(:action) && options[:action].to_s.index("/") + options[:template] = options.delete(:action) + end + + if options[:status] + options[:status] = interpret_status(options[:status]).to_i + end + + options[:update] = blk if block_given? + options + end + + def render(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + def render_to_string(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: + # + # * Hash - The URL will be generated by calling url_for with the +options+. + # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. + # * String not containing a protocol - The current protocol and host is prepended to the string. + # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. + # Short-hand for redirect_to(request.env["HTTP_REFERER"]) + # + # Examples: + # redirect_to :action => "show", :id => 5 + # redirect_to post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to articles_url + # redirect_to :back + # + # The redirection happens as a "302 Moved" header unless otherwise specified. + # + # Examples: + # redirect_to post_url(@post), :status=>:found + # redirect_to :action=>'atom', :status=>:moved_permanently + # redirect_to post_url(@post), :status=>301 + # redirect_to :action=>'atom', :status=>302 + # + # When using redirect_to :back, if there is no referrer, + # RedirectBackError will be raised. You may specify some fallback + # behavior for this case by rescuing RedirectBackError. + def redirect_to(options = {}, response_status = {}) #:doc: + raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + + status = if options.is_a?(Hash) && options.key?(:status) + interpret_status(options.delete(:status)) + elsif response_status.key?(:status) + interpret_status(response_status[:status]) + else + 302 + end + + url = case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + when %r{^\w[\w\d+.-]*:.*} + options + when String + request.protocol + request.host_with_port + options + when :back + raise RedirectBackError unless refer = request.headers["Referer"] + refer + else + url_for(options) + end + + super(url, status) + end + end +end diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base/base.rb deleted file mode 100644 index 9d9f735e27..0000000000 --- a/actionpack/lib/action_controller/base/base.rb +++ /dev/null @@ -1,167 +0,0 @@ -module ActionController - class Base < Metal - abstract! - - include AbstractController::Benchmarker - include AbstractController::Callbacks - include AbstractController::Logger - - include ActionController::Helpers - include ActionController::HideActions - include ActionController::UrlFor - include ActionController::Redirector - include ActionController::Renderer - include ActionController::Renderers::All - include ActionController::Layouts - include ActionController::ConditionalGet - include ActionController::RackConvenience - - # Legacy modules - include SessionManagement - include ActionDispatch::StatusCodes - include ActionController::Caching - include ActionController::MimeResponds - - # Rails 2.x compatibility - include ActionController::Rails2Compatibility - - include ActionController::Cookies - include ActionController::Session - include ActionController::Flash - include ActionController::Verification - include ActionController::RequestForgeryProtection - include ActionController::Streaming - include ActionController::HttpAuthentication::Basic::ControllerMethods - include ActionController::HttpAuthentication::Digest::ControllerMethods - include ActionController::FilterParameterLogging - include ActionController::Translation - - # TODO: Extract into its own module - # This should be moved together with other normalizing behavior - module ImplicitRender - def send_action(*) - ret = super - default_render unless performed? - ret - end - - def default_render - render - end - - def method_for_action(action_name) - super || begin - if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) - "default_render" - end - end - end - end - - include ImplicitRender - - include ActionController::Rescue - - def self.inherited(klass) - ::ActionController::Base.subclasses << klass.to_s - super - end - - def self.subclasses - @subclasses ||= [] - end - - def _normalize_options(action = nil, options = {}, &blk) - if action.is_a?(Hash) - options, action = action, nil - elsif action.is_a?(String) || action.is_a?(Symbol) - key = case action = action.to_s - when %r{^/} then :file - when %r{/} then :template - else :action - end - options.merge! key => action - elsif action - options.merge! :partial => action - end - - if options.key?(:action) && options[:action].to_s.index("/") - options[:template] = options.delete(:action) - end - - if options[:status] - options[:status] = interpret_status(options[:status]).to_i - end - - options[:update] = blk if block_given? - options - end - - def render(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end - - def render_to_string(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end - - # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: - # - # * Hash - The URL will be generated by calling url_for with the +options+. - # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. - # * String not containing a protocol - The current protocol and host is prepended to the string. - # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) - # - # Examples: - # redirect_to :action => "show", :id => 5 - # redirect_to post - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to articles_url - # redirect_to :back - # - # The redirection happens as a "302 Moved" header unless otherwise specified. - # - # Examples: - # redirect_to post_url(@post), :status=>:found - # redirect_to :action=>'atom', :status=>:moved_permanently - # redirect_to post_url(@post), :status=>301 - # redirect_to :action=>'atom', :status=>302 - # - # When using redirect_to :back, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback - # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: - raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? - - status = if options.is_a?(Hash) && options.key?(:status) - interpret_status(options.delete(:status)) - elsif response_status.key?(:status) - interpret_status(response_status[:status]) - else - 302 - end - - url = case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - when %r{^\w[\w\d+.-]*:.*} - options - when String - request.protocol + request.host_with_port + options - when :back - raise RedirectBackError unless refer = request.headers["Referer"] - refer - else - url_for(options) - end - - super(url, status) - end - end -end diff --git a/actionpack/lib/action_controller/base/compatibility.rb b/actionpack/lib/action_controller/base/compatibility.rb deleted file mode 100644 index 23e7b1b3af..0000000000 --- a/actionpack/lib/action_controller/base/compatibility.rb +++ /dev/null @@ -1,147 +0,0 @@ -module ActionController - module Rails2Compatibility - extend ActiveSupport::Concern - - class ::ActionController::ActionControllerError < StandardError #:nodoc: - end - - # Temporary hax - included do - ::ActionController::UnknownAction = ::AbstractController::ActionNotFound - ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError - - cattr_accessor :session_options - self.session_options = {} - - cattr_accessor :allow_concurrency - self.allow_concurrency = false - - cattr_accessor :param_parsers - self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form, - Mime::URL_ENCODED_FORM => :url_encoded_form, - Mime::XML => :xml_simple, - Mime::JSON => :json } - - cattr_accessor :relative_url_root - self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] - - cattr_accessor :default_charset - self.default_charset = "utf-8" - - # cattr_reader :protected_instance_variables - cattr_accessor :protected_instance_variables - self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params - @_flash @_response) - - # Indicates whether or not optimise the generated named - # route helper methods - cattr_accessor :optimise_named_routes - self.optimise_named_routes = true - - cattr_accessor :resources_path_names - self.resources_path_names = { :new => 'new', :edit => 'edit' } - - # Controls the resource action separator - cattr_accessor :resource_action_separator - self.resource_action_separator = "/" - - cattr_accessor :use_accept_header - self.use_accept_header = true - - cattr_accessor :page_cache_directory - self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - - cattr_reader :cache_store - - cattr_accessor :consider_all_requests_local - self.consider_all_requests_local = true - - # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, - # and images to a dedicated asset server away from the main web server. Example: - # ActionController::Base.asset_host = "http://assets.example.com" - cattr_accessor :asset_host - - cattr_accessor :ip_spoofing_check - self.ip_spoofing_check = true - end - - # For old tests - def initialize_template_class(*) end - def assign_shortcuts(*) end - - # TODO: Remove this after we flip - def template - @template ||= view_context - end - - def process_action(*) - template - super - end - - module ClassMethods - def consider_all_requests_local - end - - def rescue_action(env) - raise env["action_dispatch.rescue.exception"] - end - - # Defines the storage option for cached fragments - def cache_store=(store_option) - @@cache_store = ActiveSupport::Cache.lookup_store(store_option) - end - end - - def render_to_body(options) - if options.is_a?(Hash) && options.key?(:template) - options[:template].sub!(/^\//, '') - end - - options[:text] = nil if options[:nothing] == true - - body = super - body = [' '] if body.blank? - body - end - - def _handle_method_missing - method_missing(@_action_name.to_sym) - end - - def method_for_action(action_name) - super || (respond_to?(:method_missing) && "_handle_method_missing") - end - - def _find_layout(name, details) - details[:prefix] = nil if name =~ /\blayouts/ - super - end - - # Move this into a "don't run in production" module - def _default_layout(details, require_layout = false) - super - rescue ActionView::MissingTemplate - _find_layout(_layout({}), {}) - nil - end - - def performed? - response_body - end - - # ==== Request only view path switching ==== - def append_view_path(path) - view_paths.push(*path) - end - - def prepend_view_path(path) - view_paths.unshift(*path) - end - - def view_paths - view_context.view_paths - end - end -end diff --git a/actionpack/lib/action_controller/base/conditional_get.rb b/actionpack/lib/action_controller/base/conditional_get.rb deleted file mode 100644 index 6d35137428..0000000000 --- a/actionpack/lib/action_controller/base/conditional_get.rb +++ /dev/null @@ -1,118 +0,0 @@ -module ActionController - module ConditionalGet - extend ActiveSupport::Concern - - include RackConvenience - - # Sets the etag, last_modified, or both on the response and renders a - # "304 Not Modified" response if the request is already fresh. - # - # Parameters: - # * :etag - # * :last_modified - # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). - # - # Example: - # - # def show - # @article = Article.find(params[:id]) - # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true) - # end - # - # 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, :public) - - response.etag = options[:etag] if options[:etag] - response.last_modified = options[:last_modified] if options[:last_modified] - - if options[:public] - response.cache_control[:public] = true - end - - if request.fresh?(response) - head :not_modified - end - end - - # Return a response that has no content (merely headers). The options - # argument is interpreted to be a hash of header names and values. - # This allows you to easily return a response that consists only of - # significant headers: - # - # head :created, :location => person_path(@person) - # - # It can also be used to return exceptional conditions: - # - # return head(:method_not_allowed) unless request.post? - # return head(:bad_request) unless valid_request? - # render - def head(*args) - if args.length > 2 - raise ArgumentError, "too many arguments to head" - elsif args.empty? - raise ArgumentError, "too few arguments to head" - end - options = args.extract_options! - status = args.shift || options.delete(:status) || :ok - - options.each do |key, value| - headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s - end - - render :nothing => true, :status => status - end - - # Sets the etag and/or last_modified on the response and checks it against - # the client request. If the request doesn't match the options provided, the - # request is considered stale and should be generated from scratch. Otherwise, - # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent. - # - # Parameters: - # * :etag - # * :last_modified - # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). - # - # Example: - # - # def show - # @article = Article.find(params[:id]) - # - # if stale?(:etag => @article, :last_modified => @article.created_at.utc) - # @statistics = @article.really_expensive_call - # respond_to do |format| - # # all the supported formats - # end - # end - # end - def stale?(options) - fresh_when(options) - !request.fresh?(response) - end - - # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that - # intermediate caches shouldn't cache the response. - # - # Examples: - # expires_in 20.minutes - # expires_in 3.hours, :public => true - # expires in 3.hours, 'max-stale' => 5.hours, :public => true - # - # This method will overwrite an existing Cache-Control header. - # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. - def expires_in(seconds, options = {}) #:doc: - response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public)) - options.delete(:private) - - response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} - end - - # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or - # intermediate caches (like caching proxy servers). - def expires_now #:doc: - response.headers["Cache-Control"] = "no-cache" - end - end -end diff --git a/actionpack/lib/action_controller/base/cookies.rb b/actionpack/lib/action_controller/base/cookies.rb deleted file mode 100644 index d4806623c3..0000000000 --- a/actionpack/lib/action_controller/base/cookies.rb +++ /dev/null @@ -1,94 +0,0 @@ -module ActionController #:nodoc: - # Cookies are read and written through ActionController#cookies. - # - # The cookies being read are the ones received along with the request, the cookies - # being written will be sent out with the response. Reading a cookie does not get - # the cookie object itself back, just the value it holds. - # - # Examples for writing: - # - # # Sets a simple session cookie. - # cookies[:user_name] = "david" - # - # # Sets a cookie that expires in 1 hour. - # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } - # - # Examples for reading: - # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # - # Example for deleting: - # - # cookies.delete :user_name - # - # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: - # - # cookies[:key] = { - # :value => 'a yummy cookie', - # :expires => 1.year.from_now, - # :domain => 'domain.com' - # } - # - # cookies.delete(:key, :domain => 'domain.com') - # - # The option symbols for setting cookies are: - # - # * :value - The cookie's value or list of values (as an array). - # * :path - The path for which this cookie applies. Defaults to the root - # of the application. - # * :domain - The domain for which this cookie applies. - # * :expires - The time at which this cookie expires, as a Time object. - # * :secure - Whether this cookie is a only transmitted to HTTPS servers. - # Default is +false+. - # * :httponly - Whether this cookie is accessible via scripting or - # only HTTP. Defaults to +false+. - module Cookies - def self.included(base) - base.helper_method :cookies - end - - protected - # Returns the cookie container, which operates as described above. - def cookies - @cookies ||= CookieJar.new(self) - end - end - - class CookieJar < Hash #:nodoc: - def initialize(controller) - @controller, @cookies = controller, controller.request.cookies - super() - update(@cookies) - end - - # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. - def [](name) - super(name.to_s) - end - - # Sets the cookie named +name+. The second argument may be the very cookie - # value, or a hash of options as documented above. - def []=(key, options) - if options.is_a?(Hash) - options.symbolize_keys! - else - options = { :value => options } - end - - options[:path] = "/" unless options.has_key?(:path) - super(key.to_s, options[:value]) - @controller.response.set_cookie(key, options) - end - - # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past. Like []=, you can pass in - # an options hash to delete cookies with extra data such as a :path. - def delete(key, options = {}) - options.symbolize_keys! - options[:path] = "/" unless options.has_key?(:path) - super(key.to_s) - @controller.response.delete_cookie(key, options) - end - end -end diff --git a/actionpack/lib/action_controller/base/exceptions.rb b/actionpack/lib/action_controller/base/exceptions.rb deleted file mode 100644 index d0811254cb..0000000000 --- a/actionpack/lib/action_controller/base/exceptions.rb +++ /dev/null @@ -1,58 +0,0 @@ -module ActionController - class ActionControllerError < StandardError #:nodoc: - end - - class SessionRestoreError < ActionControllerError #:nodoc: - end - - class RenderError < ActionControllerError #:nodoc: - end - - class RoutingError < ActionControllerError #:nodoc: - attr_reader :failures - def initialize(message, failures=[]) - super(message) - @failures = failures - end - end - - class MethodNotAllowed < ActionControllerError #:nodoc: - attr_reader :allowed_methods - - def initialize(*allowed_methods) - super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") - @allowed_methods = allowed_methods - end - - def allowed_methods_header - allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' - end - - def handle_response!(response) - response.headers['Allow'] ||= allowed_methods_header - end - end - - class NotImplemented < MethodNotAllowed #:nodoc: - end - - class UnknownController < ActionControllerError #:nodoc: - end - - class MissingFile < ActionControllerError #:nodoc: - end - - class RenderError < ActionControllerError #:nodoc: - end - - class SessionOverflowError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - class UnknownHttpMethod < ActionControllerError #:nodoc: - end -end \ No newline at end of file diff --git a/actionpack/lib/action_controller/base/filter_parameter_logging.rb b/actionpack/lib/action_controller/base/filter_parameter_logging.rb deleted file mode 100644 index 065e62a37f..0000000000 --- a/actionpack/lib/action_controller/base/filter_parameter_logging.rb +++ /dev/null @@ -1,96 +0,0 @@ -module ActionController - module FilterParameterLogging - extend ActiveSupport::Concern - - include AbstractController::Logger - - included do - include InstanceMethodsForNewBase - end - - module ClassMethods - # Replace sensitive parameter data from the request log. - # Filters parameters that have any of the arguments as a substring. - # Looks in all subhashes of the param hash for keys to filter. - # If a block is given, each key and value of the parameter hash and all - # subhashes is passed to it, the value or key - # can be replaced using String#replace or similar method. - # - # Examples: - # filter_parameter_logging - # => Does nothing, just slows the logging process down - # - # filter_parameter_logging :password - # => replaces the value to all keys matching /password/i with "[FILTERED]" - # - # filter_parameter_logging :foo, "bar" - # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" - # - # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i } - # => reverses the value to all keys matching /secret/i - # - # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i } - # => reverses the value to all keys matching /secret/i, and - # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" - def filter_parameter_logging(*filter_words, &block) - parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 - - define_method(:filter_parameters) do |unfiltered_parameters| - filtered_parameters = {} - - unfiltered_parameters.each do |key, value| - if key =~ parameter_filter - filtered_parameters[key] = '[FILTERED]' - elsif value.is_a?(Hash) - filtered_parameters[key] = filter_parameters(value) - elsif value.is_a?(Array) - filtered_parameters[key] = value.collect do |item| - filter_parameters(item) - end - elsif block_given? - key = key.dup - value = value.dup if value - yield key, value - filtered_parameters[key] = value - else - filtered_parameters[key] = value - end - end - - filtered_parameters - end - protected :filter_parameters - end - end - - module InstanceMethodsForNewBase - # TODO : Fix the order of information inside such that it's exactly same as the old base - def process(*) - ret = super - - if logger - parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup - parameters = parameters.except!(:controller, :action, :format, :_method, :only_path) - - unless parameters.empty? - # TODO : Move DelayedLog to AS - log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" } - logger.info(log) - end - end - - ret - end - end - - private - - # TODO : This method is not needed for the new base - 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 - end -end diff --git a/actionpack/lib/action_controller/base/flash.rb b/actionpack/lib/action_controller/base/flash.rb deleted file mode 100644 index 590f9be3ac..0000000000 --- a/actionpack/lib/action_controller/base/flash.rb +++ /dev/null @@ -1,164 +0,0 @@ -module ActionController #:nodoc: - # The flash provides a way to pass temporary objects 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 flash[:notice] = "Successfully created" before redirecting to a display action that can - # then expose the flash to its template. Actually, that exposure is automatically done. Example: - # - # class PostsController < ActionController::Base - # def create - # # save post - # flash[:notice] = "Successfully created post" - # redirect_to posts_path(@post) - # end - # - # def show - # # doesn't need to assign the flash notice to the template, that's done automatically - # end - # end - # - # show.html.erb - # <% if flash[:notice] %> - #
<%= flash[:notice] %>
- # <% 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. - # - # See docs on the FlashHash class for more details about the flash. - module Flash - extend ActiveSupport::Concern - - include Session - - class FlashNow #:nodoc: - def initialize(flash) - @flash = flash - end - - def []=(k, v) - @flash[k] = v - @flash.discard(k) - v - end - - def [](k) - @flash[k] - end - end - - class FlashHash < Hash - def initialize #:nodoc: - super - @used = {} - end - - def []=(k, v) #:nodoc: - keep(k) - super - end - - def update(h) #:nodoc: - h.keys.each { |k| keep(k) } - super - end - - alias :merge! :update - - def replace(h) #:nodoc: - @used = {} - super - end - - # Sets a flash that will not be available to the next action, only to the current. - # - # flash.now[:message] = "Hello current action" - # - # This method enables you to use the flash as a central messaging system in your app. - # When you need to pass an object to the next action, you use the standard flash assign ([]=). - # When you need to pass an object to the current action, you use now, and your object will - # vanish when the current action is done. - # - # Entries set via now are accessed the same way as standard entries: flash['my-key']. - def now - FlashNow.new(self) - end - - # Keeps either the entire current flash or a specific flash entry available for the next action: - # - # flash.keep # keeps the entire flash - # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded - def keep(k = nil) - use(k, false) - end - - # Marks the entire flash or a single flash entry to be discarded by the end of the current action: - # - # flash.discard # discard the entire flash at the end of the current action - # flash.discard(:warning) # discard only the "warning" entry at the end of the current action - def discard(k = nil) - use(k) - end - - # Mark for removal entries that were kept, and delete unkept ones. - # - # This method is called automatically by filters, so you generally don't need to care about it. - def sweep #:nodoc: - keys.each do |k| - unless @used[k] - use(k) - else - delete(k) - @used.delete(k) - end - end - - # clean up after keys that could have been left over by calling reject! or shift on the flash - (@used.keys - keys).each{ |k| @used.delete(k) } - end - - def store(session, key = "flash") - return if self.empty? - session[key] = self - end - - private - # Used internally by the keep and discard methods - # use() # marks the entire flash as used - # use('msg') # marks the "msg" entry as used - # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) - # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) - # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself - # if no key is passed. - def use(key = nil, used = true) - Array(key || keys).each { |k| @used[k] = used } - return key ? self[key] : self - end - end - - protected - def process_action(method_name) - super - if defined? @_flash - @_flash.store(session) - remove_instance_variable(:@_flash) - end - end - - def reset_session - super - remove_instance_variable(:@_flash) if defined?(@_flash) - end - - # Access the contents of the flash. Use flash["notice"] to - # read a notice you put there or flash["notice"] = "hello" - # to put a new one. - def flash #:doc: - if !defined?(@_flash) - @_flash = session["flash"] || FlashHash.new - @_flash.sweep - end - - @_flash - end - end -end diff --git a/actionpack/lib/action_controller/base/helpers.rb b/actionpack/lib/action_controller/base/helpers.rb deleted file mode 100644 index 7c52779064..0000000000 --- a/actionpack/lib/action_controller/base/helpers.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'active_support/dependencies' - -module ActionController - # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, - # +numbers+ and model objects, to name a few. These helpers are available to all templates - # by default. - # - # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to - # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will - # include a helper whose name matches that of the controller, e.g., MyController will automatically - # include MyHelper. - # - # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any - # controller which inherits from it. - # - # ==== Examples - # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if - # the Time object is blank: - # - # module FormattedTimeHelper - # def format_time(time, format=:long, blank_message=" ") - # time.blank? ? blank_message : time.to_s(format) - # end - # end - # - # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: - # - # class EventsController < ActionController::Base - # helper FormattedTimeHelper - # def index - # @events = Event.find(:all) - # end - # end - # - # Then, in any view rendered by EventController, the format_time method can be called: - # - # <% @events.each do |event| -%> - #

- # <% format_time(event.time, :short, "N/A") %> | <%= event.name %> - #

- # <% end -%> - # - # Finally, assuming we have two event instances, one which has a time and one which does not, - # the output might look like this: - # - # 23 Aug 11:30 | Carolina Railhawks Soccer Match - # N/A | Carolina Railhaws Training Workshop - # - module Helpers - extend ActiveSupport::Concern - - include AbstractController::Helpers - - included do - # Set the default directory for helpers - extlib_inheritable_accessor(:helpers_dir) do - defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers" - end - end - - module ClassMethods - def inherited(klass) - klass.class_eval { default_helper_module! unless name.blank? } - super - end - - # The +helper+ class method can take a series of helper module names, a block, or both. - # - # ==== Parameters - # *args - # block:: A block defining helper methods - # - # ==== Examples - # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file - # and include the module in the template class. The second form illustrates how to include custom helpers - # when working with namespaced controllers, or other cases where the file containing the helper definition is not - # in one of Rails' standard load paths: - # helper :foo # => requires 'foo_helper' and includes FooHelper - # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper - # - # When the argument is a module it will be included directly in the template class. - # helper FooHelper # => includes FooHelper - # - # When the argument is the symbol :all, the controller will include all helpers beneath - # ActionController::Base.helpers_dir (defaults to app/helpers/**/*.rb under RAILS_ROOT). - # helper :all - # - # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available - # to the template. - # # One line - # helper { def hello() "Hello, world!" end } - # # Multi-line - # helper do - # def foo(bar) - # "#{bar} is the very best" - # end - # end - # - # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of - # +symbols+, +strings+, +modules+ and blocks. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # - def helper(*args, &block) - super(*_modules_for_helpers(args), &block) - end - - # Declares helper accessors for controller attributes. For example, the - # following adds new +name+ and name= instance methods to a - # controller and makes them available to the view: - # helper_attr :name - # attr_accessor :name - # - # ==== Parameters - # *attrs:: Names of attributes to be converted - # into helpers. - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - # Provides a proxy to access helpers methods from outside the view. - def helpers - @helper_proxy ||= ActionView::Base.new.extend(_helpers) - end - - private - # Returns a list of modules, normalized from the acceptable kinds of - # helpers with the following behavior: - # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", - # and "foo_bar_helper.rb" is loaded using require_dependency. - # :all:: Loads all modules in the #helpers_dir - # Module:: No further processing - # - # After loading the appropriate files, the corresponding modules - # are returned. - # - # ==== Parameters - # args:: A list of helpers - # - # ==== Returns - # Array[Module]:: A normalized list of modules for the list of - # helpers provided. - def _modules_for_helpers(args) - args.flatten.map! do |arg| - case arg - when :all - _modules_for_helpers all_application_helpers - when String, Symbol - file_name = "#{arg.to_s.underscore}_helper" - require_dependency(file_name, "Missing helper file helpers/%s.rb") - file_name.camelize.constantize - when Module - arg - else - raise ArgumentError, "helper must be a String, Symbol, or Module" - end - end - end - - def default_helper_module! - module_name = name.sub(/Controller$/, '') - module_path = module_name.underscore - helper module_path - rescue MissingSourceFile => e - raise e unless e.is_missing? "#{module_path}_helper" - rescue NameError => e - raise e unless e.missing_name? "#{module_name}Helper" - end - - # Extract helper names from files in app/helpers/**/*.rb - def all_application_helpers - extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ - Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } - end - end - end -end diff --git a/actionpack/lib/action_controller/base/hide_actions.rb b/actionpack/lib/action_controller/base/hide_actions.rb deleted file mode 100644 index af68c772b1..0000000000 --- a/actionpack/lib/action_controller/base/hide_actions.rb +++ /dev/null @@ -1,35 +0,0 @@ -module ActionController - # ActionController::HideActions adds the ability to prevent public methods on a controller - # to be called as actions. - module HideActions - extend ActiveSupport::Concern - - included do - extlib_inheritable_accessor(:hidden_actions) { Set.new } - end - - private - - # Overrides AbstractController::Base#action_method? to return false if the - # action name is in the list of hidden actions. - def action_method?(action_name) - !hidden_actions.include?(action_name) && super - end - - module ClassMethods - # Sets all of the actions passed in as hidden actions. - # - # ==== Parameters - # *args<#to_s>:: A list of actions - def hide_action(*args) - hidden_actions.merge(args.map! {|a| a.to_s }) - end - - # Overrides AbstractController::Base#action_methods to remove any methods - # that are listed as hidden methods. - def action_methods - @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)}) - end - end - end -end diff --git a/actionpack/lib/action_controller/base/http_authentication.rb b/actionpack/lib/action_controller/base/http_authentication.rb deleted file mode 100644 index 525787bf92..0000000000 --- a/actionpack/lib/action_controller/base/http_authentication.rb +++ /dev/null @@ -1,309 +0,0 @@ -require 'active_support/base64' - -module ActionController - module HttpAuthentication - # Makes it dead easy to do HTTP Basic authentication. - # - # Simple Basic example: - # - # class PostsController < ApplicationController - # USER_NAME, PASSWORD = "dhh", "secret" - # - # before_filter :authenticate, :except => [ :index ] - # - # def index - # render :text => "Everyone can see me!" - # end - # - # def edit - # render :text => "I'm only accessible if you know the password" - # end - # - # private - # def authenticate - # authenticate_or_request_with_http_basic do |user_name, password| - # user_name == USER_NAME && password == PASSWORD - # end - # end - # end - # - # - # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, - # the regular HTML interface is protected by a session approach: - # - # class ApplicationController < ActionController::Base - # before_filter :set_account, :authenticate - # - # protected - # def set_account - # @account = Account.find_by_url_name(request.subdomains.first) - # end - # - # def authenticate - # case request.format - # when Mime::XML, Mime::ATOM - # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } - # @current_user = user - # else - # request_http_basic_authentication - # end - # else - # if session_authenticated? - # @current_user = @account.users.find(session[:authenticated][:user_id]) - # else - # redirect_to(login_url) and return false - # end - # end - # end - # end - # - # In your integration tests, you can do something like this: - # - # def test_access_granted_from_xml - # get( - # "/notes/1.xml", nil, - # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) - # ) - # - # assert_equal 200, status - # end - # - # Simple Digest example: - # - # require 'digest/md5' - # class PostsController < ApplicationController - # REALM = "SuperSecret" - # USERS = {"dhh" => "secret", #plain text password - # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password - # - # before_filter :authenticate, :except => [:index] - # - # def index - # render :text => "Everyone can see me!" - # end - # - # def edit - # render :text => "I'm only accessible if you know the password" - # end - # - # private - # def authenticate - # authenticate_or_request_with_http_digest(REALM) do |username| - # USERS[username] - # end - # end - # end - # - # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately - # hash to check the user's credentials. Returning +nil+ will cause authentication to fail. - # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If - # the password file or database is compromised, the attacker would be able to use the ha1 hash to - # authenticate as the user at this +realm+, but would not have the user's password to try using at - # other sites. - # - # On shared hosts, Apache sometimes doesn't pass authentication headers to - # FCGI instances. If your environment matches this description and you cannot - # authenticate, try this rule in your Apache setup: - # - # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] - module Basic - extend self - - module ControllerMethods - def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) - authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) - end - - def authenticate_with_http_basic(&login_procedure) - HttpAuthentication::Basic.authenticate(self, &login_procedure) - end - - def request_http_basic_authentication(realm = "Application") - HttpAuthentication::Basic.authentication_request(self, realm) - end - end - - def authenticate(controller, &login_procedure) - unless authorization(controller.request).blank? - login_procedure.call(*user_name_and_password(controller.request)) - end - end - - def user_name_and_password(request) - decode_credentials(request).split(/:/, 2) - end - - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] - end - - def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split.last || '') - end - - def encode_credentials(user_name, password) - "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}" - end - - def authentication_request(controller, realm) - controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}") - controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized - end - end - - module Digest - extend self - - module ControllerMethods - def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure) - authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm) - end - - # Authenticate with HTTP Digest, returns true or false - def authenticate_with_http_digest(realm = "Application", &password_procedure) - HttpAuthentication::Digest.authenticate(self, realm, &password_procedure) - end - - # Render output including the HTTP Digest authentication header - def request_http_digest_authentication(realm = "Application", message = nil) - HttpAuthentication::Digest.authentication_request(self, realm, message) - end - end - - # Returns false on a valid response, true otherwise - def authenticate(controller, realm, &password_procedure) - authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure) - end - - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] - end - - # Returns false unless the request credentials response value matches the expected value. - # First try the password as a ha1 digest password. If this fails, then try it as a plain - # text password. - def validate_digest_response(request, realm, &password_procedure) - credentials = decode_credentials_header(request) - valid_nonce = validate_nonce(request, credentials[:nonce]) - - if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] - password = password_procedure.call(credentials[:username]) - return false unless password - - method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] - - [true, false].any? do |password_is_ha1| - expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1) - expected == credentials[:response] - end - end - end - - # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ - # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead - # of a plain-text password. - def expected_response(http_method, uri, credentials, password, password_is_ha1=true) - ha1 = password_is_ha1 ? password : ha1(credentials, password) - ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) - ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) - end - - def ha1(credentials, password) - ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) - end - - def encode_credentials(http_method, credentials, password, password_is_ha1) - credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) - "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') - end - - def decode_credentials_header(request) - decode_credentials(authorization(request)) - end - - def decode_credentials(header) - header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair| - key, value = pair.split('=', 2) - hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '') - hash - end - end - - def authentication_header(controller, realm) - controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") - end - - def authentication_request(controller, realm, message = nil) - message ||= "HTTP Digest: Access denied.\n" - authentication_header(controller, realm) - controller.__send__ :render, :text => message, :status => :unauthorized - end - - # Uses an MD5 digest based on time to generate a value to be used only once. - # - # A server-specified data string which should be uniquely generated each time a 401 response is made. - # It is recommended that this string be base64 or hexadecimal data. - # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. - # - # The contents of the nonce are implementation dependent. - # The quality of the implementation depends on a good choice. - # A nonce might, for example, be constructed as the base 64 encoding of - # - # => time-stamp H(time-stamp ":" ETag ":" private-key) - # - # where time-stamp is a server-generated time or other non-repeating value, - # ETag is the value of the HTTP ETag header associated with the requested entity, - # and private-key is data known only to the server. - # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and - # reject the request if it did not match the nonce from that header or - # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. - # The inclusion of the ETag prevents a replay request for an updated version of the resource. - # (Note: including the IP address of the client in the nonce would appear to offer the server the ability - # to limit the reuse of the nonce to the same client that originally got it. - # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. - # Also, IP address spoofing is not that hard.) - # - # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to - # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for - # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 - # of this document. - # - # The nonce is opaque to the client. Composed of Time, and hash of Time with secret - # key from the Rails session secret generated upon creation of project. Ensures - # the time cannot be modified by client. - def nonce(time = Time.now) - t = time.to_i - hashed = [t, secret_key] - digest = ::Digest::MD5.hexdigest(hashed.join(":")) - ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '') - end - - # Might want a shorter timeout depending on whether the request - # is a PUT or POST, and if client is browser or web service. - # Can be much shorter if the Stale directive is implemented. This would - # allow a user to use new nonce without prompting user again for their - # username and password. - def validate_nonce(request, value, seconds_to_timeout=5*60) - t = ActiveSupport::Base64.decode64(value).split(":").first.to_i - nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout - end - - # Opaque based on random generation - but changing each request? - def opaque() - ::Digest::MD5.hexdigest(secret_key) - end - - # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. - def secret_key - ActionController::Base.session_options[:secret] - end - - end - end -end diff --git a/actionpack/lib/action_controller/base/layouts.rb b/actionpack/lib/action_controller/base/layouts.rb deleted file mode 100644 index 365351b421..0000000000 --- a/actionpack/lib/action_controller/base/layouts.rb +++ /dev/null @@ -1,192 +0,0 @@ -module ActionController - # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in - # repeated setups. The inclusion pattern has pages that look like this: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # // The header part of this layout - # <%= yield %> - # // The footer part of this layout - # - # And then you have content pages that look like this: - # - # hello world - # - # At rendering time, the content page is computed and then inserted in the layout, like this: - # - # // The header part of this layout - # hello world - # // The footer part of this layout - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - #

<%= @page_title %>

- # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - #

Welcome

- # Off-world colonies offers you a chance to start a new life - # - # == Layout assignment - # - # You can either specify a layout declaratively (using the #layout class method) or give - # it the same name as your controller, and place it in app/views/layouts. - # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. - # - # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, - # that template will be used for all actions in PostsController and controllers inheriting - # from PostsController. - # - # If you use a module, for instance Weblog::PostsController, you will need a template named - # app/views/layouts/weblog/posts.html.erb. - # - # Since all your controllers inherit from ApplicationController, they will use - # app/views/layouts/application.html.erb if no other layout is specified - # or provided. - # - # == Inheritance Examples - # - # class BankController < ActionController::Base - # layout "bank_standard" - # - # class InformationController < BankController - # - # class TellerController < BankController - # # teller.html.erb exists - # - # class TillController < TellerController - # - # class VaultController < BankController - # layout :access_level_layout - # - # class EmployeeController < BankController - # layout nil - # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # The TellerController uses +teller.html.erb+, and TillController inherits that layout and - # uses it as well. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # If no directory is specified for the template name, the template will by default be looked for in app/views/layouts/. - # Otherwise, it will be looked up relative to the template root. - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # :only and :except options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. - # - # Both the :only and :except condition can accept an arbitrary number of method references, so - # #:except => [ :rss, :text_only ] is valid, as is :except => :rss. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. - # You can do this by passing a :layout option to the render call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # def help - # render :action => "help", :layout => "help" - # end - # end - # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. - module Layouts - extend ActiveSupport::Concern - - include ActionController::Renderer - include AbstractController::Layouts - - module ClassMethods - # If no layout is provided, look for a layout with this name. - def _implied_layout_name - controller_path - end - end - - private - def _determine_template(options) - super - - return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - def _layout_for_option(name, details) - case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) - when false, nil then nil - else - raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end - end -end diff --git a/actionpack/lib/action_controller/base/metal.rb b/actionpack/lib/action_controller/base/metal.rb deleted file mode 100644 index e7d776b63e..0000000000 --- a/actionpack/lib/action_controller/base/metal.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'action_controller/abstract' - -module ActionController - # ActionController::Metal provides a way to get a valid Rack application from a controller. - # - # In AbstractController, dispatching is triggered directly by calling #process on a new controller. - # ActionController::Metal provides an #action method that returns a valid Rack application for a - # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router, - # can dispatch directly to the action returned by FooController.action(:index). - class Metal < AbstractController::Base - abstract! - - # :api: public - attr_internal :params, :env - - # Returns the last part of the controller's name, underscored, without the ending - # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for - # controller_name - # - # ==== Returns - # String - def self.controller_name - @controller_name ||= controller_path.split("/").last - end - - # Delegates to the class' #controller_name - def controller_name - self.class.controller_name - end - - # Returns the full controller name, underscored, without the ending Controller. - # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_name. - # - # ==== Returns - # String - def self.controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore - end - - # Delegates to the class' #controller_path - def controller_path - self.class.controller_path - end - - # The details below can be overridden to support a specific - # Request and Response object. The default ActionController::Base - # implementation includes RackConvenience, which makes a request - # and response object available. You might wish to control the - # environment and response manually for performance reasons. - - attr_internal :status, :headers, :content_type - - def initialize(*) - @_headers = {} - super - end - - # Basic implementations for content_type=, location=, and headers are - # provided to reduce the dependency on the RackConvenience module - # in Renderer and Redirector. - - def content_type=(type) - headers["Content-Type"] = type.to_s - end - - def location=(url) - headers["Location"] = url - end - - # :api: private - def call(name, env) - @_env = env - process(name) - to_rack - end - - # :api: private - def to_rack - [status, headers, response_body] - end - - # Return a rack endpoint for the given action. Memoize the endpoint, so - # multiple calls into MyController.action will return the same object - # for the same action. - # - # ==== Parameters - # action<#to_s>:: An action name - # - # ==== Returns - # Proc:: A rack application - def self.action(name) - @actions ||= {} - @actions[name.to_s] ||= proc do |env| - new.call(name, env) - end - end - end -end diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb deleted file mode 100644 index f4a4007a43..0000000000 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ /dev/null @@ -1,353 +0,0 @@ -module ActionController #:nodoc: - module MimeResponds #:nodoc: - extend ActiveSupport::Concern - - included do - class_inheritable_reader :mimes_for_respond_to - clear_respond_to - end - - module ClassMethods - # Defines mimes that are rendered by default when invoking respond_with. - # - # Examples: - # - # respond_to :html, :xml, :json - # - # All actions on your controller will respond to :html, :xml and :json. - # - # But if you want to specify it based on your actions, you can use only and - # except: - # - # respond_to :html - # respond_to :xml, :json, :except => [ :edit ] - # - # The definition above explicits that all actions respond to :html. And all - # actions except :edit respond to :xml and :json. - # - # You can specify also only parameters: - # - # respond_to :rjs, :only => :create - # - def respond_to(*mimes) - options = mimes.extract_options! - - only_actions = Array(options.delete(:only)) - except_actions = Array(options.delete(:except)) - - mimes.each do |mime| - mime = mime.to_sym - mimes_for_respond_to[mime] = {} - mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty? - mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty? - end - end - - # Clear all mimes in respond_to. - # - def clear_respond_to - write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new) - end - end - - # Without web-service support, an action which collects the data for displaying a list of people - # might look something like this: - # - # def index - # @people = Person.find(:all) - # end - # - # Here's the same action, with web-service support baked in: - # - # def index - # @people = Person.find(:all) - # - # respond_to do |format| - # format.html - # format.xml { render :xml => @people.to_xml } - # end - # end - # - # What that says is, "if the client wants HTML in response to this action, just respond as we - # would have before, but if the client wants XML, return them the list of people in XML format." - # (Rails determines the desired response format from the HTTP Accept header submitted by the client.) - # - # Supposing you have an action that adds a new person, optionally creating their company - # (by name) if it does not already exist, without web-services, it might look like this: - # - # def create - # @company = Company.find_or_create_by_name(params[:company][:name]) - # @person = @company.people.create(params[:person]) - # - # redirect_to(person_list_url) - # end - # - # Here's the same action, with web-service support baked in: - # - # def create - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) - # @person = @company.people.create(params[:person]) - # - # respond_to do |format| - # format.html { redirect_to(person_list_url) } - # format.js - # format.xml { render :xml => @person.to_xml(:include => @company) } - # end - # end - # - # If the client wants HTML, we just redirect them back to the person list. If they want Javascript - # (format.js), then it is an RJS request and we render the RJS template associated with this action. - # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also - # include the person's company in the rendered XML, so you get something like this: - # - # - # ... - # ... - # - # ... - # ... - # ... - # - # - # - # Note, however, the extra bit at the top of that action: - # - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) - # - # This is because the incoming XML document (if a web-service request is in process) can only contain a - # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): - # - # person[name]=...&person[company][name]=...&... - # - # And, like this (xml-encoded): - # - # - # ... - # - # ... - # - # - # - # In other words, we make the request so that it operates on a single entity's person. Then, in the action, - # we extract the company data from the request, find or create the company, and then create the new person - # with the remaining data. - # - # Note that you can define your own XML parameter parser which would allow you to describe multiple entities - # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow - # and accept Rails' defaults, life will be much easier. - # - # If you need to use a MIME type which isn't supported by default, you can register your own handlers in - # environment.rb as follows. - # - # Mime::Type.register "image/jpg", :jpg - # - # Respond to also allows you to specify a common block for different formats by using any: - # - # def index - # @people = Person.find(:all) - # - # respond_to do |format| - # format.html - # format.any(:xml, :json) { render request.format.to_sym => @people } - # end - # end - # - # In the example above, if the format is xml, it will render: - # - # render :xml => @people - # - # Or if the format is json: - # - # render :json => @people - # - # Since this is a common pattern, you can use the class method respond_to - # with the respond_with method to have the same results: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@person) - # end - # end - # - # Be sure to check respond_with and respond_to documentation for more examples. - # - def respond_to(*mimes, &block) - options = mimes.extract_options! - raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - - resource = options.delete(:with) - responder = Responder.new - - mimes = collect_mimes_from_class_level if mimes.empty? - mimes.each { |mime| responder.send(mime) } - block.call(responder) if block_given? - - if format = request.negotiate_mime(responder.order) - respond_to_block_or_template_or_resource(format, resource, - options, &responder.response_for(format)) - else - head :not_acceptable - end - end - - # respond_with allows you to respond an action with a given resource. It - # requires that you set your class with a :respond_to method with the - # formats allowed: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@person) - # end - # end - # - # When a request comes with format :xml, the respond_with will first search - # for a template as person/index.xml, if the template is not available, it - # will see if the given resource responds to :to_xml. - # - # If neither are available, it will raise an error. - # - # Extra parameters given to respond_with are used when :to_format is invoked. - # This allows you to set status and location for several formats at the same - # time. Consider this restful controller response on create for both xml - # and json formats: - # - # class PeopleController < ApplicationController - # respond_to :xml, :json - # - # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # respond_with(@person, :status => :ok, :location => person_url(@person)) - # else - # respond_with(@person.errors, :status => :unprocessable_entity) - # end - # end - # end - # - # Finally, respond_with also accepts blocks, as in respond_to. Let's take - # the same controller and create action above and add common html behavior: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # options = { :status => :ok, :location => person_url(@person) } - # - # respond_with(@person, options) do |format| - # format.html { redirect_to options[:location] } - # end - # else - # respond_with(@person.errors, :status => :unprocessable_entity) do - # format.html { render :action => :new } - # end - # end - # end - # end - # - def respond_with(resource, options={}, &block) - respond_to(options.merge!(:with => resource), &block) - end - - protected - - def respond_to_block_or_template_or_resource(format, resource, options) - self.formats = [format.to_sym] - return yield if block_given? - - begin - default_render - rescue ActionView::MissingTemplate => e - if resource && resource.respond_to?(:"to_#{format.to_sym}") - render options.merge(format.to_sym => resource) - else - raise e - end - end - end - - # Collect mimes declared in the class method respond_to valid for the - # current action. - # - def collect_mimes_from_class_level #:nodoc: - action = action_name.to_sym - - mimes_for_respond_to.keys.select do |mime| - config = mimes_for_respond_to[mime] - - if config[:except] - !config[:except].include?(action) - elsif config[:only] - config[:only].include?(action) - else - true - end - end - end - - class Responder #:nodoc: - attr_accessor :order - - def initialize - @order, @responses = [], {} - end - - def any(*args, &block) - if args.any? - args.each { |type| send(type, &block) } - else - custom(Mime::ALL, &block) - end - end - alias :all :any - - def custom(mime_type, &block) - mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) - - @order << mime_type - @responses[mime_type] ||= block - end - - def response_for(mime) - @responses[mime] || @responses[Mime::ALL] - 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 - - Mime::SET.each do |mime| - generate_method_for_mime(mime) - end - - 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 - end - - end - end -end diff --git a/actionpack/lib/action_controller/base/rack_convenience.rb b/actionpack/lib/action_controller/base/rack_convenience.rb deleted file mode 100644 index 805157b0e3..0000000000 --- a/actionpack/lib/action_controller/base/rack_convenience.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActionController - module RackConvenience - extend ActiveSupport::Concern - - included do - delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :to => "@_response" - attr_internal :request, :response - end - - def call(name, env) - @_request = ActionDispatch::Request.new(env) - @_response = ActionDispatch::Response.new - @_response.request = request - super - end - - def params - @_params ||= @_request.parameters - end - - # :api: private - def to_rack - @_response.prepare! - @_response.to_a - end - - def response_body=(body) - response.body = body if response - super - end - end -end diff --git a/actionpack/lib/action_controller/base/redirector.rb b/actionpack/lib/action_controller/base/redirector.rb deleted file mode 100644 index 20060b001f..0000000000 --- a/actionpack/lib/action_controller/base/redirector.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActionController - class RedirectBackError < AbstractController::Error #:nodoc: - DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - module Redirector - def redirect_to(url, status) #:doc: - raise AbstractController::DoubleRenderError if response_body - logger.info("Redirected to #{url}") if logger && logger.info? - self.status = status - self.location = url.gsub(/[\r\n]/, '') - self.response_body = "You are being redirected." - end - end -end diff --git a/actionpack/lib/action_controller/base/render_options.rb b/actionpack/lib/action_controller/base/render_options.rb deleted file mode 100644 index 65ee09883e..0000000000 --- a/actionpack/lib/action_controller/base/render_options.rb +++ /dev/null @@ -1,103 +0,0 @@ -module ActionController - module RenderOptions - extend ActiveSupport::Concern - - included do - extlib_inheritable_accessor :_renderers - self._renderers = [] - end - - module ClassMethods - def _write_render_options - renderers = _renderers.map do |r| - <<-RUBY_EVAL - if options.key?(:#{r}) - _process_options(options) - return _render_#{r}(options[:#{r}], options) - end - RUBY_EVAL - end - - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _handle_render_options(options) - #{renderers.join} - end - RUBY_EVAL - end - - def _add_render_option(name) - _renderers << name - _write_render_options - end - end - - def render_to_body(options) - _handle_render_options(options) || super - end - end - - module RenderOption #:nodoc: - def self.extended(base) - base.extend ActiveSupport::Concern - base.send :include, ::ActionController::RenderOptions - - def base.register_renderer(name) - included { _add_render_option(name) } - end - end - end - - module Renderers - module Json - extend RenderOption - register_renderer :json - - def _render_json(json, options) - json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - self.response_body = json - end - end - - module Js - extend RenderOption - register_renderer :js - - def _render_js(js, options) - self.content_type ||= Mime::JS - self.response_body = js - end - end - - module Xml - extend RenderOption - register_renderer :xml - - def _render_xml(xml, options) - self.content_type ||= Mime::XML - self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml - end - end - - module RJS - extend RenderOption - register_renderer :update - - def _render_update(proc, options) - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) - self.content_type = Mime::JS - self.response_body = generator.to_s - end - end - - module All - extend ActiveSupport::Concern - - include ActionController::Renderers::Json - include ActionController::Renderers::Js - include ActionController::Renderers::Xml - include ActionController::Renderers::RJS - end - end -end diff --git a/actionpack/lib/action_controller/base/renderer.rb b/actionpack/lib/action_controller/base/renderer.rb deleted file mode 100644 index 572da451ff..0000000000 --- a/actionpack/lib/action_controller/base/renderer.rb +++ /dev/null @@ -1,77 +0,0 @@ -module ActionController - module Renderer - extend ActiveSupport::Concern - - include AbstractController::Renderer - - def process_action(*) - self.formats = request.formats.map {|x| x.to_sym} - super - end - - def render(options) - super - self.content_type ||= begin - mime = options[:_template].mime_type - formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) - end.to_s - response_body - end - - def render_to_body(options) - _process_options(options) - - if options.key?(:partial) - _render_partial(options[:partial], options) - end - - super - end - - private - def _prefix - controller_path - end - - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] - elsif !options.key?(:partial) - options[:_template_name] = (options[:action] || action_name).to_s - options[:_prefix] = _prefix - end - - super - end - - def _render_partial(partial, options) - case partial - when true - options[:_prefix] = _prefix - when String - options[:_prefix] = _prefix unless partial.index('/') - options[:_template_name] = partial - else - options[:_partial_object] = true - return - end - - options[:_partial] = options[:object] || true - end - - def _process_options(options) - status, content_type, location = options.values_at(:status, :content_type, :location) - self.status = status if status - self.content_type = content_type if content_type - self.headers["Location"] = url_for(location) if location - end - end -end diff --git a/actionpack/lib/action_controller/base/request_forgery_protection.rb b/actionpack/lib/action_controller/base/request_forgery_protection.rb deleted file mode 100644 index ad06657f86..0000000000 --- a/actionpack/lib/action_controller/base/request_forgery_protection.rb +++ /dev/null @@ -1,118 +0,0 @@ -module ActionController #:nodoc: - class InvalidAuthenticityToken < ActionControllerError #:nodoc: - end - - module RequestForgeryProtection - extend ActiveSupport::Concern - - # TODO : Remove the defined? check when new base is the main base - include AbstractController::Helpers, Session - - included do - # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ - # sets it to :authenticity_token by default. - cattr_accessor :request_forgery_protection_token - - # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. - class_inheritable_accessor :allow_forgery_protection - self.allow_forgery_protection = true - - helper_method :form_authenticity_token - helper_method :protect_against_forgery? - end - - # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a - # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all - # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only - # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication - # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway. - # - # This is turned on with the protect_from_forgery method, which will check the token and raise an - # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in - # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0 - # applications. - # - # The token parameter is named authenticity_token by default. If you are generating an HTML form manually (without the - # use of Rails' form_for, form_tag or other helpers), you have to include a hidden field named like that and - # set its value to what is returned by form_authenticity_token. Same applies to manually constructed Ajax requests. To - # make the token available through a global variable to scripts on a certain page, you could add something like this to a view: - # - # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> - # - # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to - # config/environments/test.rb: - # - # # Disable request forgery protection in test environment - # config.action_controller.allow_forgery_protection = false - # - # == Learn more about CSRF (Cross-Site Request Forgery) attacks - # - # Here are some resources: - # * http://isc.sans.org/diary.html?storyid=1750 - # * http://en.wikipedia.org/wiki/Cross-site_request_forgery - # - # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. - # There are a few guidelines you should follow: - # - # * Keep your GET requests safe and idempotent. More reading material: - # * http://www.xml.com/pub/a/2002/04/24/deviant.html - # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 - # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session" - # - module ClassMethods - # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. - # - # Example: - # - # class FooController < ApplicationController - # protect_from_forgery :except => :index - # - # # you can disable csrf protection on controller-by-controller basis: - # skip_before_filter :verify_authenticity_token - # end - # - # Valid Options: - # - # * :only/:except - Passed to the before_filter call. Set which actions are verified. - def protect_from_forgery(options = {}) - self.request_forgery_protection_token ||= :authenticity_token - before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) - if options[:secret] || options[:digest] - ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller) - end - end - end - - protected - # The actual before_filter that is used. Modify this to change how you handle unverified requests. - def verify_authenticity_token - verified_request? || raise(ActionController::InvalidAuthenticityToken) - end - - # Returns true or false if a request is verified. Checks: - # - # * is the format restricted? By default, only HTML requests are checked. - # * is it a GET request? Gets should be safe and idempotent - # * Does the form_authenticity_token match the given token value from the params? - def verified_request? - !protect_against_forgery? || - request.method == :get || - request.xhr? || - !verifiable_request_format? || - form_authenticity_token == params[request_forgery_protection_token] - end - - def verifiable_request_format? - !request.content_type.nil? && request.content_type.verify_request? - end - - # Sets the token value for the current session. - def form_authenticity_token - session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) - end - - def protect_against_forgery? - allow_forgery_protection && request_forgery_protection_token - end - end -end diff --git a/actionpack/lib/action_controller/base/rescuable.rb b/actionpack/lib/action_controller/base/rescuable.rb deleted file mode 100644 index 029e643d93..0000000000 --- a/actionpack/lib/action_controller/base/rescuable.rb +++ /dev/null @@ -1,52 +0,0 @@ -module ActionController #:nodoc: - # Actions that fail to perform as expected throw exceptions. These - # exceptions can either be rescued for the public view (with a nice - # user-friendly explanation) or for the developers view (with tons of - # debugging information). The developers view is already implemented by - # the Action Controller, but the public view should be tailored to your - # specific application. - # - # The default behavior for public exceptions is to render a static html - # file with the name of the error code thrown. If no such file exists, an - # empty response is sent with the correct status code. - # - # You can override what constitutes a local request by overriding the - # local_request? method in your own controller. Custom rescue - # behavior is achieved by overriding the rescue_action_in_public - # and rescue_action_locally methods. - module Rescue - extend ActiveSupport::Concern - - included do - include ActiveSupport::Rescuable - end - - module ClassMethods - # This can be removed once we can move action(:_rescue_action) into middlewares.rb - # Currently, it does controller.method(:rescue_action), which is hiding the implementation - # difference between the old and new base. - def rescue_action(env) - action(:_rescue_action).call(env) - end - end - - attr_internal :rescued_exception - - private - def method_for_action(action_name) - return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception") - super - end - - def _rescue_action - rescue_with_handler(rescued_exception) || raise(rescued_exception) - end - - def process_action(*) - super - rescue Exception => exception - self.rescued_exception = exception - _rescue_action - end - end -end diff --git a/actionpack/lib/action_controller/base/session.rb b/actionpack/lib/action_controller/base/session.rb deleted file mode 100644 index bcedd6e1c7..0000000000 --- a/actionpack/lib/action_controller/base/session.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActionController - module Session - extend ActiveSupport::Concern - - include RackConvenience - - def session - @_request.session - end - - def reset_session - @_request.reset_session - end - end -end diff --git a/actionpack/lib/action_controller/base/session_management.rb b/actionpack/lib/action_controller/base/session_management.rb deleted file mode 100644 index ffce8e1bd1..0000000000 --- a/actionpack/lib/action_controller/base/session_management.rb +++ /dev/null @@ -1,54 +0,0 @@ -module ActionController #:nodoc: - module SessionManagement #:nodoc: - def self.included(base) - base.class_eval do - extend ClassMethods - end - end - - module ClassMethods - # Set the session store to be used for keeping the session data between requests. - # By default, sessions are stored in browser cookies (:cookie_store), - # but you can also specify one of the other included stores (:active_record_store, - # :mem_cache_store, or your own custom class. - def session_store=(store) - if store == :active_record_store - self.session_store = ActiveRecord::SessionStore - else - @@session_store = store.is_a?(Symbol) ? - Session.const_get(store.to_s.camelize) : - store - end - end - - # Returns the session store class currently used. - def session_store - if defined? @@session_store - @@session_store - else - ActionDispatch::Session::CookieStore - end - end - - def session=(options = {}) - self.session_store = nil if options.delete(:disabled) - session_options.merge!(options) - end - - # Returns the hash used to configure the session. Example use: - # - # ActionController::Base.session_options[:secure] = true # session only available over HTTPS - def session_options - @session_options ||= {} - end - - def session(*args) - ActiveSupport::Deprecation.warn( - "Disabling sessions for a single controller has been deprecated. " + - "Sessions are now lazy loaded. So if you don't access them, " + - "consider them off. You can still modify the session cookie " + - "options with request.session_options.", caller) - end - end - end -end diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb deleted file mode 100644 index f0317c6e99..0000000000 --- a/actionpack/lib/action_controller/base/streaming.rb +++ /dev/null @@ -1,187 +0,0 @@ -require 'active_support/core_ext/string/bytesize' - -module ActionController #:nodoc: - # Methods for sending arbitrary data and for streaming files to the browser, - # instead of rendering. - module Streaming - extend ActiveSupport::Concern - - include ActionController::Renderer - - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - :stream => true, - :buffer_size => 4096, - :x_sendfile => false - }.freeze - - X_SENDFILE_HEADER = 'X-Sendfile'.freeze - - protected - # Sends the file, by default streaming it 4096 bytes at a time. This way the - # whole file doesn't need to be read into memory at once. This makes it - # feasible to send even large files. You can optionally turn off streaming - # and send the whole file at once. - # - # Be careful to sanitize the path parameter if it is coming from a web - # page. send_file(params[:path]) allows a malicious user to - # download any file on your server. - # - # Options: - # * :filename - suggests a filename for the browser to use. - # Defaults to File.basename(path). - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with Mime::Type.register, for example :json - # * :length - used to manually override the length (in bytes) of the content that - # is going to be sent to the client. Defaults to File.size(path). - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * :stream - whether to send the file to the user agent as it is read (+true+) - # or to read the entire file before sending (+false+). Defaults to +true+. - # * :buffer_size - specifies size (in bytes) of the buffer used to stream the file. - # Defaults to 4096. - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # * :url_based_filename - set to +true+ if you want the browser guess the filename from - # the URL, which is necessary for i18n filenames on certain browsers - # (setting :filename overrides this option). - # * :x_sendfile - uses X-Sendfile to send the file when set to +true+. This is currently - # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this - # uses the web server to send the file, this may lower memory consumption on your server and - # it will not block your application for further requests. - # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and - # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. - # - # The default Content-Type and Content-Disposition headers are - # set to download arbitrary binary files in as many browsers as - # possible. IE versions 4, 5, 5.5, and 6 are all known to have - # a variety of quirks (especially when downloading over SSL). - # - # Simple download: - # - # send_file '/path/to.zip' - # - # Show a JPEG in the browser: - # - # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' - # - # Show a 404 page in the browser: - # - # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 - # - # Read about the other Content-* HTTP headers if you'd like to - # provide the user with more information (such as Content-Description) in - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. - # - # Also be aware that the document may be cached by proxies and browsers. - # The Pragma and Cache-Control headers declare how the file may be cached - # by intermediaries. They default to require clients to validate with - # the server before releasing cached responses. See - # http://www.mnot.net/cache_docs/ for an overview of web caching and - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 - # for the Cache-Control header spec. - def send_file(path, options = {}) #:doc: - raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) - - options[:length] ||= File.size(path) - options[:filename] ||= File.basename(path) unless options[:url_based_filename] - send_file_headers! options - - @performed_render = false - - if options[:x_sendfile] - logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger - head options[:status], X_SENDFILE_HEADER => path - else - if options[:stream] - # TODO : Make render :text => proc {} work with the new base - render :status => options[:status], :text => Proc.new { |response, output| - logger.info "Streaming file #{path}" unless logger.nil? - len = options[:buffer_size] || 4096 - File.open(path, 'rb') do |file| - while buf = file.read(len) - output.write(buf) - end - end - } - else - logger.info "Sending file #{path}" unless logger.nil? - File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } - end - end - end - - # Sends the given binary data to the browser. This method is similar to - # render :text => data, but also allows you to specify whether - # the browser should display the response as a file attachment (i.e. in a - # download dialog) or as inline data. You may also set the content type, - # the apparent file name, and other things. - # - # Options: - # * :filename - suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with Mime::Type.register, for example :json - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # - # Generic data download: - # - # send_data buffer - # - # Download a dynamically-generated tarball: - # - # send_data generate_tgz('dir'), :filename => 'dir.tgz' - # - # Display an image Active Record in the browser: - # - # send_data image.data, :type => image.content_type, :disposition => 'inline' - # - # See +send_file+ for more information on HTTP Content-* headers and caching. - # - # Tip: if you want to stream large amounts of on-the-fly generated - # data to the browser, then use render :text => proc { ... } - # instead. See ActionController::Base#render for more information. - def send_data(data, options = {}) #:doc: - logger.info "Sending data #{options[:filename]}" if logger - send_file_headers! options.merge(:length => data.bytesize) - @performed_render = false - render :status => options[:status], :text => data - end - - private - def send_file_headers!(options) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:length, :type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition].dup || 'attachment' - - disposition <<= %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] - - if content_type.is_a?(Symbol) - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s) - self.content_type = Mime::Type.lookup_by_extension(content_type.to_s) - else - self.content_type = content_type - end - - headers.merge!( - 'Content-Length' => options[:length].to_s, - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) - - # Fix a problem with IE 6.0 on opening downloaded files: - # If Cache-Control: no-cache is set (which Rails does by default), - # IE removes the file it just downloaded from its cache immediately - # after it displays the "open/save" dialog, which means that if you - # hit "open" the file isn't there anymore when the application that - # is called for handling the download is run, so let's workaround that - response.cache_control[:public] ||= false - end - end -end diff --git a/actionpack/lib/action_controller/base/testing.rb b/actionpack/lib/action_controller/base/testing.rb deleted file mode 100644 index a4a1116d9e..0000000000 --- a/actionpack/lib/action_controller/base/testing.rb +++ /dev/null @@ -1,39 +0,0 @@ -module ActionController - module Testing - extend ActiveSupport::Concern - - include RackConvenience - - # OMG MEGA HAX - def process_with_new_base_test(request, response) - @_request = request - @_response = response - @_response.request = request - ret = process(request.parameters[:action]) - @_response.body ||= self.response_body - @_response.prepare! - set_test_assigns - ret - end - - 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 - end - end - - # TODO : Rewrite tests using controller.headers= to use Rack env - def headers=(new_headers) - @_response ||= ActionDispatch::Response.new - @_response.headers.replace(new_headers) - end - - module ClassMethods - def before_filters - _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} - end - end - end -end diff --git a/actionpack/lib/action_controller/base/url_for.rb b/actionpack/lib/action_controller/base/url_for.rb deleted file mode 100644 index 7119c14cd3..0000000000 --- a/actionpack/lib/action_controller/base/url_for.rb +++ /dev/null @@ -1,49 +0,0 @@ -module ActionController - module UrlFor - extend ActiveSupport::Concern - - include RackConvenience - - def process_action(*) - initialize_current_url - super - end - - def initialize_current_url - @url = UrlRewriter.new(request, params.clone) - end - - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options = nil) - end - - def rewrite_options(options) #:nodoc: - if defaults = default_url_options(options) - defaults.merge(options) - else - options - end - end - - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - @url.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end - end - end -end diff --git a/actionpack/lib/action_controller/base/verification.rb b/actionpack/lib/action_controller/base/verification.rb deleted file mode 100644 index 951ae1bee1..0000000000 --- a/actionpack/lib/action_controller/base/verification.rb +++ /dev/null @@ -1,130 +0,0 @@ -module ActionController #:nodoc: - module Verification #:nodoc: - extend ActiveSupport::Concern - - include AbstractController::Callbacks, Session, Flash, Renderer - - # This module provides a class-level method for specifying that certain - # actions are guarded against being called without certain prerequisites - # being met. This is essentially a special kind of before_filter. - # - # An action may be guarded against being invoked without certain request - # parameters being set, or without certain session values existing. - # - # When a verification is violated, values may be inserted into the flash, and - # a specified redirection is triggered. If no specific action is configured, - # verification failures will by default result in a 400 Bad Request response. - # - # Usage: - # - # class GlobalController < ActionController::Base - # # Prevent the #update_settings action from being invoked unless - # # the 'admin_privileges' request parameter exists. The - # # settings action will be redirected to in current controller - # # if verification fails. - # verify :params => "admin_privileges", :only => :update_post, - # :redirect_to => { :action => "settings" } - # - # # Disallow a post from being updated if there was no information - # # submitted with the post, and if there is no active post in the - # # session, and if there is no "note" key in the flash. The route - # # named category_url will be redirected to if verification fails. - # - # verify :params => "post", :session => "post", "flash" => "note", - # :only => :update_post, - # :add_flash => { "alert" => "Failed to create your message" }, - # :redirect_to => :category_url - # - # Note that these prerequisites are not business rules. They do not examine - # the content of the session or the parameters. That level of validation should - # be encapsulated by your domain model or helper methods in the controller. - module ClassMethods - # Verify the given actions so that if certain prerequisites are not met, - # the user is redirected to a different action. The +options+ parameter - # is a hash consisting of the following key/value pairs: - # - # :params:: - # a single key or an array of keys that must be in the params - # hash in order for the action(s) to be safely called. - # :session:: - # a single key or an array of keys that must be in the session - # in order for the action(s) to be safely called. - # :flash:: - # a single key or an array of keys that must be in the flash in order - # for the action(s) to be safely called. - # :method:: - # a single key or an array of keys--any one of which must match the - # current request method in order for the action(s) to be safely called. - # (The key should be a symbol: :get or :post, for - # example.) - # :xhr:: - # true/false option to ensure that the request is coming from an Ajax - # call or not. - # :add_flash:: - # a hash of name/value pairs that should be merged into the session's - # flash if the prerequisites cannot be satisfied. - # :add_headers:: - # a hash of name/value pairs that should be merged into the response's - # headers hash if the prerequisites cannot be satisfied. - # :redirect_to:: - # the redirection parameters to be used when redirecting if the - # prerequisites cannot be satisfied. You can redirect either to named - # route or to the action in some controller. - # :render:: - # the render parameters to be used when the prerequisites cannot be satisfied. - # :only:: - # only apply this verification to the actions specified in the associated - # array (may also be a single value). - # :except:: - # do not apply this verification to the actions specified in the associated - # array (may also be a single value). - def verify(options={}) - before_filter :only => options[:only], :except => options[:except] do |c| - c.__send__ :verify_action, options - end - end - end - - private - - def verify_action(options) #:nodoc: - if prereqs_invalid?(options) - flash.update(options[:add_flash]) if options[:add_flash] - response.headers.merge!(options[:add_headers]) if options[:add_headers] - apply_remaining_actions(options) unless performed? - end - end - - def prereqs_invalid?(options) # :nodoc: - verify_presence_of_keys_in_hash_flash_or_params(options) || - verify_method(options) || - verify_request_xhr_status(options) - end - - def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc: - [*options[:params] ].find { |v| v && params[v.to_sym].nil? } || - [*options[:session]].find { |v| session[v].nil? } || - [*options[:flash] ].find { |v| flash[v].nil? } - end - - def verify_method(options) # :nodoc: - [*options[:method]].all? { |v| request.method != v.to_sym } if options[:method] - end - - def verify_request_xhr_status(options) # :nodoc: - request.xhr? != options[:xhr] unless options[:xhr].nil? - end - - def apply_redirect_to(redirect_to_option) # :nodoc: - (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option - end - - def apply_remaining_actions(options) # :nodoc: - case - when options[:render] ; render(options[:render]) - when options[:redirect_to] ; redirect_to(apply_redirect_to(options[:redirect_to])) - else head(:bad_request) - end - end - end -end \ No newline at end of file diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb new file mode 100644 index 0000000000..e7d776b63e --- /dev/null +++ b/actionpack/lib/action_controller/metal.rb @@ -0,0 +1,99 @@ +require 'action_controller/abstract' + +module ActionController + # ActionController::Metal provides a way to get a valid Rack application from a controller. + # + # In AbstractController, dispatching is triggered directly by calling #process on a new controller. + # ActionController::Metal provides an #action method that returns a valid Rack application for a + # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router, + # can dispatch directly to the action returned by FooController.action(:index). + class Metal < AbstractController::Base + abstract! + + # :api: public + attr_internal :params, :env + + # Returns the last part of the controller's name, underscored, without the ending + # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for + # controller_name + # + # ==== Returns + # String + def self.controller_name + @controller_name ||= controller_path.split("/").last + end + + # Delegates to the class' #controller_name + def controller_name + self.class.controller_name + end + + # Returns the full controller name, underscored, without the ending Controller. + # For instance, MyApp::MyPostsController would return "my_app/my_posts" for + # controller_name. + # + # ==== Returns + # String + def self.controller_path + @controller_path ||= name && name.sub(/Controller$/, '').underscore + end + + # Delegates to the class' #controller_path + def controller_path + self.class.controller_path + end + + # The details below can be overridden to support a specific + # Request and Response object. The default ActionController::Base + # implementation includes RackConvenience, which makes a request + # and response object available. You might wish to control the + # environment and response manually for performance reasons. + + attr_internal :status, :headers, :content_type + + def initialize(*) + @_headers = {} + super + end + + # Basic implementations for content_type=, location=, and headers are + # provided to reduce the dependency on the RackConvenience module + # in Renderer and Redirector. + + def content_type=(type) + headers["Content-Type"] = type.to_s + end + + def location=(url) + headers["Location"] = url + end + + # :api: private + def call(name, env) + @_env = env + process(name) + to_rack + end + + # :api: private + def to_rack + [status, headers, response_body] + end + + # Return a rack endpoint for the given action. Memoize the endpoint, so + # multiple calls into MyController.action will return the same object + # for the same action. + # + # ==== Parameters + # action<#to_s>:: An action name + # + # ==== Returns + # Proc:: A rack application + def self.action(name) + @actions ||= {} + @actions[name.to_s] ||= proc do |env| + new.call(name, env) + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb new file mode 100644 index 0000000000..23e7b1b3af --- /dev/null +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -0,0 +1,147 @@ +module ActionController + module Rails2Compatibility + extend ActiveSupport::Concern + + class ::ActionController::ActionControllerError < StandardError #:nodoc: + end + + # Temporary hax + included do + ::ActionController::UnknownAction = ::AbstractController::ActionNotFound + ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError + + cattr_accessor :session_options + self.session_options = {} + + cattr_accessor :allow_concurrency + self.allow_concurrency = false + + cattr_accessor :param_parsers + self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form, + Mime::URL_ENCODED_FORM => :url_encoded_form, + Mime::XML => :xml_simple, + Mime::JSON => :json } + + cattr_accessor :relative_url_root + self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] + + cattr_accessor :default_charset + self.default_charset = "utf-8" + + # cattr_reader :protected_instance_variables + cattr_accessor :protected_instance_variables + self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller + @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params + @_flash @_response) + + # Indicates whether or not optimise the generated named + # route helper methods + cattr_accessor :optimise_named_routes + self.optimise_named_routes = true + + cattr_accessor :resources_path_names + self.resources_path_names = { :new => 'new', :edit => 'edit' } + + # Controls the resource action separator + cattr_accessor :resource_action_separator + self.resource_action_separator = "/" + + cattr_accessor :use_accept_header + self.use_accept_header = true + + cattr_accessor :page_cache_directory + self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" + + cattr_reader :cache_store + + cattr_accessor :consider_all_requests_local + self.consider_all_requests_local = true + + # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, + # and images to a dedicated asset server away from the main web server. Example: + # ActionController::Base.asset_host = "http://assets.example.com" + cattr_accessor :asset_host + + cattr_accessor :ip_spoofing_check + self.ip_spoofing_check = true + end + + # For old tests + def initialize_template_class(*) end + def assign_shortcuts(*) end + + # TODO: Remove this after we flip + def template + @template ||= view_context + end + + def process_action(*) + template + super + end + + module ClassMethods + def consider_all_requests_local + end + + def rescue_action(env) + raise env["action_dispatch.rescue.exception"] + end + + # Defines the storage option for cached fragments + def cache_store=(store_option) + @@cache_store = ActiveSupport::Cache.lookup_store(store_option) + end + end + + def render_to_body(options) + if options.is_a?(Hash) && options.key?(:template) + options[:template].sub!(/^\//, '') + end + + options[:text] = nil if options[:nothing] == true + + body = super + body = [' '] if body.blank? + body + end + + def _handle_method_missing + method_missing(@_action_name.to_sym) + end + + def method_for_action(action_name) + super || (respond_to?(:method_missing) && "_handle_method_missing") + end + + def _find_layout(name, details) + details[:prefix] = nil if name =~ /\blayouts/ + super + end + + # Move this into a "don't run in production" module + def _default_layout(details, require_layout = false) + super + rescue ActionView::MissingTemplate + _find_layout(_layout({}), {}) + nil + end + + def performed? + response_body + end + + # ==== Request only view path switching ==== + def append_view_path(path) + view_paths.push(*path) + end + + def prepend_view_path(path) + view_paths.unshift(*path) + end + + def view_paths + view_context.view_paths + end + end +end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb new file mode 100644 index 0000000000..6d35137428 --- /dev/null +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -0,0 +1,118 @@ +module ActionController + module ConditionalGet + extend ActiveSupport::Concern + + include RackConvenience + + # Sets the etag, last_modified, or both on the response and renders a + # "304 Not Modified" response if the request is already fresh. + # + # Parameters: + # * :etag + # * :last_modified + # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # + # Example: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true) + # end + # + # 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, :public) + + response.etag = options[:etag] if options[:etag] + response.last_modified = options[:last_modified] if options[:last_modified] + + if options[:public] + response.cache_control[:public] = true + end + + if request.fresh?(response) + head :not_modified + end + end + + # Return a response that has no content (merely headers). The options + # argument is interpreted to be a hash of header names and values. + # This allows you to easily return a response that consists only of + # significant headers: + # + # head :created, :location => person_path(@person) + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + def head(*args) + if args.length > 2 + raise ArgumentError, "too many arguments to head" + elsif args.empty? + raise ArgumentError, "too few arguments to head" + end + options = args.extract_options! + status = args.shift || options.delete(:status) || :ok + + options.each do |key, value| + headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s + end + + render :nothing => true, :status => status + end + + # Sets the etag and/or last_modified on the response and checks it against + # the client request. If the request doesn't match the options provided, the + # request is considered stale and should be generated from scratch. Otherwise, + # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent. + # + # Parameters: + # * :etag + # * :last_modified + # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # + # Example: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(:etag => @article, :last_modified => @article.created_at.utc) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + def stale?(options) + fresh_when(options) + !request.fresh?(response) + end + + # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that + # intermediate caches shouldn't cache the response. + # + # Examples: + # expires_in 20.minutes + # expires_in 3.hours, :public => true + # expires in 3.hours, 'max-stale' => 5.hours, :public => true + # + # This method will overwrite an existing Cache-Control header. + # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + def expires_in(seconds, options = {}) #:doc: + response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public)) + options.delete(:private) + + response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} + end + + # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or + # intermediate caches (like caching proxy servers). + def expires_now #:doc: + response.headers["Cache-Control"] = "no-cache" + end + end +end diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb new file mode 100644 index 0000000000..d4806623c3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -0,0 +1,94 @@ +module ActionController #:nodoc: + # Cookies are read and written through ActionController#cookies. + # + # The cookies being read are the ones received along with the request, the cookies + # being written will be sent out with the response. Reading a cookie does not get + # the cookie object itself back, just the value it holds. + # + # Examples for writing: + # + # # Sets a simple session cookie. + # cookies[:user_name] = "david" + # + # # Sets a cookie that expires in 1 hour. + # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } + # + # Examples for reading: + # + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # + # Example for deleting: + # + # cookies.delete :user_name + # + # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: + # + # cookies[:key] = { + # :value => 'a yummy cookie', + # :expires => 1.year.from_now, + # :domain => 'domain.com' + # } + # + # cookies.delete(:key, :domain => 'domain.com') + # + # The option symbols for setting cookies are: + # + # * :value - The cookie's value or list of values (as an array). + # * :path - The path for which this cookie applies. Defaults to the root + # of the application. + # * :domain - The domain for which this cookie applies. + # * :expires - The time at which this cookie expires, as a Time object. + # * :secure - Whether this cookie is a only transmitted to HTTPS servers. + # Default is +false+. + # * :httponly - Whether this cookie is accessible via scripting or + # only HTTP. Defaults to +false+. + module Cookies + def self.included(base) + base.helper_method :cookies + end + + protected + # Returns the cookie container, which operates as described above. + def cookies + @cookies ||= CookieJar.new(self) + end + end + + class CookieJar < Hash #:nodoc: + def initialize(controller) + @controller, @cookies = controller, controller.request.cookies + super() + update(@cookies) + end + + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. + def [](name) + super(name.to_s) + end + + # Sets the cookie named +name+. The second argument may be the very cookie + # value, or a hash of options as documented above. + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { :value => options } + end + + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s, options[:value]) + @controller.response.set_cookie(key, options) + end + + # Removes the cookie on the client machine by setting the value to an empty string + # and setting its expiration date into the past. Like []=, you can pass in + # an options hash to delete cookies with extra data such as a :path. + def delete(key, options = {}) + options.symbolize_keys! + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s) + @controller.response.delete_cookie(key, options) + end + end +end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb new file mode 100644 index 0000000000..d0811254cb --- /dev/null +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -0,0 +1,58 @@ +module ActionController + class ActionControllerError < StandardError #:nodoc: + end + + class SessionRestoreError < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class RoutingError < ActionControllerError #:nodoc: + attr_reader :failures + def initialize(message, failures=[]) + super(message) + @failures = failures + end + end + + class MethodNotAllowed < ActionControllerError #:nodoc: + attr_reader :allowed_methods + + def initialize(*allowed_methods) + super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") + @allowed_methods = allowed_methods + end + + def allowed_methods_header + allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' + end + + def handle_response!(response) + response.headers['Allow'] ||= allowed_methods_header + end + end + + class NotImplemented < MethodNotAllowed #:nodoc: + end + + class UnknownController < ActionControllerError #:nodoc: + end + + class MissingFile < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class SessionOverflowError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class UnknownHttpMethod < ActionControllerError #:nodoc: + end +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb new file mode 100644 index 0000000000..065e62a37f --- /dev/null +++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb @@ -0,0 +1,96 @@ +module ActionController + module FilterParameterLogging + extend ActiveSupport::Concern + + include AbstractController::Logger + + included do + include InstanceMethodsForNewBase + end + + module ClassMethods + # Replace sensitive parameter data from the request log. + # Filters parameters that have any of the arguments as a substring. + # Looks in all subhashes of the param hash for keys to filter. + # If a block is given, each key and value of the parameter hash and all + # subhashes is passed to it, the value or key + # can be replaced using String#replace or similar method. + # + # Examples: + # filter_parameter_logging + # => Does nothing, just slows the logging process down + # + # filter_parameter_logging :password + # => replaces the value to all keys matching /password/i with "[FILTERED]" + # + # filter_parameter_logging :foo, "bar" + # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + # + # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i } + # => reverses the value to all keys matching /secret/i + # + # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i } + # => reverses the value to all keys matching /secret/i, and + # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + def filter_parameter_logging(*filter_words, &block) + parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 + + define_method(:filter_parameters) do |unfiltered_parameters| + filtered_parameters = {} + + unfiltered_parameters.each do |key, value| + if key =~ parameter_filter + filtered_parameters[key] = '[FILTERED]' + elsif value.is_a?(Hash) + filtered_parameters[key] = filter_parameters(value) + elsif value.is_a?(Array) + filtered_parameters[key] = value.collect do |item| + filter_parameters(item) + end + elsif block_given? + key = key.dup + value = value.dup if value + yield key, value + filtered_parameters[key] = value + else + filtered_parameters[key] = value + end + end + + filtered_parameters + end + protected :filter_parameters + end + end + + module InstanceMethodsForNewBase + # TODO : Fix the order of information inside such that it's exactly same as the old base + def process(*) + ret = super + + if logger + parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup + parameters = parameters.except!(:controller, :action, :format, :_method, :only_path) + + unless parameters.empty? + # TODO : Move DelayedLog to AS + log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" } + logger.info(log) + end + end + + ret + end + end + + private + + # TODO : This method is not needed for the new base + 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 + end +end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb new file mode 100644 index 0000000000..590f9be3ac --- /dev/null +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -0,0 +1,164 @@ +module ActionController #:nodoc: + # The flash provides a way to pass temporary objects 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 flash[:notice] = "Successfully created" before redirecting to a display action that can + # then expose the flash to its template. Actually, that exposure is automatically done. Example: + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Successfully created post" + # redirect_to posts_path(@post) + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # show.html.erb + # <% if flash[:notice] %> + #
<%= flash[:notice] %>
+ # <% 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. + # + # See docs on the FlashHash class for more details about the flash. + module Flash + extend ActiveSupport::Concern + + include Session + + class FlashNow #:nodoc: + def initialize(flash) + @flash = flash + end + + def []=(k, v) + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k] + end + end + + class FlashHash < Hash + def initialize #:nodoc: + super + @used = {} + end + + def []=(k, v) #:nodoc: + keep(k) + super + end + + def update(h) #:nodoc: + h.keys.each { |k| keep(k) } + super + end + + alias :merge! :update + + def replace(h) #:nodoc: + @used = {} + super + end + + # Sets a flash that will not be available to the next action, only to the current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your app. + # When you need to pass an object to the next action, you use the standard flash assign ([]=). + # When you need to pass an object to the current action, you use now, and your object will + # vanish when the current action is done. + # + # Entries set via now are accessed the same way as standard entries: flash['my-key']. + def now + FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + use(k, false) + end + + # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + def discard(k = nil) + use(k) + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to care about it. + def sweep #:nodoc: + keys.each do |k| + unless @used[k] + use(k) + else + delete(k) + @used.delete(k) + end + end + + # clean up after keys that could have been left over by calling reject! or shift on the flash + (@used.keys - keys).each{ |k| @used.delete(k) } + end + + def store(session, key = "flash") + return if self.empty? + session[key] = self + end + + private + # Used internally by the keep and discard methods + # use() # marks the entire flash as used + # use('msg') # marks the "msg" entry as used + # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) + # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) + # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself + # if no key is passed. + def use(key = nil, used = true) + Array(key || keys).each { |k| @used[k] = used } + return key ? self[key] : self + end + end + + protected + def process_action(method_name) + super + if defined? @_flash + @_flash.store(session) + remove_instance_variable(:@_flash) + end + end + + def reset_session + super + remove_instance_variable(:@_flash) if defined?(@_flash) + end + + # Access the contents of the flash. Use flash["notice"] to + # read a notice you put there or flash["notice"] = "hello" + # to put a new one. + def flash #:doc: + if !defined?(@_flash) + @_flash = session["flash"] || FlashHash.new + @_flash.sweep + end + + @_flash + end + end +end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb new file mode 100644 index 0000000000..7c52779064 --- /dev/null +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -0,0 +1,176 @@ +require 'active_support/dependencies' + +module ActionController + # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, + # +numbers+ and model objects, to name a few. These helpers are available to all templates + # by default. + # + # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to + # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will + # include a helper whose name matches that of the controller, e.g., MyController will automatically + # include MyHelper. + # + # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any + # controller which inherits from it. + # + # ==== Examples + # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if + # the Time object is blank: + # + # module FormattedTimeHelper + # def format_time(time, format=:long, blank_message=" ") + # time.blank? ? blank_message : time.to_s(format) + # end + # end + # + # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: + # + # class EventsController < ActionController::Base + # helper FormattedTimeHelper + # def index + # @events = Event.find(:all) + # end + # end + # + # Then, in any view rendered by EventController, the format_time method can be called: + # + # <% @events.each do |event| -%> + #

+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %> + #

+ # <% end -%> + # + # Finally, assuming we have two event instances, one which has a time and one which does not, + # the output might look like this: + # + # 23 Aug 11:30 | Carolina Railhawks Soccer Match + # N/A | Carolina Railhaws Training Workshop + # + module Helpers + extend ActiveSupport::Concern + + include AbstractController::Helpers + + included do + # Set the default directory for helpers + extlib_inheritable_accessor(:helpers_dir) do + defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers" + end + end + + module ClassMethods + def inherited(klass) + klass.class_eval { default_helper_module! unless name.blank? } + super + end + + # The +helper+ class method can take a series of helper module names, a block, or both. + # + # ==== Parameters + # *args + # block:: A block defining helper methods + # + # ==== Examples + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers + # when working with namespaced controllers, or other cases where the file containing the helper definition is not + # in one of Rails' standard load paths: + # helper :foo # => requires 'foo_helper' and includes FooHelper + # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper + # + # When the argument is a module it will be included directly in the template class. + # helper FooHelper # => includes FooHelper + # + # When the argument is the symbol :all, the controller will include all helpers beneath + # ActionController::Base.helpers_dir (defaults to app/helpers/**/*.rb under RAILS_ROOT). + # helper :all + # + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # to the template. + # # One line + # helper { def hello() "Hello, world!" end } + # # Multi-line + # helper do + # def foo(bar) + # "#{bar} is the very best" + # end + # end + # + # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of + # +symbols+, +strings+, +modules+ and blocks. + # helper(:three, BlindHelper) { def mice() 'mice' end } + # + def helper(*args, &block) + super(*_modules_for_helpers(args), &block) + end + + # Declares helper accessors for controller attributes. For example, the + # following adds new +name+ and name= instance methods to a + # controller and makes them available to the view: + # helper_attr :name + # attr_accessor :name + # + # ==== Parameters + # *attrs:: Names of attributes to be converted + # into helpers. + def helper_attr(*attrs) + attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } + end + + # Provides a proxy to access helpers methods from outside the view. + def helpers + @helper_proxy ||= ActionView::Base.new.extend(_helpers) + end + + private + # Returns a list of modules, normalized from the acceptable kinds of + # helpers with the following behavior: + # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", + # and "foo_bar_helper.rb" is loaded using require_dependency. + # :all:: Loads all modules in the #helpers_dir + # Module:: No further processing + # + # After loading the appropriate files, the corresponding modules + # are returned. + # + # ==== Parameters + # args:: A list of helpers + # + # ==== Returns + # Array[Module]:: A normalized list of modules for the list of + # helpers provided. + def _modules_for_helpers(args) + args.flatten.map! do |arg| + case arg + when :all + _modules_for_helpers all_application_helpers + when String, Symbol + file_name = "#{arg.to_s.underscore}_helper" + require_dependency(file_name, "Missing helper file helpers/%s.rb") + file_name.camelize.constantize + when Module + arg + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end + end + end + + def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path + rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" + rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" + end + + # Extract helper names from files in app/helpers/**/*.rb + def all_application_helpers + extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ + Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb new file mode 100644 index 0000000000..af68c772b1 --- /dev/null +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -0,0 +1,35 @@ +module ActionController + # ActionController::HideActions adds the ability to prevent public methods on a controller + # to be called as actions. + module HideActions + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor(:hidden_actions) { Set.new } + end + + private + + # Overrides AbstractController::Base#action_method? to return false if the + # action name is in the list of hidden actions. + def action_method?(action_name) + !hidden_actions.include?(action_name) && super + end + + module ClassMethods + # Sets all of the actions passed in as hidden actions. + # + # ==== Parameters + # *args<#to_s>:: A list of actions + def hide_action(*args) + hidden_actions.merge(args.map! {|a| a.to_s }) + end + + # Overrides AbstractController::Base#action_methods to remove any methods + # that are listed as hidden methods. + def action_methods + @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)}) + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb new file mode 100644 index 0000000000..525787bf92 --- /dev/null +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -0,0 +1,309 @@ +require 'active_support/base64' + +module ActionController + module HttpAuthentication + # Makes it dead easy to do HTTP Basic authentication. + # + # Simple Basic example: + # + # class PostsController < ApplicationController + # USER_NAME, PASSWORD = "dhh", "secret" + # + # before_filter :authenticate, :except => [ :index ] + # + # def index + # render :text => "Everyone can see me!" + # end + # + # def edit + # render :text => "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_basic do |user_name, password| + # user_name == USER_NAME && password == PASSWORD + # end + # end + # end + # + # + # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, + # the regular HTML interface is protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_filter :set_account, :authenticate + # + # protected + # def set_account + # @account = Account.find_by_url_name(request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime::XML, Mime::ATOM + # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } + # @current_user = user + # else + # request_http_basic_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # get( + # "/notes/1.xml", nil, + # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # ) + # + # assert_equal 200, status + # end + # + # Simple Digest example: + # + # require 'digest/md5' + # class PostsController < ApplicationController + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password + # + # before_filter :authenticate, :except => [:index] + # + # def index + # render :text => "Everyone can see me!" + # end + # + # def edit + # render :text => "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_digest(REALM) do |username| + # USERS[username] + # end + # end + # end + # + # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately + # hash to check the user's credentials. Returning +nil+ will cause authentication to fail. + # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If + # the password file or database is compromised, the attacker would be able to use the ha1 hash to + # authenticate as the user at this +realm+, but would not have the user's password to try using at + # other sites. + # + # On shared hosts, Apache sometimes doesn't pass authentication headers to + # FCGI instances. If your environment matches this description and you cannot + # authenticate, try this rule in your Apache setup: + # + # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] + module Basic + extend self + + module ControllerMethods + def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) + authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) + end + + def authenticate_with_http_basic(&login_procedure) + HttpAuthentication::Basic.authenticate(self, &login_procedure) + end + + def request_http_basic_authentication(realm = "Application") + HttpAuthentication::Basic.authentication_request(self, realm) + end + end + + def authenticate(controller, &login_procedure) + unless authorization(controller.request).blank? + login_procedure.call(*user_name_and_password(controller.request)) + end + end + + def user_name_and_password(request) + decode_credentials(request).split(/:/, 2) + end + + def authorization(request) + request.env['HTTP_AUTHORIZATION'] || + request.env['X-HTTP_AUTHORIZATION'] || + request.env['X_HTTP_AUTHORIZATION'] || + request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + end + + def decode_credentials(request) + ActiveSupport::Base64.decode64(authorization(request).split.last || '') + end + + def encode_credentials(user_name, password) + "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}" + end + + def authentication_request(controller, realm) + controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}") + controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized + end + end + + module Digest + extend self + + module ControllerMethods + def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure) + authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm) + end + + # Authenticate with HTTP Digest, returns true or false + def authenticate_with_http_digest(realm = "Application", &password_procedure) + HttpAuthentication::Digest.authenticate(self, realm, &password_procedure) + end + + # Render output including the HTTP Digest authentication header + def request_http_digest_authentication(realm = "Application", message = nil) + HttpAuthentication::Digest.authentication_request(self, realm, message) + end + end + + # Returns false on a valid response, true otherwise + def authenticate(controller, realm, &password_procedure) + authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure) + end + + def authorization(request) + request.env['HTTP_AUTHORIZATION'] || + request.env['X-HTTP_AUTHORIZATION'] || + request.env['X_HTTP_AUTHORIZATION'] || + request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + end + + # Returns false unless the request credentials response value matches the expected value. + # First try the password as a ha1 digest password. If this fails, then try it as a plain + # text password. + def validate_digest_response(request, realm, &password_procedure) + credentials = decode_credentials_header(request) + valid_nonce = validate_nonce(request, credentials[:nonce]) + + if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] + password = password_procedure.call(credentials[:username]) + return false unless password + + method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + + [true, false].any? do |password_is_ha1| + expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected == credentials[:response] + end + end + end + + # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ + # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead + # of a plain-text password. + def expected_response(http_method, uri, credentials, password, password_is_ha1=true) + ha1 = password_is_ha1 ? password : ha1(credentials, password) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) + ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) + end + + def ha1(credentials, password) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + end + + def encode_credentials(http_method, credentials, password, password_is_ha1) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) + "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') + end + + def decode_credentials_header(request) + decode_credentials(authorization(request)) + end + + def decode_credentials(header) + header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair| + key, value = pair.split('=', 2) + hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '') + hash + end + end + + def authentication_header(controller, realm) + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") + end + + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Digest: Access denied.\n" + authentication_header(controller, realm) + controller.__send__ :render, :text => message, :status => :unauthorized + end + + # Uses an MD5 digest based on time to generate a value to be used only once. + # + # A server-specified data string which should be uniquely generated each time a 401 response is made. + # It is recommended that this string be base64 or hexadecimal data. + # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. + # + # The contents of the nonce are implementation dependent. + # The quality of the implementation depends on a good choice. + # A nonce might, for example, be constructed as the base 64 encoding of + # + # => time-stamp H(time-stamp ":" ETag ":" private-key) + # + # where time-stamp is a server-generated time or other non-repeating value, + # ETag is the value of the HTTP ETag header associated with the requested entity, + # and private-key is data known only to the server. + # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and + # reject the request if it did not match the nonce from that header or + # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. + # The inclusion of the ETag prevents a replay request for an updated version of the resource. + # (Note: including the IP address of the client in the nonce would appear to offer the server the ability + # to limit the reuse of the nonce to the same client that originally got it. + # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. + # Also, IP address spoofing is not that hard.) + # + # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to + # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for + # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 + # of this document. + # + # The nonce is opaque to the client. Composed of Time, and hash of Time with secret + # key from the Rails session secret generated upon creation of project. Ensures + # the time cannot be modified by client. + def nonce(time = Time.now) + t = time.to_i + hashed = [t, secret_key] + digest = ::Digest::MD5.hexdigest(hashed.join(":")) + ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '') + end + + # Might want a shorter timeout depending on whether the request + # is a PUT or POST, and if client is browser or web service. + # Can be much shorter if the Stale directive is implemented. This would + # allow a user to use new nonce without prompting user again for their + # username and password. + def validate_nonce(request, value, seconds_to_timeout=5*60) + t = ActiveSupport::Base64.decode64(value).split(":").first.to_i + nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout + end + + # Opaque based on random generation - but changing each request? + def opaque() + ::Digest::MD5.hexdigest(secret_key) + end + + # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. + def secret_key + ActionController::Base.session_options[:secret] + end + + end + end +end diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb new file mode 100644 index 0000000000..365351b421 --- /dev/null +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -0,0 +1,192 @@ +module ActionController + # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in + # repeated setups. The inclusion pattern has pages that look like this: + # + # <%= render "shared/header" %> + # Hello World + # <%= render "shared/footer" %> + # + # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose + # and if you ever want to change the structure of these two includes, you'll have to change all the templates. + # + # With layouts, you can flip it around and have the common structure know where to insert changing content. This means + # that the header and footer are only mentioned in one place, like this: + # + # // The header part of this layout + # <%= yield %> + # // The footer part of this layout + # + # And then you have content pages that look like this: + # + # hello world + # + # At rendering time, the content page is computed and then inserted in the layout, like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + #

<%= @page_title %>

+ # <%= yield %> + # + # ...and content pages that fulfill these references _at_ rendering time: + # + # <% @page_title = "Welcome" %> + # Off-world colonies offers you a chance to start a new life + # + # The result after rendering is: + # + #

Welcome

+ # Off-world colonies offers you a chance to start a new life + # + # == Layout assignment + # + # You can either specify a layout declaratively (using the #layout class method) or give + # it the same name as your controller, and place it in app/views/layouts. + # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. + # + # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, + # that template will be used for all actions in PostsController and controllers inheriting + # from PostsController. + # + # If you use a module, for instance Weblog::PostsController, you will need a template named + # app/views/layouts/weblog/posts.html.erb. + # + # Since all your controllers inherit from ApplicationController, they will use + # app/views/layouts/application.html.erb if no other layout is specified + # or provided. + # + # == Inheritance Examples + # + # class BankController < ActionController::Base + # layout "bank_standard" + # + # class InformationController < BankController + # + # class TellerController < BankController + # # teller.html.erb exists + # + # class TillController < TellerController + # + # class VaultController < BankController + # layout :access_level_layout + # + # class EmployeeController < BankController + # layout nil + # + # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites + # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. + # + # The TellerController uses +teller.html.erb+, and TillController inherits that layout and + # uses it as well. + # + # == Types of layouts + # + # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes + # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can + # be done either by specifying a method reference as a symbol or using an inline method (as a proc). + # + # The method reference is the preferred approach to variable layouts and is used like this: + # + # class WeblogController < ActionController::Base + # layout :writers_and_readers + # + # def index + # # fetching posts + # end + # + # private + # def writers_and_readers + # logged_in? ? "writer_layout" : "reader_layout" + # end + # + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # is logged in or not. + # + # If you want to use an inline method, such as a proc, do something like this: + # + # class WeblogController < ActionController::Base + # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # + # Of course, the most common way of specifying a layout is still just as a plain template name: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # If no directory is specified for the template name, the template will by default be looked for in app/views/layouts/. + # Otherwise, it will be looked up relative to the template root. + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # :only and :except options can be passed to the layout call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard", :except => :rss + # + # # ... + # + # end + # + # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout + # around the rendered view. + # + # Both the :only and :except condition can accept an arbitrary number of method references, so + # #:except => [ :rss, :text_only ] is valid, as is :except => :rss. + # + # == Using a different layout in the action render call + # + # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a :layout option to the render call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # def help + # render :action => "help", :layout => "help" + # end + # end + # + # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. + module Layouts + extend ActiveSupport::Concern + + include ActionController::Renderer + include AbstractController::Layouts + + module ClassMethods + # If no layout is provided, look for a layout with this name. + def _implied_layout_name + controller_path + end + end + + private + def _determine_template(options) + super + + return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout) + layout = options.key?(:layout) ? options[:layout] : :default + options[:_layout] = _layout_for_option(layout, options[:_template].details) + end + + def _layout_for_option(name, details) + case name + when String then _layout_for_name(name, details) + when true then _default_layout(details, true) + when :default then _default_layout(details, false) + when false, nil then nil + else + raise ArgumentError, + "String, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb new file mode 100644 index 0000000000..f4a4007a43 --- /dev/null +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -0,0 +1,353 @@ +module ActionController #:nodoc: + module MimeResponds #:nodoc: + extend ActiveSupport::Concern + + included do + class_inheritable_reader :mimes_for_respond_to + clear_respond_to + end + + module ClassMethods + # Defines mimes that are rendered by default when invoking respond_with. + # + # Examples: + # + # respond_to :html, :xml, :json + # + # All actions on your controller will respond to :html, :xml and :json. + # + # But if you want to specify it based on your actions, you can use only and + # except: + # + # respond_to :html + # respond_to :xml, :json, :except => [ :edit ] + # + # The definition above explicits that all actions respond to :html. And all + # actions except :edit respond to :xml and :json. + # + # You can specify also only parameters: + # + # respond_to :rjs, :only => :create + # + def respond_to(*mimes) + options = mimes.extract_options! + + only_actions = Array(options.delete(:only)) + except_actions = Array(options.delete(:except)) + + mimes.each do |mime| + mime = mime.to_sym + mimes_for_respond_to[mime] = {} + mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty? + mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty? + end + end + + # Clear all mimes in respond_to. + # + def clear_respond_to + write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new) + end + end + + # Without web-service support, an action which collects the data for displaying a list of people + # might look something like this: + # + # def index + # @people = Person.find(:all) + # end + # + # Here's the same action, with web-service support baked in: + # + # def index + # @people = Person.find(:all) + # + # respond_to do |format| + # format.html + # format.xml { render :xml => @people.to_xml } + # end + # end + # + # What that says is, "if the client wants HTML in response to this action, just respond as we + # would have before, but if the client wants XML, return them the list of people in XML format." + # (Rails determines the desired response format from the HTTP Accept header submitted by the client.) + # + # Supposing you have an action that adds a new person, optionally creating their company + # (by name) if it does not already exist, without web-services, it might look like this: + # + # def create + # @company = Company.find_or_create_by_name(params[:company][:name]) + # @person = @company.people.create(params[:person]) + # + # redirect_to(person_list_url) + # end + # + # Here's the same action, with web-service support baked in: + # + # def create + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by_name(company[:name]) + # @person = @company.people.create(params[:person]) + # + # respond_to do |format| + # format.html { redirect_to(person_list_url) } + # format.js + # format.xml { render :xml => @person.to_xml(:include => @company) } + # end + # end + # + # If the client wants HTML, we just redirect them back to the person list. If they want Javascript + # (format.js), then it is an RJS request and we render the RJS template associated with this action. + # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also + # include the person's company in the rendered XML, so you get something like this: + # + # + # ... + # ... + # + # ... + # ... + # ... + # + # + # + # Note, however, the extra bit at the top of that action: + # + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by_name(company[:name]) + # + # This is because the incoming XML document (if a web-service request is in process) can only contain a + # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): + # + # person[name]=...&person[company][name]=...&... + # + # And, like this (xml-encoded): + # + # + # ... + # + # ... + # + # + # + # In other words, we make the request so that it operates on a single entity's person. Then, in the action, + # we extract the company data from the request, find or create the company, and then create the new person + # with the remaining data. + # + # Note that you can define your own XML parameter parser which would allow you to describe multiple entities + # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow + # and accept Rails' defaults, life will be much easier. + # + # If you need to use a MIME type which isn't supported by default, you can register your own handlers in + # environment.rb as follows. + # + # Mime::Type.register "image/jpg", :jpg + # + # Respond to also allows you to specify a common block for different formats by using any: + # + # def index + # @people = Person.find(:all) + # + # respond_to do |format| + # format.html + # format.any(:xml, :json) { render request.format.to_sym => @people } + # end + # end + # + # In the example above, if the format is xml, it will render: + # + # render :xml => @people + # + # Or if the format is json: + # + # render :json => @people + # + # Since this is a common pattern, you can use the class method respond_to + # with the respond_with method to have the same results: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@person) + # end + # end + # + # Be sure to check respond_with and respond_to documentation for more examples. + # + def respond_to(*mimes, &block) + options = mimes.extract_options! + raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? + + resource = options.delete(:with) + responder = Responder.new + + mimes = collect_mimes_from_class_level if mimes.empty? + mimes.each { |mime| responder.send(mime) } + block.call(responder) if block_given? + + if format = request.negotiate_mime(responder.order) + respond_to_block_or_template_or_resource(format, resource, + options, &responder.response_for(format)) + else + head :not_acceptable + end + end + + # respond_with allows you to respond an action with a given resource. It + # requires that you set your class with a :respond_to method with the + # formats allowed: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@person) + # end + # end + # + # When a request comes with format :xml, the respond_with will first search + # for a template as person/index.xml, if the template is not available, it + # will see if the given resource responds to :to_xml. + # + # If neither are available, it will raise an error. + # + # Extra parameters given to respond_with are used when :to_format is invoked. + # This allows you to set status and location for several formats at the same + # time. Consider this restful controller response on create for both xml + # and json formats: + # + # class PeopleController < ApplicationController + # respond_to :xml, :json + # + # def create + # @person = Person.new(params[:person]) + # + # if @person.save + # respond_with(@person, :status => :ok, :location => person_url(@person)) + # else + # respond_with(@person.errors, :status => :unprocessable_entity) + # end + # end + # end + # + # Finally, respond_with also accepts blocks, as in respond_to. Let's take + # the same controller and create action above and add common html behavior: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def create + # @person = Person.new(params[:person]) + # + # if @person.save + # options = { :status => :ok, :location => person_url(@person) } + # + # respond_with(@person, options) do |format| + # format.html { redirect_to options[:location] } + # end + # else + # respond_with(@person.errors, :status => :unprocessable_entity) do + # format.html { render :action => :new } + # end + # end + # end + # end + # + def respond_with(resource, options={}, &block) + respond_to(options.merge!(:with => resource), &block) + end + + protected + + def respond_to_block_or_template_or_resource(format, resource, options) + self.formats = [format.to_sym] + return yield if block_given? + + begin + default_render + rescue ActionView::MissingTemplate => e + if resource && resource.respond_to?(:"to_#{format.to_sym}") + render options.merge(format.to_sym => resource) + else + raise e + end + end + end + + # Collect mimes declared in the class method respond_to valid for the + # current action. + # + def collect_mimes_from_class_level #:nodoc: + action = action_name.to_sym + + mimes_for_respond_to.keys.select do |mime| + config = mimes_for_respond_to[mime] + + if config[:except] + !config[:except].include?(action) + elsif config[:only] + config[:only].include?(action) + else + true + end + end + end + + class Responder #:nodoc: + attr_accessor :order + + def initialize + @order, @responses = [], {} + end + + def any(*args, &block) + if args.any? + args.each { |type| send(type, &block) } + else + custom(Mime::ALL, &block) + end + end + alias :all :any + + def custom(mime_type, &block) + mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) + + @order << mime_type + @responses[mime_type] ||= block + end + + def response_for(mime) + @responses[mime] || @responses[Mime::ALL] + 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 + + Mime::SET.each do |mime| + generate_method_for_mime(mime) + end + + 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 + end + + end + end +end diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb new file mode 100644 index 0000000000..805157b0e3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/rack_convenience.rb @@ -0,0 +1,33 @@ +module ActionController + module RackConvenience + extend ActiveSupport::Concern + + included do + delegate :headers, :status=, :location=, :content_type=, + :status, :location, :content_type, :to => "@_response" + attr_internal :request, :response + end + + def call(name, env) + @_request = ActionDispatch::Request.new(env) + @_response = ActionDispatch::Response.new + @_response.request = request + super + end + + def params + @_params ||= @_request.parameters + end + + # :api: private + def to_rack + @_response.prepare! + @_response.to_a + end + + def response_body=(body) + response.body = body if response + super + end + end +end diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb new file mode 100644 index 0000000000..20060b001f --- /dev/null +++ b/actionpack/lib/action_controller/metal/redirector.rb @@ -0,0 +1,19 @@ +module ActionController + class RedirectBackError < AbstractController::Error #:nodoc: + DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Redirector + def redirect_to(url, status) #:doc: + raise AbstractController::DoubleRenderError if response_body + logger.info("Redirected to #{url}") if logger && logger.info? + self.status = status + self.location = url.gsub(/[\r\n]/, '') + self.response_body = "You are being redirected." + end + end +end diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb new file mode 100644 index 0000000000..65ee09883e --- /dev/null +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -0,0 +1,103 @@ +module ActionController + module RenderOptions + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor :_renderers + self._renderers = [] + end + + module ClassMethods + def _write_render_options + renderers = _renderers.map do |r| + <<-RUBY_EVAL + if options.key?(:#{r}) + _process_options(options) + return _render_#{r}(options[:#{r}], options) + end + RUBY_EVAL + end + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _handle_render_options(options) + #{renderers.join} + end + RUBY_EVAL + end + + def _add_render_option(name) + _renderers << name + _write_render_options + end + end + + def render_to_body(options) + _handle_render_options(options) || super + end + end + + module RenderOption #:nodoc: + def self.extended(base) + base.extend ActiveSupport::Concern + base.send :include, ::ActionController::RenderOptions + + def base.register_renderer(name) + included { _add_render_option(name) } + end + end + end + + module Renderers + module Json + extend RenderOption + register_renderer :json + + def _render_json(json, options) + json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) + json = "#{options[:callback]}(#{json})" unless options[:callback].blank? + self.content_type ||= Mime::JSON + self.response_body = json + end + end + + module Js + extend RenderOption + register_renderer :js + + def _render_js(js, options) + self.content_type ||= Mime::JS + self.response_body = js + end + end + + module Xml + extend RenderOption + register_renderer :xml + + def _render_xml(xml, options) + self.content_type ||= Mime::XML + self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml + end + end + + module RJS + extend RenderOption + register_renderer :update + + def _render_update(proc, options) + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) + self.content_type = Mime::JS + self.response_body = generator.to_s + end + end + + module All + extend ActiveSupport::Concern + + include ActionController::Renderers::Json + include ActionController::Renderers::Js + include ActionController::Renderers::Xml + include ActionController::Renderers::RJS + end + end +end diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb new file mode 100644 index 0000000000..572da451ff --- /dev/null +++ b/actionpack/lib/action_controller/metal/renderer.rb @@ -0,0 +1,77 @@ +module ActionController + module Renderer + extend ActiveSupport::Concern + + include AbstractController::Renderer + + def process_action(*) + self.formats = request.formats.map {|x| x.to_sym} + super + end + + def render(options) + super + self.content_type ||= begin + mime = options[:_template].mime_type + formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) + end.to_s + response_body + end + + def render_to_body(options) + _process_options(options) + + if options.key?(:partial) + _render_partial(options[:partial], options) + end + + super + end + + private + def _prefix + controller_path + end + + def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) + elsif options.key?(:inline) + handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") + template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + elsif !options.key?(:partial) + options[:_template_name] = (options[:action] || action_name).to_s + options[:_prefix] = _prefix + end + + super + end + + def _render_partial(partial, options) + case partial + when true + options[:_prefix] = _prefix + when String + options[:_prefix] = _prefix unless partial.index('/') + options[:_template_name] = partial + else + options[:_partial_object] = true + return + end + + options[:_partial] = options[:object] || true + end + + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location + end + end +end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb new file mode 100644 index 0000000000..ad06657f86 --- /dev/null +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -0,0 +1,118 @@ +module ActionController #:nodoc: + class InvalidAuthenticityToken < ActionControllerError #:nodoc: + end + + module RequestForgeryProtection + extend ActiveSupport::Concern + + # TODO : Remove the defined? check when new base is the main base + include AbstractController::Helpers, Session + + included do + # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ + # sets it to :authenticity_token by default. + cattr_accessor :request_forgery_protection_token + + # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. + class_inheritable_accessor :allow_forgery_protection + self.allow_forgery_protection = true + + helper_method :form_authenticity_token + helper_method :protect_against_forgery? + end + + # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a + # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all + # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only + # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication + # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway. + # + # This is turned on with the protect_from_forgery method, which will check the token and raise an + # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in + # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0 + # applications. + # + # The token parameter is named authenticity_token by default. If you are generating an HTML form manually (without the + # use of Rails' form_for, form_tag or other helpers), you have to include a hidden field named like that and + # set its value to what is returned by form_authenticity_token. Same applies to manually constructed Ajax requests. To + # make the token available through a global variable to scripts on a certain page, you could add something like this to a view: + # + # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> + # + # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to + # config/environments/test.rb: + # + # # Disable request forgery protection in test environment + # config.action_controller.allow_forgery_protection = false + # + # == Learn more about CSRF (Cross-Site Request Forgery) attacks + # + # Here are some resources: + # * http://isc.sans.org/diary.html?storyid=1750 + # * http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. + # There are a few guidelines you should follow: + # + # * Keep your GET requests safe and idempotent. More reading material: + # * http://www.xml.com/pub/a/2002/04/24/deviant.html + # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 + # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session" + # + module ClassMethods + # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. + # + # Example: + # + # class FooController < ApplicationController + # protect_from_forgery :except => :index + # + # # you can disable csrf protection on controller-by-controller basis: + # skip_before_filter :verify_authenticity_token + # end + # + # Valid Options: + # + # * :only/:except - Passed to the before_filter call. Set which actions are verified. + def protect_from_forgery(options = {}) + self.request_forgery_protection_token ||= :authenticity_token + before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) + if options[:secret] || options[:digest] + ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller) + end + end + end + + protected + # The actual before_filter that is used. Modify this to change how you handle unverified requests. + def verify_authenticity_token + verified_request? || raise(ActionController::InvalidAuthenticityToken) + end + + # Returns true or false if a request is verified. Checks: + # + # * is the format restricted? By default, only HTML requests are checked. + # * is it a GET request? Gets should be safe and idempotent + # * Does the form_authenticity_token match the given token value from the params? + def verified_request? + !protect_against_forgery? || + request.method == :get || + request.xhr? || + !verifiable_request_format? || + form_authenticity_token == params[request_forgery_protection_token] + end + + def verifiable_request_format? + !request.content_type.nil? && request.content_type.verify_request? + end + + # Sets the token value for the current session. + def form_authenticity_token + session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) + end + + def protect_against_forgery? + allow_forgery_protection && request_forgery_protection_token + end + end +end diff --git a/actionpack/lib/action_controller/metal/rescuable.rb b/actionpack/lib/action_controller/metal/rescuable.rb new file mode 100644 index 0000000000..029e643d93 --- /dev/null +++ b/actionpack/lib/action_controller/metal/rescuable.rb @@ -0,0 +1,52 @@ +module ActionController #:nodoc: + # Actions that fail to perform as expected throw exceptions. These + # exceptions can either be rescued for the public view (with a nice + # user-friendly explanation) or for the developers view (with tons of + # debugging information). The developers view is already implemented by + # the Action Controller, but the public view should be tailored to your + # specific application. + # + # The default behavior for public exceptions is to render a static html + # file with the name of the error code thrown. If no such file exists, an + # empty response is sent with the correct status code. + # + # You can override what constitutes a local request by overriding the + # local_request? method in your own controller. Custom rescue + # behavior is achieved by overriding the rescue_action_in_public + # and rescue_action_locally methods. + module Rescue + extend ActiveSupport::Concern + + included do + include ActiveSupport::Rescuable + end + + module ClassMethods + # This can be removed once we can move action(:_rescue_action) into middlewares.rb + # Currently, it does controller.method(:rescue_action), which is hiding the implementation + # difference between the old and new base. + def rescue_action(env) + action(:_rescue_action).call(env) + end + end + + attr_internal :rescued_exception + + private + def method_for_action(action_name) + return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception") + super + end + + def _rescue_action + rescue_with_handler(rescued_exception) || raise(rescued_exception) + end + + def process_action(*) + super + rescue Exception => exception + self.rescued_exception = exception + _rescue_action + end + end +end diff --git a/actionpack/lib/action_controller/metal/session.rb b/actionpack/lib/action_controller/metal/session.rb new file mode 100644 index 0000000000..bcedd6e1c7 --- /dev/null +++ b/actionpack/lib/action_controller/metal/session.rb @@ -0,0 +1,15 @@ +module ActionController + module Session + extend ActiveSupport::Concern + + include RackConvenience + + def session + @_request.session + end + + def reset_session + @_request.reset_session + end + end +end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb new file mode 100644 index 0000000000..ffce8e1bd1 --- /dev/null +++ b/actionpack/lib/action_controller/metal/session_management.rb @@ -0,0 +1,54 @@ +module ActionController #:nodoc: + module SessionManagement #:nodoc: + def self.included(base) + base.class_eval do + extend ClassMethods + end + end + + module ClassMethods + # Set the session store to be used for keeping the session data between requests. + # By default, sessions are stored in browser cookies (:cookie_store), + # but you can also specify one of the other included stores (:active_record_store, + # :mem_cache_store, or your own custom class. + def session_store=(store) + if store == :active_record_store + self.session_store = ActiveRecord::SessionStore + else + @@session_store = store.is_a?(Symbol) ? + Session.const_get(store.to_s.camelize) : + store + end + end + + # Returns the session store class currently used. + def session_store + if defined? @@session_store + @@session_store + else + ActionDispatch::Session::CookieStore + end + end + + def session=(options = {}) + self.session_store = nil if options.delete(:disabled) + session_options.merge!(options) + end + + # Returns the hash used to configure the session. Example use: + # + # ActionController::Base.session_options[:secure] = true # session only available over HTTPS + def session_options + @session_options ||= {} + end + + def session(*args) + ActiveSupport::Deprecation.warn( + "Disabling sessions for a single controller has been deprecated. " + + "Sessions are now lazy loaded. So if you don't access them, " + + "consider them off. You can still modify the session cookie " + + "options with request.session_options.", caller) + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb new file mode 100644 index 0000000000..f0317c6e99 --- /dev/null +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -0,0 +1,187 @@ +require 'active_support/core_ext/string/bytesize' + +module ActionController #:nodoc: + # Methods for sending arbitrary data and for streaming files to the browser, + # instead of rendering. + module Streaming + extend ActiveSupport::Concern + + include ActionController::Renderer + + DEFAULT_SEND_FILE_OPTIONS = { + :type => 'application/octet-stream'.freeze, + :disposition => 'attachment'.freeze, + :stream => true, + :buffer_size => 4096, + :x_sendfile => false + }.freeze + + X_SENDFILE_HEADER = 'X-Sendfile'.freeze + + protected + # Sends the file, by default streaming it 4096 bytes at a time. This way the + # whole file doesn't need to be read into memory at once. This makes it + # feasible to send even large files. You can optionally turn off streaming + # and send the whole file at once. + # + # Be careful to sanitize the path parameter if it is coming from a web + # page. send_file(params[:path]) allows a malicious user to + # download any file on your server. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # Defaults to File.basename(path). + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json + # * :length - used to manually override the length (in bytes) of the content that + # is going to be sent to the client. Defaults to File.size(path). + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :stream - whether to send the file to the user agent as it is read (+true+) + # or to read the entire file before sending (+false+). Defaults to +true+. + # * :buffer_size - specifies size (in bytes) of the buffer used to stream the file. + # Defaults to 4096. + # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # * :url_based_filename - set to +true+ if you want the browser guess the filename from + # the URL, which is necessary for i18n filenames on certain browsers + # (setting :filename overrides this option). + # * :x_sendfile - uses X-Sendfile to send the file when set to +true+. This is currently + # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this + # uses the web server to send the file, this may lower memory consumption on your server and + # it will not block your application for further requests. + # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and + # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. + # + # The default Content-Type and Content-Disposition headers are + # set to download arbitrary binary files in as many browsers as + # possible. IE versions 4, 5, 5.5, and 6 are all known to have + # a variety of quirks (especially when downloading over SSL). + # + # Simple download: + # + # send_file '/path/to.zip' + # + # Show a JPEG in the browser: + # + # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' + # + # Show a 404 page in the browser: + # + # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 + # + # Read about the other Content-* HTTP headers if you'd like to + # provide the user with more information (such as Content-Description) in + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. + # + # Also be aware that the document may be cached by proxies and browsers. + # The Pragma and Cache-Control headers declare how the file may be cached + # by intermediaries. They default to require clients to validate with + # the server before releasing cached responses. See + # http://www.mnot.net/cache_docs/ for an overview of web caching and + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # for the Cache-Control header spec. + def send_file(path, options = {}) #:doc: + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) + + options[:length] ||= File.size(path) + options[:filename] ||= File.basename(path) unless options[:url_based_filename] + send_file_headers! options + + @performed_render = false + + if options[:x_sendfile] + logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger + head options[:status], X_SENDFILE_HEADER => path + else + if options[:stream] + # TODO : Make render :text => proc {} work with the new base + render :status => options[:status], :text => Proc.new { |response, output| + logger.info "Streaming file #{path}" unless logger.nil? + len = options[:buffer_size] || 4096 + File.open(path, 'rb') do |file| + while buf = file.read(len) + output.write(buf) + end + end + } + else + logger.info "Sending file #{path}" unless logger.nil? + File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } + end + end + end + + # Sends the given binary data to the browser. This method is similar to + # render :text => data, but also allows you to specify whether + # the browser should display the response as a file attachment (i.e. in a + # download dialog) or as inline data. You may also set the content type, + # the apparent file name, and other things. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # + # Generic data download: + # + # send_data buffer + # + # Download a dynamically-generated tarball: + # + # send_data generate_tgz('dir'), :filename => 'dir.tgz' + # + # Display an image Active Record in the browser: + # + # send_data image.data, :type => image.content_type, :disposition => 'inline' + # + # See +send_file+ for more information on HTTP Content-* headers and caching. + # + # Tip: if you want to stream large amounts of on-the-fly generated + # data to the browser, then use render :text => proc { ... } + # instead. See ActionController::Base#render for more information. + def send_data(data, options = {}) #:doc: + logger.info "Sending data #{options[:filename]}" if logger + send_file_headers! options.merge(:length => data.bytesize) + @performed_render = false + render :status => options[:status], :text => data + end + + private + def send_file_headers!(options) + options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) + [:length, :type, :disposition].each do |arg| + raise ArgumentError, ":#{arg} option required" if options[arg].nil? + end + + disposition = options[:disposition].dup || 'attachment' + + disposition <<= %(; filename="#{options[:filename]}") if options[:filename] + + content_type = options[:type] + + if content_type.is_a?(Symbol) + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s) + self.content_type = Mime::Type.lookup_by_extension(content_type.to_s) + else + self.content_type = content_type + end + + headers.merge!( + 'Content-Length' => options[:length].to_s, + 'Content-Disposition' => disposition, + 'Content-Transfer-Encoding' => 'binary' + ) + + # Fix a problem with IE 6.0 on opening downloaded files: + # If Cache-Control: no-cache is set (which Rails does by default), + # IE removes the file it just downloaded from its cache immediately + # after it displays the "open/save" dialog, which means that if you + # hit "open" the file isn't there anymore when the application that + # is called for handling the download is run, so let's workaround that + response.cache_control[:public] ||= false + end + end +end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb new file mode 100644 index 0000000000..a4a1116d9e --- /dev/null +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -0,0 +1,39 @@ +module ActionController + module Testing + extend ActiveSupport::Concern + + include RackConvenience + + # OMG MEGA HAX + def process_with_new_base_test(request, response) + @_request = request + @_response = response + @_response.request = request + ret = process(request.parameters[:action]) + @_response.body ||= self.response_body + @_response.prepare! + set_test_assigns + ret + end + + 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 + end + end + + # TODO : Rewrite tests using controller.headers= to use Rack env + def headers=(new_headers) + @_response ||= ActionDispatch::Response.new + @_response.headers.replace(new_headers) + end + + module ClassMethods + def before_filters + _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb new file mode 100644 index 0000000000..7119c14cd3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -0,0 +1,49 @@ +module ActionController + module UrlFor + extend ActiveSupport::Concern + + include RackConvenience + + def process_action(*) + initialize_current_url + super + end + + def initialize_current_url + @url = UrlRewriter.new(request, params.clone) + end + + # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in + # the form of a hash, just like the one you would use for url_for directly. Example: + # + # def default_url_options(options) + # { :project => @project.active? ? @project.url_name : "unknown" } + # end + # + # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the + # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set + # by this method. + def default_url_options(options = nil) + end + + def rewrite_options(options) #:nodoc: + if defaults = default_url_options(options) + defaults.merge(options) + else + options + end + end + + def url_for(options = {}) + options ||= {} + case options + when String + options + when Hash + @url.rewrite(rewrite_options(options)) + else + polymorphic_url(options) + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb new file mode 100644 index 0000000000..951ae1bee1 --- /dev/null +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -0,0 +1,130 @@ +module ActionController #:nodoc: + module Verification #:nodoc: + extend ActiveSupport::Concern + + include AbstractController::Callbacks, Session, Flash, Renderer + + # This module provides a class-level method for specifying that certain + # actions are guarded against being called without certain prerequisites + # being met. This is essentially a special kind of before_filter. + # + # An action may be guarded against being invoked without certain request + # parameters being set, or without certain session values existing. + # + # When a verification is violated, values may be inserted into the flash, and + # a specified redirection is triggered. If no specific action is configured, + # verification failures will by default result in a 400 Bad Request response. + # + # Usage: + # + # class GlobalController < ActionController::Base + # # Prevent the #update_settings action from being invoked unless + # # the 'admin_privileges' request parameter exists. The + # # settings action will be redirected to in current controller + # # if verification fails. + # verify :params => "admin_privileges", :only => :update_post, + # :redirect_to => { :action => "settings" } + # + # # Disallow a post from being updated if there was no information + # # submitted with the post, and if there is no active post in the + # # session, and if there is no "note" key in the flash. The route + # # named category_url will be redirected to if verification fails. + # + # verify :params => "post", :session => "post", "flash" => "note", + # :only => :update_post, + # :add_flash => { "alert" => "Failed to create your message" }, + # :redirect_to => :category_url + # + # Note that these prerequisites are not business rules. They do not examine + # the content of the session or the parameters. That level of validation should + # be encapsulated by your domain model or helper methods in the controller. + module ClassMethods + # Verify the given actions so that if certain prerequisites are not met, + # the user is redirected to a different action. The +options+ parameter + # is a hash consisting of the following key/value pairs: + # + # :params:: + # a single key or an array of keys that must be in the params + # hash in order for the action(s) to be safely called. + # :session:: + # a single key or an array of keys that must be in the session + # in order for the action(s) to be safely called. + # :flash:: + # a single key or an array of keys that must be in the flash in order + # for the action(s) to be safely called. + # :method:: + # a single key or an array of keys--any one of which must match the + # current request method in order for the action(s) to be safely called. + # (The key should be a symbol: :get or :post, for + # example.) + # :xhr:: + # true/false option to ensure that the request is coming from an Ajax + # call or not. + # :add_flash:: + # a hash of name/value pairs that should be merged into the session's + # flash if the prerequisites cannot be satisfied. + # :add_headers:: + # a hash of name/value pairs that should be merged into the response's + # headers hash if the prerequisites cannot be satisfied. + # :redirect_to:: + # the redirection parameters to be used when redirecting if the + # prerequisites cannot be satisfied. You can redirect either to named + # route or to the action in some controller. + # :render:: + # the render parameters to be used when the prerequisites cannot be satisfied. + # :only:: + # only apply this verification to the actions specified in the associated + # array (may also be a single value). + # :except:: + # do not apply this verification to the actions specified in the associated + # array (may also be a single value). + def verify(options={}) + before_filter :only => options[:only], :except => options[:except] do |c| + c.__send__ :verify_action, options + end + end + end + + private + + def verify_action(options) #:nodoc: + if prereqs_invalid?(options) + flash.update(options[:add_flash]) if options[:add_flash] + response.headers.merge!(options[:add_headers]) if options[:add_headers] + apply_remaining_actions(options) unless performed? + end + end + + def prereqs_invalid?(options) # :nodoc: + verify_presence_of_keys_in_hash_flash_or_params(options) || + verify_method(options) || + verify_request_xhr_status(options) + end + + def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc: + [*options[:params] ].find { |v| v && params[v.to_sym].nil? } || + [*options[:session]].find { |v| session[v].nil? } || + [*options[:flash] ].find { |v| flash[v].nil? } + end + + def verify_method(options) # :nodoc: + [*options[:method]].all? { |v| request.method != v.to_sym } if options[:method] + end + + def verify_request_xhr_status(options) # :nodoc: + request.xhr? != options[:xhr] unless options[:xhr].nil? + end + + def apply_redirect_to(redirect_to_option) # :nodoc: + (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option + end + + def apply_remaining_actions(options) # :nodoc: + case + when options[:render] ; render(options[:render]) + when options[:redirect_to] ; redirect_to(apply_redirect_to(options[:redirect_to])) + else head(:bad_request) + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From 70d779aaea719e747b242ad88f1489bf19c6795e Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 20:03:59 -0300 Subject: Update _render_options to reflect the fact that they're public --- actionpack/lib/action_controller/metal/render_options.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb index 65ee09883e..c6a965472f 100644 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -13,7 +13,7 @@ module ActionController <<-RUBY_EVAL if options.key?(:#{r}) _process_options(options) - return _render_#{r}(options[:#{r}], options) + return render_#{r}(options[:#{r}], options) end RUBY_EVAL end @@ -52,7 +52,7 @@ module ActionController extend RenderOption register_renderer :json - def _render_json(json, options) + def render_json(json, options) json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON @@ -64,9 +64,9 @@ module ActionController extend RenderOption register_renderer :js - def _render_js(js, options) + def render_js(js, options) self.content_type ||= Mime::JS - self.response_body = js + self.response_body = js.respond_to?(:to_js) ? js.to_js : js end end @@ -74,7 +74,7 @@ module ActionController extend RenderOption register_renderer :xml - def _render_xml(xml, options) + def render_xml(xml, options) self.content_type ||= Mime::XML self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml end @@ -84,7 +84,7 @@ module ActionController extend RenderOption register_renderer :update - def _render_update(proc, options) + def render_update(proc, options) generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) self.content_type = Mime::JS self.response_body = generator.to_s -- cgit v1.2.3 From 16c01224cbe503ab1f4d0f02955b16662be00116 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 20:05:14 -0300 Subject: ActionController::Metal#to_rack converted to #to_a to match normal rack convention --- actionpack/lib/action_controller/metal.rb | 4 ++-- actionpack/lib/action_controller/metal/rack_convenience.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index e7d776b63e..82d05414b9 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -72,11 +72,11 @@ module ActionController def call(name, env) @_env = env process(name) - to_rack + to_a end # :api: private - def to_rack + def to_a [status, headers, response_body] end diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb index 805157b0e3..5fac445dab 100644 --- a/actionpack/lib/action_controller/metal/rack_convenience.rb +++ b/actionpack/lib/action_controller/metal/rack_convenience.rb @@ -20,7 +20,7 @@ module ActionController end # :api: private - def to_rack + def to_a @_response.prepare! @_response.to_a end -- cgit v1.2.3 From 71638e6760bed0445e5fefc185924b07076fef47 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 22:51:24 -0300 Subject: Move AbstractController to a top-level component --- actionpack/lib/action_controller/abstract.rb | 16 -- actionpack/lib/action_controller/abstract/base.rb | 159 -------------------- .../lib/action_controller/abstract/benchmarker.rb | 38 ----- .../lib/action_controller/abstract/callbacks.rb | 115 --------------- .../lib/action_controller/abstract/exceptions.rb | 12 -- .../lib/action_controller/abstract/helpers.rb | 82 ----------- .../lib/action_controller/abstract/layouts.rb | 164 --------------------- .../lib/action_controller/abstract/logger.rb | 52 ------- .../lib/action_controller/abstract/renderer.rb | 156 -------------------- actionpack/lib/action_controller/metal.rb | 2 - 10 files changed, 796 deletions(-) delete mode 100644 actionpack/lib/action_controller/abstract.rb delete mode 100644 actionpack/lib/action_controller/abstract/base.rb delete mode 100644 actionpack/lib/action_controller/abstract/benchmarker.rb delete mode 100644 actionpack/lib/action_controller/abstract/callbacks.rb delete mode 100644 actionpack/lib/action_controller/abstract/exceptions.rb delete mode 100644 actionpack/lib/action_controller/abstract/helpers.rb delete mode 100644 actionpack/lib/action_controller/abstract/layouts.rb delete mode 100644 actionpack/lib/action_controller/abstract/logger.rb delete mode 100644 actionpack/lib/action_controller/abstract/renderer.rb (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/abstract.rb b/actionpack/lib/action_controller/abstract.rb deleted file mode 100644 index d0eba253b8..0000000000 --- a/actionpack/lib/action_controller/abstract.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "active_support/core_ext/module/attr_internal" -require "active_support/core_ext/module/delegation" - -module AbstractController - autoload :Base, "action_controller/abstract/base" - autoload :Benchmarker, "action_controller/abstract/benchmarker" - autoload :Callbacks, "action_controller/abstract/callbacks" - autoload :Helpers, "action_controller/abstract/helpers" - autoload :Layouts, "action_controller/abstract/layouts" - autoload :Logger, "action_controller/abstract/logger" - autoload :Renderer, "action_controller/abstract/renderer" - # === Exceptions - autoload :ActionNotFound, "action_controller/abstract/exceptions" - autoload :DoubleRenderError, "action_controller/abstract/exceptions" - autoload :Error, "action_controller/abstract/exceptions" -end diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb deleted file mode 100644 index b93e6ce634..0000000000 --- a/actionpack/lib/action_controller/abstract/base.rb +++ /dev/null @@ -1,159 +0,0 @@ -module AbstractController - - class Base - attr_internal :response_body - attr_internal :action_name - - class << self - attr_reader :abstract - alias_method :abstract?, :abstract - - # Define a controller as abstract. See internal_methods for more - # details. - def abstract! - @abstract = true - end - - def inherited(klass) - ::AbstractController::Base.descendants << klass.to_s - super - end - - # A list of all descendents of AbstractController::Base. This is - # useful for initializers which need to add behavior to all controllers. - def descendants - @descendants ||= [] - end - - # A list of all internal methods for a controller. This finds the first - # abstract superclass of a controller, and gets a list of all public - # instance methods on that abstract class. Public instance methods of - # a controller would normally be considered action methods, so we - # are removing those methods on classes declared as abstract - # (ActionController::Metal and ActionController::Base are defined - # as abstract) - def internal_methods - controller = self - controller = controller.superclass until controller.abstract? - controller.public_instance_methods(true) - end - - # The list of hidden actions to an empty Array. Defaults to an - # empty Array. This can be modified by other modules or subclasses - # to specify particular actions as hidden. - # - # ==== Returns - # Array[String]:: An array of method names that should not be - # considered actions. - def hidden_actions - [] - end - - # A list of method names that should be considered actions. This - # includes all public instance methods on a controller, less - # any internal methods (see #internal_methods), adding back in - # any methods that are internal, but still exist on the class - # itself. Finally, #hidden_actions are removed. - # - # ==== Returns - # Array[String]:: A list of all methods that should be considered - # actions. - def action_methods - @action_methods ||= - # All public instance methods of this class, including ancestors - public_instance_methods(true).map { |m| m.to_s }.to_set - - # Except for public instance methods of Base and its ancestors - internal_methods.map { |m| m.to_s } + - # Be sure to include shadowed public instance methods of this class - public_instance_methods(false).map { |m| m.to_s } - - # And always exclude explicitly hidden actions - hidden_actions - end - end - - abstract! - - # Calls the action going through the entire action dispatch stack. - # - # The actual method that is called is determined by calling - # #method_for_action. If no method can handle the action, then an - # ActionNotFound error is raised. - # - # ==== Returns - # self - def process(action) - @_action_name = action_name = action.to_s - - unless action_name = method_for_action(action_name) - raise ActionNotFound, "The action '#{action}' could not be found" - end - - process_action(action_name) - self - end - - private - # Returns true if the name can be considered an action. This can - # be overridden in subclasses to modify the semantics of what - # can be considered an action. - # - # ==== Parameters - # name:: The name of an action to be tested - # - # ==== Returns - # TrueClass, FalseClass - def action_method?(name) - self.class.action_methods.include?(name) - end - - # Call the action. Override this in a subclass to modify the - # behavior around processing an action. This, and not #process, - # is the intended way to override action dispatching. - def process_action(method_name) - send_action(method_name) - end - - # Actually call the method associated with the action. Override - # this method if you wish to change how action methods are called, - # not to add additional behavior around it. For example, you would - # override #send_action if you want to inject arguments into the - # method. - alias send_action send - - # If the action name was not found, but a method called "action_missing" - # was found, #method_for_action will return "_handle_action_missing". - # This method calls #action_missing with the current action name. - def _handle_action_missing - action_missing(@_action_name) - end - - # Takes an action name and returns the name of the method that will - # handle the action. In normal cases, this method returns the same - # name as it receives. By default, if #method_for_action receives - # a name that is not an action, it will look for an #action_missing - # method and return "_handle_action_missing" if one is found. - # - # Subclasses may override this method to add additional conditions - # that should be considered an action. For instance, an HTTP controller - # with a template matching the action name is considered to exist. - # - # If you override this method to handle additional cases, you may - # also provide a method (like _handle_method_missing) to handle - # the case. - # - # If none of these conditions are true, and method_for_action - # returns nil, an ActionNotFound exception will be raised. - # - # ==== Parameters - # action_name:: An action name to find a method name for - # - # ==== Returns - # String:: The name of the method that handles the action - # nil:: No method name could be found. Raise ActionNotFound. - def method_for_action(action_name) - if action_method?(action_name) then action_name - elsif respond_to?(:action_missing, true) then "_handle_action_missing" - end - end - end -end diff --git a/actionpack/lib/action_controller/abstract/benchmarker.rb b/actionpack/lib/action_controller/abstract/benchmarker.rb deleted file mode 100644 index 58e9564c2f..0000000000 --- a/actionpack/lib/action_controller/abstract/benchmarker.rb +++ /dev/null @@ -1,38 +0,0 @@ -module AbstractController - module Benchmarker - extend ActiveSupport::Concern - - include Logger - - module ClassMethods - # Execute the passed in block, timing the duration of the block in ms. - # - # ==== Parameters - # title<#to_s>:: The title of block to benchmark - # log_level:: A valid log level. Defaults to Logger::DEBUG - # use_silence:: Whether or not to silence the - # logger for the duration of the block. - # - # ==== Returns - # Object:: The result of the block - def benchmark(title, log_level = ::Logger::DEBUG, use_silence = true) - if logger && logger.level >= log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") - result - else - yield - end - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger - yield - ensure - logger.level = old_logger_level if logger - end - end - end -end diff --git a/actionpack/lib/action_controller/abstract/callbacks.rb b/actionpack/lib/action_controller/abstract/callbacks.rb deleted file mode 100644 index ea4b59466e..0000000000 --- a/actionpack/lib/action_controller/abstract/callbacks.rb +++ /dev/null @@ -1,115 +0,0 @@ -require "active_support/new_callbacks" - -module AbstractController - module Callbacks - extend ActiveSupport::Concern - - # Uses ActiveSupport::NewCallbacks as the base functionality. For - # more details on the whole callback system, read the documentation - # for ActiveSupport::NewCallbacks. - include ActiveSupport::NewCallbacks - - included do - define_callbacks :process_action, "response_body" - end - - # Override AbstractController::Base's process_action to run the - # process_action callbacks around the normal behavior. - def process_action(method_name) - _run_process_action_callbacks(method_name) do - super - end - end - - module ClassMethods - # If :only or :accept are used, convert the options into the - # primitive form (:per_key) used by ActiveSupport::Callbacks. - # The basic idea is that :only => :index gets converted to - # :if => proc {|c| c.action_name == "index" }, but that the - # proc is only evaluated once per action for the lifetime of - # a Rails process. - # - # ==== Options - # :only<#to_s>:: The callback should be run only for this action - # :except<#to_s>:: The callback should be run for all actions - # except this action - def _normalize_callback_options(options) - if only = options[:only] - only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ") - options[:per_key] = {:if => only} - end - if except = options[:except] - except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ") - options[:per_key] = {:unless => except} - end - end - - # Skip before, after, and around filters matching any of the names - # - # ==== Parameters - # *names:: A list of valid names that could be used for - # callbacks. Note that skipping uses Ruby equality, so it's - # impossible to skip a callback defined using an anonymous proc - # using #skip_filter - def skip_filter(*names, &blk) - skip_before_filter(*names) - skip_after_filter(*names) - skip_around_filter(*names) - end - - # Take callback names and an optional callback proc, normalize them, - # then call the block with each callback. This allows us to abstract - # the normalization across several methods that use it. - # - # ==== Parameters - # callbacks:: A list of callbacks, with an optional - # options hash as the last parameter. - # block:: A proc that should be added to the callbacks. - # - # ==== Block Parameters - # name:: The callback to be added - # options:: A list of options to be used when adding the callback - def _insert_callbacks(callbacks, block) - options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} - _normalize_callback_options(options) - callbacks.push(block) if block - callbacks.each do |callback| - yield callback, options - end - end - - # set up before_filter, prepend_before_filter, skip_before_filter, etc. - # for each of before, after, and around. - [:before, :after, :around].each do |filter| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - # Append a before, after or around filter. See _insert_callbacks - # for details on the allowed parameters. - def #{filter}_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{filter}, name, options) - end - end - - # Prepend a before, after or around filter. See _insert_callbacks - # for details on the allowed parameters. - def prepend_#{filter}_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) - end - end - - # Skip a before, after or around filter. See _insert_callbacks - # for details on the allowed parameters. - def skip_#{filter}_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| - skip_callback(:process_action, :#{filter}, name, options) - end - end - - # *_filter is the same as append_*_filter - alias_method :append_#{filter}_filter, :#{filter}_filter - RUBY_EVAL - end - end - end -end diff --git a/actionpack/lib/action_controller/abstract/exceptions.rb b/actionpack/lib/action_controller/abstract/exceptions.rb deleted file mode 100644 index b671516de1..0000000000 --- a/actionpack/lib/action_controller/abstract/exceptions.rb +++ /dev/null @@ -1,12 +0,0 @@ -module AbstractController - class Error < StandardError; end - class ActionNotFound < StandardError; end - - class DoubleRenderError < Error - DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end -end diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb deleted file mode 100644 index 5efa37fde3..0000000000 --- a/actionpack/lib/action_controller/abstract/helpers.rb +++ /dev/null @@ -1,82 +0,0 @@ -module AbstractController - module Helpers - extend ActiveSupport::Concern - - include Renderer - - included do - extlib_inheritable_accessor(:_helpers) { Module.new } - end - - module ClassMethods - # When a class is inherited, wrap its helper module in a new module. - # This ensures that the parent class's module can be changed - # independently of the child class's. - def inherited(klass) - helpers = _helpers - klass._helpers = Module.new { include helpers } - - super - end - - # Declare a controller method as a helper. For example, the following - # makes the +current_user+ controller method available to the view: - # class ApplicationController < ActionController::Base - # helper_method :current_user, :logged_in? - # - # def current_user - # @current_user ||= User.find_by_id(session[:user]) - # end - # - # def logged_in? - # current_user != nil - # end - # end - # - # In a view: - # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> - # - # ==== Parameters - # meths:: The name of a method on the controller - # to be made available on the view. - def helper_method(*meths) - meths.flatten.each do |meth| - _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def #{meth}(*args, &blk) - controller.send(%(#{meth}), *args, &blk) - end - ruby_eval - end - end - - # Make a number of helper modules part of this class' default - # helpers. - # - # ==== Parameters - # *args:: Modules to be included - # block:: Evalulate the block in the context - # of the helper module. Any methods defined in the block - # will be helpers. - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - end - end - _helpers.module_eval(&block) if block_given? - end - - private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # mod:: The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end - end - end -end diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb deleted file mode 100644 index f021dd8b62..0000000000 --- a/actionpack/lib/action_controller/abstract/layouts.rb +++ /dev/null @@ -1,164 +0,0 @@ -module AbstractController - module Layouts - extend ActiveSupport::Concern - - include Renderer - - included do - extlib_inheritable_accessor(:_layout_conditions) { Hash.new } - _write_layout_method - end - - module ClassMethods - def inherited(klass) - super - klass._write_layout_method - end - - # Specify the layout to use for this class. - # - # If the specified layout is a: - # String:: the String is the template name - # Symbol:: call the method specified by the symbol, which will return - # the template name - # false:: There is no layout - # true:: raise an ArgumentError - # - # ==== Parameters - # layout:: The layout to use. - # - # ==== Options (conditions) - # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to. - # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one - def layout(layout, conditions = {}) - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } - self._layout_conditions = conditions - - @_layout = layout || false # Converts nil to false - _write_layout_method - end - - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # String:: A template name - def _implied_layout_name - name.underscore - end - - # Takes the specified layout and creates a _layout method to be called - # by _default_layout - # - # If there is no explicit layout specified: - # If a layout is found in the view paths with the controller's - # name, return that string. Otherwise, use the superclass' - # layout (which might also be implied) - def _write_layout_method - case @_layout - when String - self.class_eval %{def _layout(details) #{@_layout.inspect} end} - when Symbol - self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def _layout(details) - #{@_layout}.tap do |layout| - unless layout.is_a?(String) || !layout - raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \ - "should have returned a String, false, or nil" - end - end - end - ruby_eval - when false - self.class_eval %{def _layout(details) end} - when true - raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" - when nil - self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def _layout(details) - if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts") - "#{_implied_layout_name}" - else - super - end - end - ruby_eval - end - self.class_eval { private :_layout } - end - end - - private - # This will be overwritten by _write_layout_method - def _layout(details) end - - # Determine the layout for a given name and details. - # - # ==== Parameters - # name:: The name of the template - # details Object}>:: A list of details to restrict - # the lookup to. By default, layout lookup is limited to the - # formats specified for the current request. - def _layout_for_name(name, details = {:formats => formats}) - name && _find_layout(name, details) - end - - # Take in the name and details and find a Template. - # - # ==== Parameters - # name:: The name of the template to retrieve - # details:: A list of details to restrict the search by. This - # might include details like the format or locale of the template. - # - # ==== Returns - # Template:: A template object matching the name and details - def _find_layout(name, details) - # TODO: Make prefix actually part of details in ViewPath#find_by_parts - prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - view_paths.find_by_parts(name, details, prefix) - end - - # Returns the default layout for this controller and a given set of details. - # Optionally raises an exception if the layout could not be found. - # - # ==== Parameters - # details:: A list of details to restrict the search by. This - # might include details like the format or locale of the template. - # require_layout:: If this is true, raise an ArgumentError - # with details about the fact that the exception could not be - # found (defaults to false) - # - # ==== Returns - # Template:: The template object for the default layout (or nil) - def _default_layout(details, require_layout = false) - if require_layout && _action_has_layout? && !_layout(details) - raise ArgumentError, - "There was no default layout for #{self.class} in #{view_paths.inspect}" - end - - begin - _layout_for_name(_layout(details), details) if _action_has_layout? - rescue NameError => e - raise NoMethodError, - "You specified #{@_layout.inspect} as the layout, but no such method was found" - end - end - - # Determines whether the current action has a layout by checking the - # action name against the :only and :except conditions set on the - # layout. - # - # ==== Returns - # Boolean:: True if the action has a layout, false otherwise. - def _action_has_layout? - conditions = _layout_conditions - if only = conditions[:only] - only.include?(action_name) - elsif except = conditions[:except] - !except.include?(action_name) - else - true - end - end - end -end diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb deleted file mode 100644 index fd33bd2ddd..0000000000 --- a/actionpack/lib/action_controller/abstract/logger.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'active_support/core_ext/logger' - -module AbstractController - module Logger - extend ActiveSupport::Concern - - # A class that allows you to defer expensive processing - # until the logger actually tries to log. Otherwise, you are - # forced to do the processing in advance, and send the - # entire processed String to the logger, which might - # just discard the String if the log level is too low. - # - # TODO: Require that Rails loggers accept a block. - class DelayedLog - def initialize(&blk) - @blk = blk - end - - def to_s - @blk.call - end - alias to_str to_s - end - - included do - cattr_accessor :logger - end - - # Override process_action in the AbstractController::Base - # to log details about the method. - def process_action(action) - super - - if logger - log = DelayedLog.new do - "\n\nProcessing #{self.class.name}\##{action_name} " \ - "to #{request.formats} " \ - "(for #{request_origin}) [#{request.method.to_s.upcase}]" - end - - logger.info(log) - end - end - - private - def request_origin - # this *needs* to be cached! - # otherwise you'd get different results if calling it more than once - @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}" - end - end -end diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb deleted file mode 100644 index fe556281ab..0000000000 --- a/actionpack/lib/action_controller/abstract/renderer.rb +++ /dev/null @@ -1,156 +0,0 @@ -require "action_controller/abstract/logger" - -module AbstractController - module Renderer - extend ActiveSupport::Concern - - include AbstractController::Logger - - included do - attr_internal :formats - - extlib_inheritable_accessor :_view_paths - - self._view_paths ||= ActionView::PathSet.new - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template:: The template to render - # layout:: The layout to render around the template - # options:: See _render_template_with_layout in ActionView::Base - # partial:: Whether or not the template to render is a partial - # - # Override this method in a to change the default behavior. - def view_context - @_view_context ||= ActionView::Base.for_controller(self) - end - - # Mostly abstracts the fact that calling render twice is a DoubleRenderError. - # Delegates render_to_body and sticks the result in self.response_body. - def render(*args) - if response_body - raise AbstractController::DoubleRenderError, "OMG" - end - - self.response_body = render_to_body(*args) - end - - # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # - # :api: plugin - def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options[:_partial_object] - view_context.render_partial(options) - else - _determine_template(options) - _render_template(options) - end - end - - # Raw rendering of a template to a string. Just convert the results of - # render_to_body into a String. - # - # :api: plugin - def render_to_string(options = {}) - AbstractController::Renderer.body_to_s(render_to_body(options)) - end - - # Renders the template from an object. - # - # ==== Options - # _template:: The template to render - # _layout:: The layout to wrap the template in (optional) - # _partial:: Whether or not the template to be rendered is a partial - def _render_template(options) - view_context.render_template(options[:_template], options[:_layout], options, options[:_partial]) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths - end - - # Return a string representation of a Rack-compatible response body. - def self.body_to_s(body) - if body.respond_to?(:to_str) - body - else - strings = [] - body.each { |part| strings << part.to_s } - body.close if body.respond_to?(:close) - strings.join - end - end - - private - # Take in a set of options and determine the template to render - # - # ==== Options - # _template:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial:: Whether or not the file to look up is a partial - def _determine_template(options) - name = (options[:_template_name] || action_name).to_s - - options[:_template] ||= view_paths.find_by_parts( - name, { :formats => formats }, options[:_prefix], options[:_partial] - ) - end - - module ClassMethods - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths << path - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - self.view_paths.unshift(path) - end - - # A list of all of the default view paths for this controller. - def view_paths - self._view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - self._view_paths = paths.is_a?(ActionView::PathSet) ? - paths : ActionView::Base.process_view_paths(paths) - end - end - end -end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 82d05414b9..5333ca497c 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,5 +1,3 @@ -require 'action_controller/abstract' - module ActionController # ActionController::Metal provides a way to get a valid Rack application from a controller. # -- cgit v1.2.3 From 0435178ff896e9b3854cb53cbd9fbd33a8f78209 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 6 Aug 2009 22:57:42 -0300 Subject: Remove file that doesn't seem to be used anymore --- actionpack/lib/action_controller/old_base.rb | 84 ---------------------------- 1 file changed, 84 deletions(-) delete mode 100644 actionpack/lib/action_controller/old_base.rb (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/old_base.rb b/actionpack/lib/action_controller/old_base.rb deleted file mode 100644 index dd22bfd617..0000000000 --- a/actionpack/lib/action_controller/old_base.rb +++ /dev/null @@ -1,84 +0,0 @@ -#-- -# Copyright (c) 2004-2009 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift activesupport_path if File.directory?(activesupport_path) -require 'active_support' - -require File.join(File.dirname(__FILE__), "action_pack") - -module ActionController - # TODO: Review explicit to see if they will automatically be handled by - # the initilizer if they are really needed. - def self.load_all! - [Base, Request, Response, UrlRewriter, UrlWriter] - [ActionDispatch::Http::Headers] - end - - autoload :Base, 'action_controller/base/base' - autoload :Benchmarking, 'action_controller/base/chained/benchmarking' - autoload :Caching, 'action_controller/caching' - autoload :Cookies, 'action_controller/base/cookies' - autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :Filters, 'action_controller/base/chained/filters' - autoload :Flash, 'action_controller/base/chained/flash' - autoload :Helpers, 'action_controller/base/helpers' - autoload :HttpAuthentication, 'action_controller/base/http_authentication' - autoload :Integration, 'action_controller/testing/integration' - autoload :IntegrationTest, 'action_controller/testing/integration' - autoload :Layout, 'action_controller/base/layout' - autoload :MimeResponds, 'action_controller/base/mime_responds' - autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' - autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Redirector, 'action_controller/base/redirect' - autoload :Renderer, 'action_controller/base/render' - autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection' - autoload :Rescue, 'action_controller/base/rescue' - autoload :Resources, 'action_controller/routing/resources' - autoload :Responder, 'action_controller/base/responder' - autoload :Routing, 'action_controller/routing' - autoload :SessionManagement, 'action_controller/base/session_management' - autoload :Streaming, 'action_controller/base/streaming' - autoload :TestCase, 'action_controller/testing/test_case' - autoload :TestProcess, 'action_controller/testing/process' - autoload :Translation, 'action_controller/translation' - autoload :UrlEncodedPairParser, 'action_controller/dispatch/url_encoded_pair_parser' - autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' - autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' - autoload :Verification, 'action_controller/base/verification' - autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging' - - module Assertions - autoload :DomAssertions, 'action_controller/testing/assertions/dom' - autoload :ModelAssertions, 'action_controller/testing/assertions/model' - autoload :ResponseAssertions, 'action_controller/testing/assertions/response' - autoload :RoutingAssertions, 'action_controller/testing/assertions/routing' - autoload :SelectorAssertions, 'action_controller/testing/assertions/selector' - autoload :TagAssertions, 'action_controller/testing/assertions/tag' - end -end - -autoload :HTML, 'action_controller/vendor/html-scanner' - -require 'action_dispatch' -require 'action_view' -- cgit v1.2.3 From 70a440aa277676078e3b8baafe1101d1287e114f Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 7 Aug 2009 00:52:13 -0300 Subject: Clean up render @object a bit more. --- actionpack/lib/action_controller/record_identifier.rb | 15 --------------- 1 file changed, 15 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index b4408e4e1d..1165c3b7c5 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -36,21 +36,6 @@ module ActionController JOIN = '_'.freeze NEW = 'new'.freeze - # Returns plural/singular for a record or class. Example: - # - # partial_path(post) # => "posts/post" - # partial_path(Person) # => "people/person" - # partial_path(Person, "admin/games") # => "admin/people/person" - def partial_path(record_or_class, controller_path = nil) - name = model_name_from_record_or_class(record_or_class) - - if controller_path && controller_path.include?("/") - "#{File.dirname(controller_path)}/#{name.partial_path}" - else - name.partial_path - end - end - # The DOM class convention is to use the singular form of an object or class. Examples: # # dom_class(post) # => "post" -- cgit v1.2.3 From 8534c5bf193c6a234d55116d520a54a1b634a93e Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 7 Aug 2009 01:51:50 -0300 Subject: Start cleaning up partial path --- actionpack/lib/action_controller/metal/renderer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb index 572da451ff..5a458764ad 100644 --- a/actionpack/lib/action_controller/metal/renderer.rb +++ b/actionpack/lib/action_controller/metal/renderer.rb @@ -57,7 +57,7 @@ module ActionController when true options[:_prefix] = _prefix when String - options[:_prefix] = _prefix unless partial.index('/') + options[:_prefix] = _prefix unless partial.include?(?/) options[:_template_name] = partial else options[:_partial_object] = true -- cgit v1.2.3