mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-03 15:58:50 +01:00
freeze rails 2.2
This commit is contained in:
parent
fe5f962dcf
commit
59d5d4c8b6
1468 changed files with 213171 additions and 0 deletions
69
vendor/rails/actionpack/lib/action_controller/assertions.rb
vendored
Normal file
69
vendor/rails/actionpack/lib/action_controller/assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
require 'test/unit/assertions'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||
# can be used against. These collections are:
|
||||
#
|
||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||
# * session: Objects being saved in the session.
|
||||
# * flash: The flash objects currently in the session.
|
||||
# * cookies: Cookies being sent to the user on this request.
|
||||
#
|
||||
# These collections can be used just like any other hash:
|
||||
#
|
||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||
#
|
||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
#
|
||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||
# action call which can then be asserted against.
|
||||
#
|
||||
# == Manipulating the request collections
|
||||
#
|
||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||
# and cookies, though. For sessions, you just do:
|
||||
#
|
||||
# @request.session[:key] = "value"
|
||||
#
|
||||
# For cookies, you need to manually create the cookie, like this:
|
||||
#
|
||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
||||
#
|
||||
# == Testing named routes
|
||||
#
|
||||
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
|
||||
# Example:
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
module Assertions
|
||||
def self.included(klass)
|
||||
%w(response selector tag dom routing model).each do |kind|
|
||||
require "action_controller/assertions/#{kind}_assertions"
|
||||
klass.module_eval { include const_get("#{kind.camelize}Assertions") }
|
||||
end
|
||||
end
|
||||
|
||||
def clean_backtrace(&block)
|
||||
yield
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions"))
|
||||
error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path }
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
include ActionController::Assertions
|
||||
end
|
||||
end
|
||||
end
|
||||
39
vendor/rails/actionpack/lib/action_controller/assertions/dom_assertions.rb
vendored
Normal file
39
vendor/rails/actionpack/lib/action_controller/assertions/dom_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module ActionController
|
||||
module Assertions
|
||||
module DomAssertions
|
||||
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the referenced method generates the appropriate HTML string
|
||||
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
|
||||
#
|
||||
def assert_dom_equal(expected, actual, message = "")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
|
||||
assert_block(full_message) { expected_dom == actual_dom }
|
||||
end
|
||||
end
|
||||
|
||||
# The negated form of +assert_dom_equivalent+.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the referenced method does not generate the specified HTML string
|
||||
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
|
||||
#
|
||||
def assert_dom_not_equal(expected, actual, message = "")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
|
||||
assert_block(full_message) { expected_dom != actual_dom }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb
vendored
Normal file
20
vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
module ActionController
|
||||
module Assertions
|
||||
module ModelAssertions
|
||||
# Ensures that the passed record is valid by Active Record standards and
|
||||
# returns any error messages if it is not.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that a newly created record is valid
|
||||
# model = Model.new
|
||||
# assert_valid(model)
|
||||
#
|
||||
def assert_valid(record)
|
||||
clean_backtrace do
|
||||
assert record.valid?, record.errors.full_messages.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
123
vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb
vendored
Normal file
123
vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
require 'rexml/document'
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
# A small suite of assertions that test responses from Rails applications.
|
||||
module ResponseAssertions
|
||||
# Asserts that the response is one of the following types:
|
||||
#
|
||||
# * <tt>:success</tt> - Status code was 200
|
||||
# * <tt>:redirect</tt> - Status code was in the 300-399 range
|
||||
# * <tt>:missing</tt> - Status code was 404
|
||||
# * <tt>:error</tt> - Status code was in the 500-599 range
|
||||
#
|
||||
# You can also pass an explicit status number like assert_response(501)
|
||||
# or its symbolic equivalent assert_response(:not_implemented).
|
||||
# See ActionController::StatusCodes for a full list.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the response was a redirection
|
||||
# assert_response :redirect
|
||||
#
|
||||
# # assert that the response code was status code 401 (unauthorized)
|
||||
# assert_response 401
|
||||
#
|
||||
def assert_response(type, message = nil)
|
||||
clean_backtrace do
|
||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Fixnum) && @response.response_code == type
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
|
||||
assert_block("") { true } # to count the assertion
|
||||
else
|
||||
if @response.error?
|
||||
exception = @response.template.instance_variable_get(:@exception)
|
||||
exception_message = exception && exception.message
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>\n<?>", type, @response.response_code, exception_message.to_s)) { false }
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Assert that the redirection options passed in match those of the redirect called in the latest action.
|
||||
# This match can be partial, such that assert_redirected_to(:controller => "weblog") will also
|
||||
# match the redirection of redirect_to(:controller => "weblog", :action => "show") and so on.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the redirection was to the "index" action on the WeblogController
|
||||
# assert_redirected_to :controller => "weblog", :action => "index"
|
||||
#
|
||||
# # assert that the redirection was to the named route login_url
|
||||
# assert_redirected_to login_url
|
||||
#
|
||||
# # assert that the redirection was to the url for @customer
|
||||
# assert_redirected_to @customer
|
||||
#
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
assert_response(:redirect, message)
|
||||
return true if options == @response.redirected_to
|
||||
|
||||
# Support partial arguments for hash redirections
|
||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||
end
|
||||
|
||||
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
|
||||
options_after_normalisation = normalize_argument_to_redirection(options)
|
||||
|
||||
if redirected_to_after_normalisation != options_after_normalisation
|
||||
flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the "new" view template was rendered
|
||||
# assert_template "new"
|
||||
#
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = @response.rendered_template.to_s
|
||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
||||
assert_block(msg) do
|
||||
if expected.nil?
|
||||
@response.rendered_template.blank?
|
||||
else
|
||||
rendered.to_s.match(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Proxy to to_param if the object will respond to it.
|
||||
def parameterize(value)
|
||||
value.respond_to?(:to_param) ? value.to_param : value
|
||||
end
|
||||
|
||||
def normalize_argument_to_redirection(fragment)
|
||||
after_routing = @controller.url_for(fragment)
|
||||
if after_routing =~ %r{^\w+://.*}
|
||||
after_routing
|
||||
else
|
||||
# FIXME - this should probably get removed.
|
||||
if after_routing.first != '/'
|
||||
after_routing = '/' + after_routing
|
||||
end
|
||||
@request.protocol + @request.host_with_port + after_routing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
146
vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb
vendored
Normal file
146
vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
module ActionController
|
||||
module Assertions
|
||||
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
|
||||
module RoutingAssertions
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
|
||||
#
|
||||
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
|
||||
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
|
||||
# and a :method containing the required HTTP verb.
|
||||
#
|
||||
# # assert that POSTing to /items will call the create action on ItemsController
|
||||
# assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}
|
||||
#
|
||||
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
|
||||
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
|
||||
# extras argument, appending the query string on the path directly will not work. For example:
|
||||
#
|
||||
# # assert that a path of '/items/list/1?view=print' returns the correct options
|
||||
# assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }
|
||||
#
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Check the default route (i.e., the index action)
|
||||
# assert_recognizes {:controller => 'items', :action => 'index'}, 'items'
|
||||
#
|
||||
# # Test a specific action
|
||||
# assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list'
|
||||
#
|
||||
# # Test an action with a parameter
|
||||
# assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1'
|
||||
#
|
||||
# # Test a custom route
|
||||
# assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1'
|
||||
#
|
||||
# # Check a Simply RESTful generated route
|
||||
# assert_recognizes list_items_url, 'items/list'
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
if path.is_a? Hash
|
||||
request_method = path[:method]
|
||||
path = path[:path]
|
||||
else
|
||||
request_method = nil
|
||||
end
|
||||
|
||||
clean_backtrace do
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
request = recognized_request_for(path, request_method)
|
||||
|
||||
expected_options = expected_options.clone
|
||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
||||
|
||||
expected_options.stringify_keys!
|
||||
routing_diff = expected_options.diff(request.path_parameters)
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
||||
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
|
||||
assert_block(msg) { request.path_parameters == expected_options }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
|
||||
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
|
||||
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
|
||||
#
|
||||
# The +defaults+ parameter is unused.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Asserts that the default action is generated for a route with no action
|
||||
# assert_generates "/items", :controller => "items", :action => "index"
|
||||
#
|
||||
# # Tests that the list action is properly routed
|
||||
# assert_generates "/items/list", :controller => "items", :action => "list"
|
||||
#
|
||||
# # Tests the generation of a route with a parameter
|
||||
# assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
|
||||
#
|
||||
# # Asserts that the generated route gives us our custom route
|
||||
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
|
||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
||||
|
||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
||||
assert_block(msg) { found_extras == extras }
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
expected_path)
|
||||
assert_block(msg) { expected_path == generated_path }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
|
||||
# and +assert_generates+ into one step.
|
||||
#
|
||||
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Assert a basic route: a controller with the default action (index)
|
||||
# assert_routing '/home', :controller => 'home', :action => 'index'
|
||||
#
|
||||
# # Test a route generated with a specific controller, action, and parameter (id)
|
||||
# assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23
|
||||
#
|
||||
# # Assert a basic route (controller + default action), with an error message if it fails
|
||||
# assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
|
||||
#
|
||||
# # Tests a route, providing a defaults hash
|
||||
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
|
||||
#
|
||||
# # Tests a route with a HTTP method
|
||||
# assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
||||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
private
|
||||
# Recognizes the route for a given path.
|
||||
def recognized_request_for(path, request_method = nil)
|
||||
path = "/#{path}" unless path.first == '/'
|
||||
|
||||
# Assume given controller
|
||||
request = ActionController::TestRequest.new({}, {}, nil)
|
||||
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
|
||||
request.path = path
|
||||
|
||||
ActionController::Routing::Routes.recognize(request)
|
||||
request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
627
vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb
vendored
Normal file
627
vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,627 @@
|
|||
#--
|
||||
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
|
||||
# Under MIT and/or CC By license.
|
||||
#++
|
||||
|
||||
require 'rexml/document'
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
unless const_defined?(:NO_STRIP)
|
||||
NO_STRIP = %w{pre script style textarea}
|
||||
end
|
||||
|
||||
# Adds the +assert_select+ method for use in Rails functional
|
||||
# test cases, which can be used to make assertions on the response HTML of a controller
|
||||
# action. You can also call +assert_select+ within another +assert_select+ to
|
||||
# make assertions on elements selected by the enclosing assertion.
|
||||
#
|
||||
# Use +css_select+ to select elements without making an assertions, either
|
||||
# from the response HTML or elements selected by the enclosing assertion.
|
||||
#
|
||||
# In addition to HTML responses, you can make the following assertions:
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
|
||||
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
module SelectorAssertions
|
||||
# :call-seq:
|
||||
# css_select(selector) => array
|
||||
# css_select(element, selector) => array
|
||||
#
|
||||
# Select and return all matching elements.
|
||||
#
|
||||
# If called with a single argument, uses that argument as a selector
|
||||
# to match all elements of the current page. Returns an empty array
|
||||
# if no match is found.
|
||||
#
|
||||
# If called with two arguments, uses the first argument as the base
|
||||
# element and the second argument as the selector. Attempts to match the
|
||||
# base element and any of its children. Returns an empty array if no
|
||||
# match is found.
|
||||
#
|
||||
# The selector may be a CSS selector expression (String), an expression
|
||||
# with substitution values (Array) or an HTML::Selector object.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Selects all div tags
|
||||
# divs = css_select("div")
|
||||
#
|
||||
# # Selects all paragraph tags and does something interesting
|
||||
# pars = css_select("p")
|
||||
# pars.each do |par|
|
||||
# # Do something fun with paragraphs here...
|
||||
# end
|
||||
#
|
||||
# # Selects all list items in unordered lists
|
||||
# items = css_select("ul>li")
|
||||
#
|
||||
# # Selects all form tags and then all inputs inside the form
|
||||
# forms = css_select("form")
|
||||
# forms.each do |form|
|
||||
# inputs = css_select(form, "input")
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
def css_select(*args)
|
||||
# See assert_select to understand what's going on here.
|
||||
arg = args.shift
|
||||
|
||||
if arg.is_a?(HTML::Node)
|
||||
root = arg
|
||||
arg = args.shift
|
||||
elsif arg == nil
|
||||
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
|
||||
elsif @selected
|
||||
matches = []
|
||||
|
||||
@selected.each do |selected|
|
||||
subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
|
||||
subset.each do |match|
|
||||
matches << match unless matches.any? { |m| m.equal?(match) }
|
||||
end
|
||||
end
|
||||
|
||||
return matches
|
||||
else
|
||||
root = response_from_page_or_rjs
|
||||
end
|
||||
|
||||
case arg
|
||||
when String
|
||||
selector = HTML::Selector.new(arg, args)
|
||||
when Array
|
||||
selector = HTML::Selector.new(*arg)
|
||||
when HTML::Selector
|
||||
selector = arg
|
||||
else raise ArgumentError, "Expecting a selector as the first argument"
|
||||
end
|
||||
|
||||
selector.select(root)
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# assert_select(selector, equality?, message?)
|
||||
# assert_select(element, selector, equality?, message?)
|
||||
#
|
||||
# An assertion that selects elements and makes one or more equality tests.
|
||||
#
|
||||
# If the first argument is an element, selects all matching elements
|
||||
# starting from (and including) that element and all its children in
|
||||
# depth-first order.
|
||||
#
|
||||
# If no element if specified, calling +assert_select+ will select from the
|
||||
# response HTML. Calling #assert_select inside an +assert_select+ block will
|
||||
# run the assertion for each element selected by the enclosing assertion.
|
||||
#
|
||||
# ==== Example
|
||||
# assert_select "ol>li" do |elements|
|
||||
# elements.each do |element|
|
||||
# assert_select element, "li"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Or for short:
|
||||
# assert_select "ol>li" do
|
||||
# assert_select "li"
|
||||
# end
|
||||
#
|
||||
# The selector may be a CSS selector expression (String), an expression
|
||||
# with substitution values, or an HTML::Selector object.
|
||||
#
|
||||
# === Equality Tests
|
||||
#
|
||||
# The equality test may be one of the following:
|
||||
# * <tt>true</tt> - Assertion is true if at least one element selected.
|
||||
# * <tt>false</tt> - Assertion is true if no element selected.
|
||||
# * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
|
||||
# one element matches the string or regular expression.
|
||||
# * <tt>Integer</tt> - Assertion is true if exactly that number of
|
||||
# elements are selected.
|
||||
# * <tt>Range</tt> - Assertion is true if the number of selected
|
||||
# elements fit the range.
|
||||
# If no equality test specified, the assertion is true if at least one
|
||||
# element selected.
|
||||
#
|
||||
# To perform more than one equality tests, use a hash with the following keys:
|
||||
# * <tt>:text</tt> - Narrow the selection to elements that have this text
|
||||
# value (string or regexp).
|
||||
# * <tt>:html</tt> - Narrow the selection to elements that have this HTML
|
||||
# content (string or regexp).
|
||||
# * <tt>:count</tt> - Assertion is true if the number of selected elements
|
||||
# is equal to this value.
|
||||
# * <tt>:minimum</tt> - Assertion is true if the number of selected
|
||||
# elements is at least this value.
|
||||
# * <tt>:maximum</tt> - Assertion is true if the number of selected
|
||||
# elements is at most this value.
|
||||
#
|
||||
# If the method is called with a block, once all equality tests are
|
||||
# evaluated the block is called with an array of all matched elements.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # At least one form element
|
||||
# assert_select "form"
|
||||
#
|
||||
# # Form element includes four input fields
|
||||
# assert_select "form input", 4
|
||||
#
|
||||
# # Page title is "Welcome"
|
||||
# assert_select "title", "Welcome"
|
||||
#
|
||||
# # Page title is "Welcome" and there is only one title element
|
||||
# assert_select "title", {:count=>1, :text=>"Welcome"},
|
||||
# "Wrong title or more than one title element"
|
||||
#
|
||||
# # Page contains no forms
|
||||
# assert_select "form", false, "This page must contain no forms"
|
||||
#
|
||||
# # Test the content and style
|
||||
# assert_select "body div.header ul.menu"
|
||||
#
|
||||
# # Use substitution values
|
||||
# assert_select "ol>li#?", /item-\d+/
|
||||
#
|
||||
# # All input fields in the form have a name
|
||||
# assert_select "form input" do
|
||||
# assert_select "[name=?]", /.+/ # Not empty
|
||||
# end
|
||||
def assert_select(*args, &block)
|
||||
# Start with optional element followed by mandatory selector.
|
||||
arg = args.shift
|
||||
|
||||
if arg.is_a?(HTML::Node)
|
||||
# First argument is a node (tag or text, but also HTML root),
|
||||
# so we know what we're selecting from.
|
||||
root = arg
|
||||
arg = args.shift
|
||||
elsif arg == nil
|
||||
# This usually happens when passing a node/element that
|
||||
# happens to be nil.
|
||||
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
|
||||
elsif @selected
|
||||
root = HTML::Node.new(nil)
|
||||
root.children.concat @selected
|
||||
else
|
||||
# Otherwise just operate on the response document.
|
||||
root = response_from_page_or_rjs
|
||||
end
|
||||
|
||||
# First or second argument is the selector: string and we pass
|
||||
# all remaining arguments. Array and we pass the argument. Also
|
||||
# accepts selector itself.
|
||||
case arg
|
||||
when String
|
||||
selector = HTML::Selector.new(arg, args)
|
||||
when Array
|
||||
selector = HTML::Selector.new(*arg)
|
||||
when HTML::Selector
|
||||
selector = arg
|
||||
else raise ArgumentError, "Expecting a selector as the first argument"
|
||||
end
|
||||
|
||||
# Next argument is used for equality tests.
|
||||
equals = {}
|
||||
case arg = args.shift
|
||||
when Hash
|
||||
equals = arg
|
||||
when String, Regexp
|
||||
equals[:text] = arg
|
||||
when Integer
|
||||
equals[:count] = arg
|
||||
when Range
|
||||
equals[:minimum] = arg.begin
|
||||
equals[:maximum] = arg.end
|
||||
when FalseClass
|
||||
equals[:count] = 0
|
||||
when NilClass, TrueClass
|
||||
equals[:minimum] = 1
|
||||
else raise ArgumentError, "I don't understand what you're trying to match"
|
||||
end
|
||||
|
||||
# By default we're looking for at least one match.
|
||||
if equals[:count]
|
||||
equals[:minimum] = equals[:maximum] = equals[:count]
|
||||
else
|
||||
equals[:minimum] = 1 unless equals[:minimum]
|
||||
end
|
||||
|
||||
# Last argument is the message we use if the assertion fails.
|
||||
message = args.shift
|
||||
#- message = "No match made with selector #{selector.inspect}" unless message
|
||||
if args.shift
|
||||
raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
|
||||
end
|
||||
|
||||
matches = selector.select(root)
|
||||
# If text/html, narrow down to those elements that match it.
|
||||
content_mismatch = nil
|
||||
if match_with = equals[:text]
|
||||
matches.delete_if do |match|
|
||||
text = ""
|
||||
text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding)
|
||||
stack = match.children.reverse
|
||||
while node = stack.pop
|
||||
if node.tag?
|
||||
stack.concat node.children.reverse
|
||||
else
|
||||
content = node.content
|
||||
content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding)
|
||||
text << content
|
||||
end
|
||||
end
|
||||
text.strip! unless NO_STRIP.include?(match.name)
|
||||
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
|
||||
content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, text)
|
||||
true
|
||||
end
|
||||
end
|
||||
elsif match_with = equals[:html]
|
||||
matches.delete_if do |match|
|
||||
html = match.children.map(&:to_s).join
|
||||
html.strip! unless NO_STRIP.include?(match.name)
|
||||
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
|
||||
content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, html)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
# Expecting foo found bar element only if found zero, not if
|
||||
# found one but expecting two.
|
||||
message ||= content_mismatch if matches.empty?
|
||||
# Test minimum/maximum occurrence.
|
||||
min, max = equals[:minimum], equals[:maximum]
|
||||
message = message || %(Expected #{count_description(min, max)} matching "#{selector.to_s}", found #{matches.size}.)
|
||||
assert matches.size >= min, message if min
|
||||
assert matches.size <= max, message if max
|
||||
|
||||
# If a block is given call that block. Set @selected to allow
|
||||
# nested assert_select, which can be nested several levels deep.
|
||||
if block_given? && !matches.empty?
|
||||
begin
|
||||
in_scope, @selected = @selected, matches
|
||||
yield matches
|
||||
ensure
|
||||
@selected = in_scope
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all matches elements.
|
||||
matches
|
||||
end
|
||||
|
||||
def count_description(min, max) #:nodoc:
|
||||
pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
|
||||
|
||||
if min && max && (max != min)
|
||||
"between #{min} and #{max} elements"
|
||||
elsif min && !(min == 1 && max == 1)
|
||||
"at least #{min} #{pluralize['element', min]}"
|
||||
elsif max
|
||||
"at most #{max} #{pluralize['element', max]}"
|
||||
end
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# assert_select_rjs(id?) { |elements| ... }
|
||||
# assert_select_rjs(statement, id?) { |elements| ... }
|
||||
# assert_select_rjs(:insert, position, id?) { |elements| ... }
|
||||
#
|
||||
# Selects content from the RJS response.
|
||||
#
|
||||
# === Narrowing down
|
||||
#
|
||||
# With no arguments, asserts that one or more elements are updated or
|
||||
# inserted by RJS statements.
|
||||
#
|
||||
# Use the +id+ argument to narrow down the assertion to only statements
|
||||
# that update or insert an element with that identifier.
|
||||
#
|
||||
# Use the first argument to narrow down assertions to only statements
|
||||
# of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
|
||||
# <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and
|
||||
# <tt>:insert_html</tt>.
|
||||
#
|
||||
# Use the argument <tt>:insert</tt> followed by an insertion position to narrow
|
||||
# down the assertion to only statements that insert elements in that
|
||||
# position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
|
||||
# and <tt>:after</tt>.
|
||||
#
|
||||
# Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
|
||||
# be ignored as there is no HTML passed for this statement.
|
||||
#
|
||||
# === Using blocks
|
||||
#
|
||||
# Without a block, +assert_select_rjs+ merely asserts that the response
|
||||
# contains one or more RJS statements that replace or update content.
|
||||
#
|
||||
# With a block, +assert_select_rjs+ also selects all elements used in
|
||||
# these statements and passes them to the block. Nested assertions are
|
||||
# supported.
|
||||
#
|
||||
# Calling +assert_select_rjs+ with no arguments and using nested asserts
|
||||
# asserts that the HTML content is returned by one or more RJS statements.
|
||||
# Using +assert_select+ directly makes the same assertion on the content,
|
||||
# but without distinguishing whether the content is returned in an HTML
|
||||
# or JavaScript.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # Replacing the element foo.
|
||||
# # page.replace 'foo', ...
|
||||
# assert_select_rjs :replace, "foo"
|
||||
#
|
||||
# # Replacing with the chained RJS proxy.
|
||||
# # page[:foo].replace ...
|
||||
# assert_select_rjs :chained_replace, 'foo'
|
||||
#
|
||||
# # Inserting into the element bar, top position.
|
||||
# assert_select_rjs :insert, :top, "bar"
|
||||
#
|
||||
# # Remove the element bar
|
||||
# assert_select_rjs :remove, "bar"
|
||||
#
|
||||
# # Changing the element foo, with an image.
|
||||
# assert_select_rjs "foo" do
|
||||
# assert_select "img[src=/images/logo.gif""
|
||||
# end
|
||||
#
|
||||
# # RJS inserts or updates a list with four items.
|
||||
# assert_select_rjs do
|
||||
# assert_select "ol>li", 4
|
||||
# end
|
||||
#
|
||||
# # The same, but shorter.
|
||||
# assert_select "ol>li", 4
|
||||
def assert_select_rjs(*args, &block)
|
||||
rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
|
||||
id = args.first.is_a?(String) ? args.shift : nil
|
||||
|
||||
# If the first argument is a symbol, it's the type of RJS statement we're looking
|
||||
# for (update, replace, insertion, etc). Otherwise, we're looking for just about
|
||||
# any RJS statement.
|
||||
if rjs_type
|
||||
if rjs_type == :insert
|
||||
position = args.shift
|
||||
insertion = "insert_#{position}".to_sym
|
||||
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
|
||||
statement = "(#{RJS_STATEMENTS[insertion]})"
|
||||
else
|
||||
raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
|
||||
statement = "(#{RJS_STATEMENTS[rjs_type]})"
|
||||
end
|
||||
else
|
||||
statement = "#{RJS_STATEMENTS[:any]}"
|
||||
end
|
||||
|
||||
# Next argument we're looking for is the element identifier. If missing, we pick
|
||||
# any element, otherwise we replace it in the statement.
|
||||
pattern = Regexp.new(
|
||||
id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
|
||||
)
|
||||
|
||||
# Duplicate the body since the next step involves destroying it.
|
||||
matches = nil
|
||||
case rjs_type
|
||||
when :remove, :show, :hide, :toggle
|
||||
matches = @response.body.match(pattern)
|
||||
else
|
||||
@response.body.gsub(pattern) do |match|
|
||||
html = unescape_rjs(match)
|
||||
matches ||= []
|
||||
matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
if matches
|
||||
assert_block("") { true } # to count the assertion
|
||||
if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
|
||||
begin
|
||||
in_scope, @selected = @selected, matches
|
||||
yield matches
|
||||
ensure
|
||||
@selected = in_scope
|
||||
end
|
||||
end
|
||||
matches
|
||||
else
|
||||
# RJS statement not found.
|
||||
case rjs_type
|
||||
when :remove, :show, :hide, :toggle
|
||||
flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
|
||||
else
|
||||
flunk_message = "No RJS statement that replaces or inserts HTML content."
|
||||
end
|
||||
flunk args.shift || flunk_message
|
||||
end
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# assert_select_encoded(element?) { |elements| ... }
|
||||
#
|
||||
# Extracts the content of an element, treats it as encoded HTML and runs
|
||||
# nested assertion on it.
|
||||
#
|
||||
# You typically call this method within another assertion to operate on
|
||||
# all currently selected elements. You can also pass an element or array
|
||||
# of elements.
|
||||
#
|
||||
# The content of each element is un-encoded, and wrapped in the root
|
||||
# element +encoded+. It then calls the block with all un-encoded elements.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
|
||||
# assert_select_feed :atom, 1.0 do
|
||||
# # Select each entry item and then the title item
|
||||
# assert_select "entry>title" do
|
||||
# # Run assertions on the encoded title elements
|
||||
# assert_select_encoded do
|
||||
# assert_select "b"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # Selects all paragraph tags from within the description of an RSS feed
|
||||
# assert_select_feed :rss, 2.0 do
|
||||
# # Select description element of each feed item.
|
||||
# assert_select "channel>item>description" do
|
||||
# # Run assertions on the encoded elements.
|
||||
# assert_select_encoded do
|
||||
# assert_select "p"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
def assert_select_encoded(element = nil, &block)
|
||||
case element
|
||||
when Array
|
||||
elements = element
|
||||
when HTML::Node
|
||||
elements = [element]
|
||||
when nil
|
||||
unless elements = @selected
|
||||
raise ArgumentError, "First argument is optional, but must be called from a nested assert_select"
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "Argument is optional, and may be node or array of nodes"
|
||||
end
|
||||
|
||||
fix_content = lambda do |node|
|
||||
# Gets around a bug in the Rails 1.1 HTML parser.
|
||||
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { CGI.escapeHTML($1) }
|
||||
end
|
||||
|
||||
selected = elements.map do |element|
|
||||
text = element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
|
||||
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
|
||||
css_select(root, "encoded:root", &block)[0]
|
||||
end
|
||||
|
||||
begin
|
||||
old_selected, @selected = @selected, selected
|
||||
assert_select ":root", &block
|
||||
ensure
|
||||
@selected = old_selected
|
||||
end
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# assert_select_email { }
|
||||
#
|
||||
# Extracts the body of an email and runs nested assertions on it.
|
||||
#
|
||||
# You must enable deliveries for this assertion to work, use:
|
||||
# ActionMailer::Base.perform_deliveries = true
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# assert_select_email do
|
||||
# assert_select "h1", "Email alert"
|
||||
# end
|
||||
#
|
||||
# assert_select_email do
|
||||
# items = assert_select "ol>li"
|
||||
# items.each do
|
||||
# # Work with items here...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def assert_select_email(&block)
|
||||
deliveries = ActionMailer::Base.deliveries
|
||||
assert !deliveries.empty?, "No e-mail in delivery list"
|
||||
|
||||
for delivery in deliveries
|
||||
for part in delivery.parts
|
||||
if part["Content-Type"].to_s =~ /^text\/html\W/
|
||||
root = HTML::Document.new(part.body).root
|
||||
assert_select root, ":root", &block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
unless const_defined?(:RJS_STATEMENTS)
|
||||
RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
|
||||
RJS_ANY_ID = "\"([^\"])*\""
|
||||
RJS_STATEMENTS = {
|
||||
:chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
|
||||
:chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
|
||||
:replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
|
||||
:replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)"
|
||||
}
|
||||
[:remove, :show, :hide, :toggle].each do |action|
|
||||
RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
|
||||
end
|
||||
RJS_INSERTIONS = ["top", "bottom", "before", "after"]
|
||||
RJS_INSERTIONS.each do |insertion|
|
||||
RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
|
||||
end
|
||||
RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
|
||||
RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
|
||||
RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
|
||||
end
|
||||
|
||||
# +assert_select+ and +css_select+ call this to obtain the content in the HTML
|
||||
# page, or from all the RJS statements, depending on the type of response.
|
||||
def response_from_page_or_rjs()
|
||||
content_type = @response.content_type
|
||||
|
||||
if content_type && content_type =~ /text\/javascript/
|
||||
body = @response.body.dup
|
||||
root = HTML::Node.new(nil)
|
||||
|
||||
while true
|
||||
next if body.sub!(RJS_STATEMENTS[:any]) do |match|
|
||||
html = unescape_rjs(match)
|
||||
matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
|
||||
root.children.concat matches
|
||||
""
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
root
|
||||
else
|
||||
html_document.root
|
||||
end
|
||||
end
|
||||
|
||||
# Unescapes a RJS string.
|
||||
def unescape_rjs(rjs_string)
|
||||
# RJS encodes double quotes and line breaks.
|
||||
unescaped= rjs_string.gsub('\"', '"')
|
||||
unescaped.gsub!(/\\\//, '/')
|
||||
unescaped.gsub!('\n', "\n")
|
||||
unescaped.gsub!('\076', '>')
|
||||
unescaped.gsub!('\074', '<')
|
||||
# RJS encodes non-ascii characters.
|
||||
unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
|
||||
unescaped
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
130
vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb
vendored
Normal file
130
vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
require 'rexml/document'
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
# Pair of assertions to testing elements in the HTML output of the response.
|
||||
module TagAssertions
|
||||
# Asserts that there is a tag/node/element in the body of the response
|
||||
# that meets all of the given conditions. The +conditions+ parameter must
|
||||
# be a hash of any of the following keys (all are optional):
|
||||
#
|
||||
# * <tt>:tag</tt>: the node type must match the corresponding value
|
||||
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
|
||||
# corresponding values in the hash.
|
||||
# * <tt>:parent</tt>: a hash. The node's parent must match the
|
||||
# corresponding hash.
|
||||
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
|
||||
# the keys:
|
||||
# * <tt>:count</tt>: either a number or a range which must equal (or
|
||||
# include) the number of children that match.
|
||||
# * <tt>:less_than</tt>: the number of matching children must be less
|
||||
# than this number.
|
||||
# * <tt>:greater_than</tt>: the number of matching children must be
|
||||
# greater than this number.
|
||||
# * <tt>:only</tt>: another hash consisting of the keys to use
|
||||
# to match on the children, and only matching children will be
|
||||
# counted.
|
||||
# * <tt>:content</tt>: the textual content of the node must match the
|
||||
# given value. This will not match HTML tags in the body of a
|
||||
# tag--only text.
|
||||
#
|
||||
# Conditions are matched using the following algorithm:
|
||||
#
|
||||
# * if the condition is a string, it must be a substring of the value.
|
||||
# * if the condition is a regexp, it must match the value.
|
||||
# * if the condition is a number, the value must match number.to_s.
|
||||
# * if the condition is +true+, the value must not be +nil+.
|
||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# # Assert that there is a "span" tag
|
||||
# assert_tag :tag => "span"
|
||||
#
|
||||
# # Assert that there is a "span" tag with id="x"
|
||||
# assert_tag :tag => "span", :attributes => { :id => "x" }
|
||||
#
|
||||
# # Assert that there is a "span" tag using the short-hand
|
||||
# assert_tag :span
|
||||
#
|
||||
# # Assert that there is a "span" tag with id="x" using the short-hand
|
||||
# assert_tag :span, :attributes => { :id => "x" }
|
||||
#
|
||||
# # Assert that there is a "span" inside of a "div"
|
||||
# assert_tag :tag => "span", :parent => { :tag => "div" }
|
||||
#
|
||||
# # Assert that there is a "span" somewhere inside a table
|
||||
# assert_tag :tag => "span", :ancestor => { :tag => "table" }
|
||||
#
|
||||
# # Assert that there is a "span" with at least one "em" child
|
||||
# assert_tag :tag => "span", :child => { :tag => "em" }
|
||||
#
|
||||
# # Assert that there is a "span" containing a (possibly nested)
|
||||
# # "strong" tag.
|
||||
# assert_tag :tag => "span", :descendant => { :tag => "strong" }
|
||||
#
|
||||
# # Assert that there is a "span" containing between 2 and 4 "em" tags
|
||||
# # as immediate children
|
||||
# assert_tag :tag => "span",
|
||||
# :children => { :count => 2..4, :only => { :tag => "em" } }
|
||||
#
|
||||
# # Get funky: assert that there is a "div", with an "ul" ancestor
|
||||
# # and an "li" parent (with "class" = "enum"), and containing a
|
||||
# # "span" descendant that contains text matching /hello world/
|
||||
# assert_tag :tag => "div",
|
||||
# :ancestor => { :tag => "ul" },
|
||||
# :parent => { :tag => "li",
|
||||
# :attributes => { :class => "enum" } },
|
||||
# :descendant => { :tag => "span",
|
||||
# :child => /hello world/ }
|
||||
#
|
||||
# <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
|
||||
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
|
||||
# (like br and hr and such) but will not work correctly with tags
|
||||
# that allow optional closing tags (p, li, td). <em>You must explicitly
|
||||
# close all of your tags to use these assertions.</em>
|
||||
def assert_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Identical to +assert_tag+, but asserts that a matching tag does _not_
|
||||
# exist. (See +assert_tag+ for a full discussion of the syntax.)
|
||||
#
|
||||
# === Examples
|
||||
# # Assert that there is not a "div" containing a "p"
|
||||
# assert_no_tag :tag => "div", :descendant => { :tag => "p" }
|
||||
#
|
||||
# # Assert that an unordered list is empty
|
||||
# assert_no_tag :tag => "ul", :descendant => { :tag => "li" }
|
||||
#
|
||||
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
|
||||
# # as immediate children
|
||||
# assert_no_tag :tag => "p",
|
||||
# :children => { :count => 1..3, :only => { :tag => "img" } }
|
||||
def assert_no_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1340
vendor/rails/actionpack/lib/action_controller/base.rb
vendored
Normal file
1340
vendor/rails/actionpack/lib/action_controller/base.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
107
vendor/rails/actionpack/lib/action_controller/benchmarking.rb
vendored
Normal file
107
vendor/rails/actionpack/lib/action_controller/benchmarking.rb
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
require 'benchmark'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
|
||||
# package has been included, a separate timing section for database calls will be added as well.
|
||||
module Benchmarking #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
alias_method_chain :perform_action, :benchmark
|
||||
alias_method_chain :render, :benchmark
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
|
||||
# (unless <tt>use_silence</tt> is set to false).
|
||||
#
|
||||
# The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
|
||||
# easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
|
||||
# will only be conducted if the log level is low enough.
|
||||
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
||||
if logger && logger.level == log_level
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}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
|
||||
|
||||
protected
|
||||
def render_with_benchmark(options = nil, extra_options = {}, &block)
|
||||
if logger
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
end
|
||||
|
||||
render_output = nil
|
||||
@view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@view_runtime -= @db_rt_after_render
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
render_without_benchmark(options, extra_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_action_with_benchmark
|
||||
if logger
|
||||
seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
|
||||
logging_view = defined?(@view_runtime)
|
||||
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
|
||||
|
||||
if logging_view || logging_active_record
|
||||
log_message << " ("
|
||||
log_message << view_runtime if logging_view
|
||||
|
||||
if logging_active_record
|
||||
log_message << ", " if logging_view
|
||||
log_message << active_record_runtime + ")"
|
||||
else
|
||||
")"
|
||||
end
|
||||
end
|
||||
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
|
||||
else
|
||||
perform_action_without_benchmark
|
||||
end
|
||||
end
|
||||
|
||||
def view_runtime
|
||||
"View: %.0f" % (@view_runtime * 1000)
|
||||
end
|
||||
|
||||
def active_record_runtime
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
"DB: %.0f" % (db_runtime * 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
72
vendor/rails/actionpack/lib/action_controller/caching.rb
vendored
Normal file
72
vendor/rails/actionpack/lib/action_controller/caching.rb
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
require 'set'
|
||||
|
||||
require 'action_controller/caching/pages'
|
||||
require 'action_controller/caching/actions'
|
||||
require 'action_controller/caching/sql_cache'
|
||||
require 'action_controller/caching/sweeping'
|
||||
require 'action_controller/caching/fragments'
|
||||
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
# around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
|
||||
#
|
||||
# You can read more about each approach and the sweeping assistance by clicking the modules below.
|
||||
#
|
||||
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
||||
#
|
||||
#
|
||||
# == Caching stores
|
||||
#
|
||||
# All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only
|
||||
# affects action and fragment caching as page caching is always written to disk.
|
||||
#
|
||||
# Configuration examples (MemoryStore is the default):
|
||||
#
|
||||
# ActionController::Base.cache_store = :memory_store
|
||||
# ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
|
||||
# ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
|
||||
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
|
||||
module Caching
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
@@cache_store = nil
|
||||
cattr_reader :cache_store
|
||||
|
||||
# Defines the storage option for cached fragments
|
||||
def self.cache_store=(store_option)
|
||||
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
|
||||
end
|
||||
|
||||
include Pages, Actions, Fragments
|
||||
include Sweeping, SqlCache if defined?(ActiveRecord)
|
||||
|
||||
@@perform_caching = true
|
||||
cattr_accessor :perform_caching
|
||||
|
||||
def self.cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Convenience accessor
|
||||
def cache(key, options = {}, &block)
|
||||
if cache_configured?
|
||||
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def cache_configured?
|
||||
self.class.cache_configured?
|
||||
end
|
||||
end
|
||||
end
|
||||
176
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
176
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
require 'set'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :index, :show, :feed
|
||||
# end
|
||||
#
|
||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
||||
#
|
||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
|
||||
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
|
||||
#
|
||||
# And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
|
||||
#
|
||||
# Finally, if you are using memcached, you can also pass :expires_in.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
|
||||
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
|
||||
# caches_action :feed, :cache_path => Proc.new { |controller|
|
||||
# controller.params[:user_id] ?
|
||||
# controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
|
||||
# controller.send(:list_url, controller.params[:id]) }
|
||||
# end
|
||||
#
|
||||
# If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
|
||||
#
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless cache_configured?
|
||||
options = actions.extract_options!
|
||||
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
|
||||
|
||||
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
|
||||
around_filter(cache_filter, filter_options)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def expire_action(options = {})
|
||||
return unless cache_configured?
|
||||
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options, false))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(options, &block)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
|
||||
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
options = { :text => cache }
|
||||
options.merge!(:layout => true) if cache_layout?
|
||||
controller.__send__(:render, options)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if controller.rendered_action_cache || !caching_allowed(controller)
|
||||
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
|
||||
controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
|
||||
end
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
|
||||
def cache_layout?
|
||||
@options[:layout] == false
|
||||
end
|
||||
|
||||
def content_for_layout(controller)
|
||||
controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout')
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options, infer_extension=true)
|
||||
new(controller, options, infer_extension).path
|
||||
end
|
||||
end
|
||||
|
||||
# When true, infer_extension will look up the cache path extension from the request's path & format.
|
||||
# This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
|
||||
def initialize(controller, options = {}, infer_extension=true)
|
||||
if infer_extension and options.is_a? Hash
|
||||
request_extension = extract_extension(controller.request)
|
||||
options = options.reverse_merge(:format => request_extension)
|
||||
end
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
if infer_extension
|
||||
@extension = request_extension
|
||||
add_extension!(path, @extension)
|
||||
end
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension and !path.ends_with?(extension)
|
||||
end
|
||||
|
||||
def extract_extension(request)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
extension = request.path[/^[^.]+\.(.+)$/, 1]
|
||||
|
||||
# If there's no extension in the path, check request.format
|
||||
if extension.nil?
|
||||
extension = request.cache_format
|
||||
end
|
||||
extension
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
112
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
||||
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
module Fragments
|
||||
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
|
||||
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
|
||||
# ActiveSupport::Cache.expand_cache_key for the expansion.
|
||||
def fragment_cache_key(key)
|
||||
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
||||
end
|
||||
|
||||
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
||||
if perform_caching
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(key, content, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment miss: #{key}" do
|
||||
cache_store.write(key, content, options)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment hit: #{key}" do
|
||||
cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def fragment_exist?(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment exists?: #{key}" do
|
||||
cache_store.exist?(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = key.is_a?(Regexp) ? key : fragment_cache_key(key)
|
||||
|
||||
if key.is_a?(Regexp)
|
||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
||||
cache_store.delete_matched(key, options)
|
||||
end
|
||||
else
|
||||
self.class.benchmark "Expired fragment: #{key}" do
|
||||
cache_store.delete(key, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
||||
# can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically
|
||||
# through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages
|
||||
# where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are
|
||||
# a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less
|
||||
# likely candidates.
|
||||
#
|
||||
# Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# caches_page :show, :new
|
||||
# end
|
||||
#
|
||||
# This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
|
||||
# which match the URLs used to trigger the dynamic generation. This is how the web server is able
|
||||
# pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
|
||||
#
|
||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
||||
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def update
|
||||
# List.update(params[:list][:id], params[:list])
|
||||
# expire_page :action => "show", :id => params[:list][:id]
|
||||
# redirect_to :action => "show", :id => params[:list][:id]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||
# expired.
|
||||
#
|
||||
# == Setting the cache directory
|
||||
#
|
||||
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
|
||||
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
|
||||
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
|
||||
# web server to look in the new location for cached files.
|
||||
#
|
||||
# == Setting the cache extension
|
||||
#
|
||||
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
|
||||
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
|
||||
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
|
||||
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
|
||||
module Pages
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
||||
cattr_accessor :page_cache_directory
|
||||
|
||||
@@page_cache_extension = '.html'
|
||||
cattr_accessor :page_cache_extension
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Expires the page that was cached with the +path+ as a key. Example:
|
||||
# expire_page "/lists/show"
|
||||
def expire_page(path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
||||
# cache_page "I'm the cached content", "/lists/show"
|
||||
def cache_page(content, path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
||||
# matches the triggering url.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # cache the index action
|
||||
# caches_page :index
|
||||
#
|
||||
# # cache the index action except for JSON requests
|
||||
# caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
options = actions.extract_options!
|
||||
after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
||||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
return unless perform_caching
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = nil)
|
||||
return unless perform_caching && caching_allowed
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
||||
#
|
||||
# class ListSweeper < ActionController::Caching::Sweeper
|
||||
# observe List, Item
|
||||
#
|
||||
# def after_save(record)
|
||||
# list = record.is_a?(List) ? record : record.list
|
||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
||||
# expire_action(:controller => "lists", :action => "all")
|
||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
#
|
||||
# You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
module Sweeping
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
configuration = sweepers.extract_options!
|
||||
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
around_filter(sweeper_instance, :only => configuration[:only])
|
||||
else
|
||||
after_filter(sweeper_instance, :only => configuration[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before) if controller.perform_caching
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after) if controller.perform_caching
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.__send__(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
vendor/rails/actionpack/lib/action_controller/cgi_ext.rb
vendored
Normal file
16
vendor/rails/actionpack/lib/action_controller/cgi_ext.rb
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
require 'action_controller/cgi_ext/stdinput'
|
||||
require 'action_controller/cgi_ext/query_extension'
|
||||
require 'action_controller/cgi_ext/cookie'
|
||||
require 'action_controller/cgi_ext/session'
|
||||
|
||||
class CGI #:nodoc:
|
||||
include ActionController::CgiExt::Stdinput
|
||||
|
||||
class << self
|
||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||
|
||||
def escapeHTML(string)
|
||||
escapeHTML_fail_on_nil(string) unless string.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
110
vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb
vendored
Normal file
110
vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
CGI.module_eval { remove_const "Cookie" }
|
||||
|
||||
# TODO: document how this differs from stdlib CGI::Cookie
|
||||
class CGI #:nodoc:
|
||||
class Cookie < DelegateClass(Array)
|
||||
attr_accessor :name, :value, :path, :domain, :expires
|
||||
attr_reader :secure, :http_only
|
||||
|
||||
# Creates a new CGI::Cookie object.
|
||||
#
|
||||
# The contents of the cookie can be specified as a +name+ and one
|
||||
# or more +value+ arguments. Alternatively, the contents can
|
||||
# be specified as a single hash argument. The possible keywords of
|
||||
# this hash are as follows:
|
||||
#
|
||||
# * <tt>:name</tt> - The name of the cookie. Required.
|
||||
# * <tt>:value</tt> - The cookie's value or list of values.
|
||||
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the
|
||||
# base directory of the CGI script.
|
||||
# * <tt>:domain</tt> - The domain for which this cookie applies.
|
||||
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
|
||||
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
|
||||
# +false+). Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
|
||||
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
|
||||
#
|
||||
# These keywords correspond to attributes of the cookie object.
|
||||
def initialize(name = '', *value)
|
||||
if name.kind_of?(String)
|
||||
@name = name
|
||||
@value = Array(value)
|
||||
@domain = nil
|
||||
@expires = nil
|
||||
@secure = false
|
||||
@http_only = false
|
||||
@path = nil
|
||||
else
|
||||
@name = name['name']
|
||||
@value = (name['value'].kind_of?(String) ? [name['value']] : Array(name['value'])).delete_if(&:blank?)
|
||||
@domain = name['domain']
|
||||
@expires = name['expires']
|
||||
@secure = name['secure'] || false
|
||||
@http_only = name['http_only'] || false
|
||||
@path = name['path']
|
||||
end
|
||||
|
||||
raise ArgumentError, "`name' required" unless @name
|
||||
|
||||
# simple support for IE
|
||||
unless @path
|
||||
%r|^(.*/)|.match(ENV['SCRIPT_NAME'])
|
||||
@path = ($1 or '')
|
||||
end
|
||||
|
||||
super(@value)
|
||||
end
|
||||
|
||||
# Sets whether the Cookie is a secure cookie or not.
|
||||
def secure=(val)
|
||||
@secure = val == true
|
||||
end
|
||||
|
||||
# Sets whether the Cookie is an HTTP only cookie or not.
|
||||
def http_only=(val)
|
||||
@http_only = val == true
|
||||
end
|
||||
|
||||
# Converts the Cookie to its string representation.
|
||||
def to_s
|
||||
buf = ''
|
||||
buf << @name << '='
|
||||
buf << (@value.kind_of?(String) ? CGI::escape(@value) : @value.collect{|v| CGI::escape(v) }.join("&"))
|
||||
buf << '; domain=' << @domain if @domain
|
||||
buf << '; path=' << @path if @path
|
||||
buf << '; expires=' << CGI::rfc1123_date(@expires) if @expires
|
||||
buf << '; secure' if @secure
|
||||
buf << '; HttpOnly' if @http_only
|
||||
buf
|
||||
end
|
||||
|
||||
# FIXME: work around broken 1.8.7 DelegateClass#respond_to?
|
||||
def respond_to?(method, include_private = false)
|
||||
return true if super(method)
|
||||
return __getobj__.respond_to?(method, include_private)
|
||||
end
|
||||
|
||||
# Parses a raw cookie string into a hash of <tt>cookie-name => cookie-object</tt>
|
||||
# pairs.
|
||||
#
|
||||
# cookies = CGI::Cookie::parse("raw_cookie_string")
|
||||
# # => { "name1" => cookie1, "name2" => cookie2, ... }
|
||||
#
|
||||
def self.parse(raw_cookie)
|
||||
cookies = Hash.new([])
|
||||
|
||||
if raw_cookie
|
||||
raw_cookie.split(/;\s?/).each do |pairs|
|
||||
name, value = pairs.split('=',2)
|
||||
next unless name and value
|
||||
name = CGI::unescape(name)
|
||||
unless cookies.has_key?(name)
|
||||
cookies[name] = new(name, CGI::unescape(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cookies
|
||||
end
|
||||
end # class Cookie
|
||||
end
|
||||
22
vendor/rails/actionpack/lib/action_controller/cgi_ext/query_extension.rb
vendored
Normal file
22
vendor/rails/actionpack/lib/action_controller/cgi_ext/query_extension.rb
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'cgi'
|
||||
|
||||
class CGI #:nodoc:
|
||||
module QueryExtension
|
||||
# Remove the old initialize_query method before redefining it.
|
||||
remove_method :initialize_query
|
||||
|
||||
# Neuter CGI parameter parsing.
|
||||
def initialize_query
|
||||
# Fix some strange request environments.
|
||||
env_table['REQUEST_METHOD'] ||= 'GET'
|
||||
|
||||
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
|
||||
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
|
||||
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
@params = {}
|
||||
end
|
||||
end
|
||||
end
|
||||
53
vendor/rails/actionpack/lib/action_controller/cgi_ext/session.rb
vendored
Normal file
53
vendor/rails/actionpack/lib/action_controller/cgi_ext/session.rb
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
require 'digest/md5'
|
||||
require 'cgi/session'
|
||||
require 'cgi/session/pstore'
|
||||
|
||||
class CGI #:nodoc:
|
||||
# * Expose the CGI instance to session stores.
|
||||
# * Don't require 'digest/md5' whenever a new session id is generated.
|
||||
class Session #:nodoc:
|
||||
def self.generate_unique_id(constant = nil)
|
||||
ActiveSupport::SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
# Make the CGI instance available to session stores.
|
||||
attr_reader :cgi
|
||||
attr_reader :dbman
|
||||
alias_method :initialize_without_cgi_reader, :initialize
|
||||
def initialize(cgi, options = {})
|
||||
@cgi = cgi
|
||||
initialize_without_cgi_reader(cgi, options)
|
||||
end
|
||||
|
||||
private
|
||||
# Create a new session id.
|
||||
def create_new_id
|
||||
@new_session = true
|
||||
self.class.generate_unique_id
|
||||
end
|
||||
|
||||
# * Don't require 'digest/md5' whenever a new session is started.
|
||||
class PStore #:nodoc:
|
||||
def initialize(session, option={})
|
||||
dir = option['tmpdir'] || Dir::tmpdir
|
||||
prefix = option['prefix'] || ''
|
||||
id = session.session_id
|
||||
md5 = Digest::MD5.hexdigest(id)[0,16]
|
||||
path = dir+"/"+prefix+md5
|
||||
path.untaint
|
||||
if File::exist?(path)
|
||||
@hash = nil
|
||||
else
|
||||
unless session.new_session
|
||||
raise CGI::Session::NoSession, "uninitialized session"
|
||||
end
|
||||
@hash = {}
|
||||
end
|
||||
@p = ::PStore.new(path)
|
||||
@p.transaction do |p|
|
||||
File.chmod(0600, p.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb
vendored
Normal file
24
vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
require 'cgi'
|
||||
|
||||
module ActionController
|
||||
module CgiExt
|
||||
# Publicize the CGI's internal input stream so we can lazy-read
|
||||
# request.body. Make it writable so we don't have to play $stdin games.
|
||||
module Stdinput
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
remove_method :stdinput
|
||||
attr_accessor :stdinput
|
||||
end
|
||||
|
||||
base.alias_method_chain :initialize, :stdinput
|
||||
end
|
||||
|
||||
def initialize_with_stdinput(type = nil, stdinput = $stdin)
|
||||
@stdinput = stdinput
|
||||
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
|
||||
initialize_without_stdinput(type || 'query')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
185
vendor/rails/actionpack/lib/action_controller/cgi_process.rb
vendored
Normal file
185
vendor/rails/actionpack/lib/action_controller/cgi_process.rb
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
require 'action_controller/cgi_ext'
|
||||
require 'action_controller/session/cookie_store'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
# Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# lib/action_controller/session.
|
||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
|
||||
# automatically generated for a new session.
|
||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a Time object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
|
||||
# the query string or POST parameters. This protects against session fixation attacks.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
new.process_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only=> true
|
||||
}
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@session_options = session_options
|
||||
@env = @cgi.__send__(:env_table)
|
||||
super()
|
||||
end
|
||||
|
||||
def query_string
|
||||
qs = @cgi.query_string if @cgi.respond_to?(:query_string)
|
||||
if !qs.blank?
|
||||
qs
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@cgi.stdinput
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
end
|
||||
end
|
||||
@session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
def method_missing(method_id, *arguments)
|
||||
@cgi.__send__(method_id, *arguments) rescue super
|
||||
end
|
||||
|
||||
private
|
||||
# Delete an old session if it exists then create a new one.
|
||||
def new_session
|
||||
if @session_options == false
|
||||
Hash.new
|
||||
else
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
||||
end
|
||||
end
|
||||
|
||||
def cookie_only?
|
||||
session_options_with_string_keys['cookie_only']
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
end_msg
|
||||
end
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
class CgiResponse < AbstractResponse #:nodoc:
|
||||
def initialize(cgi)
|
||||
@cgi = cgi
|
||||
super()
|
||||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
output.binmode if output.respond_to?(:binmode)
|
||||
output.sync = false if output.respond_to?(:sync=)
|
||||
|
||||
begin
|
||||
output.write(@cgi.header(@headers))
|
||||
|
||||
if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
# #syswrite.
|
||||
output.flush if output.respond_to?(:flush)
|
||||
@body.call(self, output)
|
||||
else
|
||||
output.write(@body)
|
||||
end
|
||||
|
||||
output.flush if output.respond_to?(:flush)
|
||||
rescue Errno::EPIPE, Errno::ECONNRESET
|
||||
# lost connection to parent process, ignore output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
169
vendor/rails/actionpack/lib/action_controller/components.rb
vendored
Normal file
169
vendor/rails/actionpack/lib/action_controller/components.rb
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
module ActionController #:nodoc:
|
||||
# Components allow you to call other actions for their rendered response while executing another action. You can either delegate
|
||||
# the entire response rendering or you can mix a partial response in with your other content.
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# # Performs a method and then lets hello_world output its render
|
||||
# def delegate_action
|
||||
# do_other_stuff_before_hello_world
|
||||
# render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class GreeterController < ActionController::Base
|
||||
# def hello_world
|
||||
# render :text => "#{params[:person]} says, Hello World!"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The same can be done in a view to do a partial rendering:
|
||||
#
|
||||
# Let's see a greeting:
|
||||
# <%= render_component :controller => "greeter", :action => "hello_world" %>
|
||||
#
|
||||
# It is also possible to specify the controller as a class constant, bypassing the inflector
|
||||
# code to compute the controller class at runtime:
|
||||
#
|
||||
# <%= render_component :controller => GreeterController, :action => "hello_world" %>
|
||||
#
|
||||
# == When to use components
|
||||
#
|
||||
# Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
|
||||
# conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
|
||||
# reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
|
||||
# across many applications at once.
|
||||
#
|
||||
# So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
|
||||
module Components
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
include ActiveSupport::Deprecation
|
||||
extend ClassMethods
|
||||
helper HelperMethods
|
||||
|
||||
# If this controller was instantiated to process a component request,
|
||||
# +parent_controller+ points to the instantiator of this controller.
|
||||
attr_accessor :parent_controller
|
||||
|
||||
alias_method_chain :process_cleanup, :components
|
||||
alias_method_chain :set_session_options, :components
|
||||
alias_method_chain :flash, :components
|
||||
|
||||
alias_method :component_request?, :parent_controller
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Track parent controller to identify component requests
|
||||
def process_with_components(request, response, parent_controller = nil) #:nodoc:
|
||||
controller = new
|
||||
controller.parent_controller = parent_controller
|
||||
controller.process(request, response)
|
||||
end
|
||||
end
|
||||
|
||||
module HelperMethods
|
||||
def render_component(options)
|
||||
@controller.__send__(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# Extracts the action_name from the request parameters and performs that action.
|
||||
def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
flash.discard if component_request?
|
||||
process_without_components(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
protected
|
||||
# Renders the component specified as the response for the current method
|
||||
def render_component(options) #:doc:
|
||||
component_logging(options) do
|
||||
render_for_text(component_response(options, true).body, response.headers["Status"])
|
||||
end
|
||||
end
|
||||
deprecate :render_component => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
||||
|
||||
# Returns the component response as a string
|
||||
def render_component_as_string(options) #:doc:
|
||||
component_logging(options) do
|
||||
response = component_response(options, false)
|
||||
|
||||
if redirected = response.redirected_to
|
||||
render_component_as_string(redirected)
|
||||
else
|
||||
response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
deprecate :render_component_as_string => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
||||
|
||||
def flash_with_components(refresh = false) #:nodoc:
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if defined?(@parent_controller)
|
||||
@parent_controller.flash
|
||||
else
|
||||
flash_without_components
|
||||
end
|
||||
end
|
||||
@_flash
|
||||
end
|
||||
|
||||
private
|
||||
def component_response(options, reuse_response)
|
||||
klass = component_class(options)
|
||||
request = request_for_component(klass.controller_name, options)
|
||||
new_response = reuse_response ? response : response.dup
|
||||
|
||||
klass.process_with_components(request, new_response, self)
|
||||
end
|
||||
|
||||
# determine the controller class for the component request
|
||||
def component_class(options)
|
||||
if controller = options[:controller]
|
||||
controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
|
||||
else
|
||||
self.class
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new request object based on the current request.
|
||||
# The new request inherits the session from the current request,
|
||||
# bypassing any session options set for the component controller's class
|
||||
def request_for_component(controller_name, options)
|
||||
new_request = request.dup
|
||||
new_request.session = request.session
|
||||
|
||||
new_request.instance_variable_set(
|
||||
:@parameters,
|
||||
(options[:params] || {}).with_indifferent_access.update(
|
||||
"controller" => controller_name, "action" => options[:action], "id" => options[:id]
|
||||
)
|
||||
)
|
||||
|
||||
new_request
|
||||
end
|
||||
|
||||
def component_logging(options)
|
||||
if logger
|
||||
logger.info "Start rendering component (#{options.inspect}): "
|
||||
result = yield
|
||||
logger.info "\n\nEnd of component rendering"
|
||||
result
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def set_session_options_with_components(request)
|
||||
set_session_options_without_components(request) unless component_request?
|
||||
end
|
||||
|
||||
def process_cleanup_with_components
|
||||
process_cleanup_without_components unless component_request?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
106
vendor/rails/actionpack/lib/action_controller/cookies.rb
vendored
Normal file
106
vendor/rails/actionpack/lib/action_controller/cookies.rb
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
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:
|
||||
#
|
||||
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
|
||||
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
|
||||
# of the application.
|
||||
# * <tt>:domain</tt> - The domain for which this cookie applies.
|
||||
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
|
||||
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
|
||||
# Default is +false+.
|
||||
# * <tt>:http_only</tt> - 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
|
||||
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)
|
||||
cookie = @cookies[name.to_s]
|
||||
if cookie && cookie.respond_to?(:value)
|
||||
cookie.size > 1 ? cookie.value : cookie.value[0]
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the cookie named +name+. The second argument may be the very cookie
|
||||
# value, or a hash of options as documented above.
|
||||
def []=(name, options)
|
||||
if options.is_a?(Hash)
|
||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
||||
options["name"] = name.to_s
|
||||
else
|
||||
options = { "name" => name.to_s, "value" => options }
|
||||
end
|
||||
|
||||
set_cookie(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 <tt>[]=</tt>, you can pass in
|
||||
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
||||
def delete(name, options = {})
|
||||
options.stringify_keys!
|
||||
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
||||
end
|
||||
|
||||
private
|
||||
# Builds a CGI::Cookie object and adds the cookie to the response headers.
|
||||
#
|
||||
# The path of the cookie defaults to "/" if there's none in +options+, and
|
||||
# everything is passed to the CGI::Cookie constructor.
|
||||
def set_cookie(options) #:doc:
|
||||
options["path"] = "/" unless options["path"]
|
||||
cookie = CGI::Cookie.new(options)
|
||||
@controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
|
||||
@controller.response.headers["cookie"] << cookie
|
||||
end
|
||||
end
|
||||
end
|
||||
196
vendor/rails/actionpack/lib/action_controller/dispatcher.rb
vendored
Normal file
196
vendor/rails/actionpack/lib/action_controller/dispatcher.rb
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
module ActionController
|
||||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
@@guard = Mutex.new
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
unless cache_classes
|
||||
# Development mode callbacks
|
||||
before_dispatch :reload_application
|
||||
after_dispatch :cleanup_application
|
||||
end
|
||||
|
||||
# Common callbacks
|
||||
to_prepare :load_application_controller do
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
after_dispatch :checkin_connections
|
||||
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
||||
end
|
||||
|
||||
after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush)
|
||||
|
||||
to_prepare do
|
||||
I18n.reload!
|
||||
end
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
#
|
||||
# An optional identifier may be supplied for the callback. If provided,
|
||||
# to_prepare may be called again with the same identifier to replace the
|
||||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
# code adding a preparation block may be reloaded.
|
||||
def to_prepare(identifier = nil, &block)
|
||||
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
def failsafe_response(fallback_output, status, originating_exception = nil)
|
||||
yield
|
||||
rescue Exception => exception
|
||||
begin
|
||||
log_failsafe_exception(status, originating_exception || exception)
|
||||
body = failsafe_response_body(status)
|
||||
fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
|
||||
nil
|
||||
rescue Exception => failsafe_error # Logger or IO errors
|
||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||
$stderr.puts "(originally #{originating_exception})" if originating_exception
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def failsafe_response_body(status)
|
||||
error_path = "#{error_file_path}/#{status.to_s[0..3]}.html"
|
||||
|
||||
if File.exist?(error_path)
|
||||
File.read(error_path)
|
||||
else
|
||||
"<html><body><h1>#{status}</h1></body></html>"
|
||||
end
|
||||
end
|
||||
|
||||
def log_failsafe_exception(status, exception)
|
||||
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
|
||||
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
||||
failsafe_logger.fatal message
|
||||
end
|
||||
|
||||
def failsafe_logger
|
||||
if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
|
||||
::RAILS_DEFAULT_LOGGER
|
||||
else
|
||||
Logger.new($stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
end
|
||||
|
||||
def dispatch_unlocked
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch
|
||||
if ActionController::Base.allow_concurrency
|
||||
dispatch_unlocked
|
||||
else
|
||||
@@guard.synchronize do
|
||||
dispatch_unlocked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
|
||||
@request = CgiRequest.new(cgi, session_options)
|
||||
@response = CgiResponse.new(cgi)
|
||||
dispatch
|
||||
end
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@request = RackRequest.new(env)
|
||||
@response = RackResponse.new(@request)
|
||||
dispatch
|
||||
end
|
||||
|
||||
def reload_application
|
||||
# Run prepare callbacks before every request in development mode
|
||||
run_callbacks :prepare_dispatch
|
||||
|
||||
Routing::Routes.reload
|
||||
ActionController::Base.view_paths.reload!
|
||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
# be reloaded on the next request without restarting the server.
|
||||
def cleanup_application
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
ActiveSupport::Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
|
||||
def flush_logger
|
||||
Base.logger.flush
|
||||
end
|
||||
|
||||
def mark_as_test_request!
|
||||
@test_request = true
|
||||
self
|
||||
end
|
||||
|
||||
def test_request?
|
||||
@test_request
|
||||
end
|
||||
|
||||
def checkin_connections
|
||||
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
|
||||
return if test_request?
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
protected
|
||||
def handle_request
|
||||
@controller = Routing::Routes.recognize(@request)
|
||||
@controller.process(@request, @response).out(@output)
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
||||
@controller.process_with_exception(@request, @response, exception).out(@output)
|
||||
else
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
680
vendor/rails/actionpack/lib/action_controller/filters.rb
vendored
Normal file
680
vendor/rails/actionpack/lib/action_controller/filters.rb
vendored
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
module ActionController #:nodoc:
|
||||
module Filters #:nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
include ActionController::Filters::InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
|
||||
def append_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block)
|
||||
filters, conditions = extract_options(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
|
||||
filters
|
||||
end
|
||||
|
||||
def skip_filter_in_chain(*filters, &test)
|
||||
filters, conditions = extract_options(filters)
|
||||
filters.each do |filter|
|
||||
if callback = find(filter) then delete(callback) end
|
||||
end if conditions.empty?
|
||||
update_filter_in_chain(filters, :skip => conditions, &test)
|
||||
end
|
||||
|
||||
private
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
insert(pos, new_filters).flatten!
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter, filter_type, options = {})
|
||||
update_filter_in_chain([filter], options)
|
||||
|
||||
if found_filter = find(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
filter_kind = case
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
:before
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
:after
|
||||
else
|
||||
:filter
|
||||
end
|
||||
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilter.new(filter_kind, filter, options)
|
||||
when :after
|
||||
AfterFilter.new(filter_kind, filter, options)
|
||||
else
|
||||
AroundFilter.new(filter_kind, filter, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_filter_in_chain(filters, options, &test)
|
||||
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
|
||||
filters.compact!
|
||||
|
||||
map! do |filter|
|
||||
if filters.include?(filter)
|
||||
new_filter = filter.dup
|
||||
new_filter.update_options!(options)
|
||||
new_filter
|
||||
else
|
||||
filter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
|
||||
def initialize(kind, method, options = {})
|
||||
super
|
||||
update_options! options
|
||||
end
|
||||
|
||||
# override these to return true in appropriate subclass
|
||||
def before?
|
||||
false
|
||||
end
|
||||
|
||||
def after?
|
||||
false
|
||||
end
|
||||
|
||||
def around?
|
||||
false
|
||||
end
|
||||
|
||||
# Make sets of strings from :only/:except options
|
||||
def update_options!(other)
|
||||
if other
|
||||
convert_only_and_except_options_to_sets_of_strings(other)
|
||||
if other[:skip]
|
||||
convert_only_and_except_options_to_sets_of_strings(other[:skip])
|
||||
end
|
||||
end
|
||||
|
||||
options.update(other)
|
||||
end
|
||||
|
||||
private
|
||||
def should_not_skip?(controller)
|
||||
if options[:skip]
|
||||
!included_in_action?(controller, options[:skip])
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def included_in_action?(controller, options)
|
||||
if options[:only]
|
||||
options[:only].include?(controller.action_name)
|
||||
elsif options[:except]
|
||||
!options[:except].include?(controller.action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def should_run_callback?(controller)
|
||||
should_not_skip?(controller) && included_in_action?(controller, options) && super
|
||||
end
|
||||
|
||||
def convert_only_and_except_options_to_sets_of_strings(opts)
|
||||
[:only, :except].each do |key|
|
||||
if values = opts[key]
|
||||
opts[key] = Array(values).map(&:to_s).to_set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AroundFilter < Filter #:nodoc:
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if should_run_callback?(controller)
|
||||
method = filter_responds_to_before_and_after? ? around_proc : self.method
|
||||
|
||||
# For around_filter do |controller, action|
|
||||
if method.is_a?(Proc) && method.arity == 2
|
||||
evaluate_method(method, controller, block)
|
||||
else
|
||||
evaluate_method(method, controller, &block)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def filter_responds_to_before_and_after?
|
||||
method.respond_to?(:before) && method.respond_to?(:after)
|
||||
end
|
||||
|
||||
def around_proc
|
||||
Proc.new do |controller, action|
|
||||
method.before(controller)
|
||||
|
||||
if controller.__send__(:performed?)
|
||||
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
method.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilter < Filter #:nodoc:
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def before?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
super
|
||||
if controller.__send__(:performed?)
|
||||
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilter < Filter #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def after?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
|
||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||
# compression after the action has been performed. Filters have access to the request, response, and all the instance
|
||||
# variables set by other filters in the chain or by the action (in the case of after filters).
|
||||
#
|
||||
# == Filter inheritance
|
||||
#
|
||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
|
||||
# affecting the superclass. For example:
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
# before_filter :audit
|
||||
#
|
||||
# private
|
||||
# def audit
|
||||
# # record the action and parameters in an audit log
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class VaultController < BankController
|
||||
# before_filter :verify_credentials
|
||||
#
|
||||
# private
|
||||
# def verify_credentials
|
||||
# # make sure the user is allowed into the vault
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Now any actions performed on the BankController will have the audit method called before. On the VaultController,
|
||||
# first the audit method is called, then the verify_credentials method. If the audit method renders or redirects, then
|
||||
# verify_credentials and the intended action are never called.
|
||||
#
|
||||
# == Filter types
|
||||
#
|
||||
# A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
|
||||
# is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
|
||||
# the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
|
||||
#
|
||||
# Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
|
||||
# are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
|
||||
#
|
||||
# class OutputCompressionFilter
|
||||
# def self.filter(controller)
|
||||
# controller.response.body = compress(controller.response.body)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class NewspaperController < ActionController::Base
|
||||
# after_filter OutputCompressionFilter
|
||||
# end
|
||||
#
|
||||
# The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
|
||||
# manipulate them as it sees fit.
|
||||
#
|
||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||
# Or just as a quick test. It works like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# before_filter { |controller| head(400) if controller.params["stop_action"] }
|
||||
# end
|
||||
#
|
||||
# As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
|
||||
# This means that the block has access to both the request and response objects complete with convenience methods for params,
|
||||
# session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
|
||||
# and returns 1 or -1 on arity will do (such as a Proc or an Method object).
|
||||
#
|
||||
# Please note that around_filters function a little differently than the normal before and after filters with regard to filter
|
||||
# types. Please see the section dedicated to around_filters below.
|
||||
#
|
||||
# == Filter chain ordering
|
||||
#
|
||||
# Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
|
||||
# just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
|
||||
# can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
|
||||
# beginning of their respective chain and executed before the rest. For example:
|
||||
#
|
||||
# class ShoppingController < ActionController::Base
|
||||
# before_filter :verify_open_shop
|
||||
#
|
||||
# class CheckoutController < ShoppingController
|
||||
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
||||
#
|
||||
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters renders or redirects, we'll never get around to see if the shop
|
||||
# is open or not.
|
||||
#
|
||||
# You may pass multiple filter arguments of each type as well as a filter block.
|
||||
# If a block is given, it is treated as the last argument.
|
||||
#
|
||||
# == Around filters
|
||||
#
|
||||
# Around filters wrap an action, executing code both before and after.
|
||||
# They may be declared as method references, blocks, or objects responding
|
||||
# to +filter+ or to both +before+ and +after+.
|
||||
#
|
||||
# To use a method as an +around_filter+, pass a symbol naming the Ruby method.
|
||||
# Yield (or <tt>block.call</tt>) within the method to run the action.
|
||||
#
|
||||
# around_filter :catch_exceptions
|
||||
#
|
||||
# private
|
||||
# def catch_exceptions
|
||||
# yield
|
||||
# rescue => exception
|
||||
# logger.debug "Caught exception! #{exception}"
|
||||
# raise
|
||||
# end
|
||||
#
|
||||
# To use a block as an +around_filter+, pass a block taking as args both
|
||||
# the controller and the action block. You can't call yield directly from
|
||||
# an +around_filter+ block; explicitly call the action block instead:
|
||||
#
|
||||
# around_filter do |controller, action|
|
||||
# logger.debug "before #{controller.action_name}"
|
||||
# action.call
|
||||
# logger.debug "after #{controller.action_name}"
|
||||
# end
|
||||
#
|
||||
# To use a filter object with +around_filter+, pass an object responding
|
||||
# to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
|
||||
# filter method, yield to the block as above:
|
||||
#
|
||||
# around_filter BenchmarkingFilter
|
||||
#
|
||||
# class BenchmarkingFilter
|
||||
# def self.filter(controller, &block)
|
||||
# Benchmark.measure(&block)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# With +before+ and +after+ methods:
|
||||
#
|
||||
# around_filter Authorizer.new
|
||||
#
|
||||
# class Authorizer
|
||||
# # This will run before the action. Redirecting aborts the action.
|
||||
# def before(controller)
|
||||
# unless user.authorized?
|
||||
# redirect_to(login_url)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # This will run after the action if and only if before did not render or redirect.
|
||||
# def after(controller)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the filter has +before+ and +after+ methods, the +before+ method will be
|
||||
# called before the action. If +before+ renders or redirects, the filter chain is
|
||||
# halted and +after+ will not be run. See Filter Chain Halting below for
|
||||
# an example.
|
||||
#
|
||||
# == Filter chain skipping
|
||||
#
|
||||
# Declaring a filter on a base class conveniently applies to its subclasses,
|
||||
# but sometimes a subclass should skip some of its superclass' filters:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :authenticate
|
||||
# around_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class WeblogController < ApplicationController
|
||||
# # Will run the :authenticate and :catch_exceptions filters.
|
||||
# end
|
||||
#
|
||||
# class SignupController < ApplicationController
|
||||
# # Skip :authenticate, run :catch_exceptions.
|
||||
# skip_before_filter :authenticate
|
||||
# end
|
||||
#
|
||||
# class ProjectsController < ApplicationController
|
||||
# # Skip :catch_exceptions, run :authenticate.
|
||||
# skip_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class ClientsController < ApplicationController
|
||||
# # Skip :catch_exceptions and :authenticate unless action is index.
|
||||
# skip_filter :catch_exceptions, :authenticate, :except => :index
|
||||
# end
|
||||
#
|
||||
# == Filter conditions
|
||||
#
|
||||
# Filters may be limited to specific actions by declaring the actions to
|
||||
# include or exclude. Both options accept single actions
|
||||
# (<tt>:only => :index</tt>) or arrays of actions
|
||||
# (<tt>:except => [:foo, :bar]</tt>).
|
||||
#
|
||||
# class Journal < ActionController::Base
|
||||
# # Require authentication for edit and delete.
|
||||
# before_filter :authorize, :only => [:edit, :delete]
|
||||
#
|
||||
# # Passing options to a filter with a block.
|
||||
# around_filter(:except => :index) do |controller, action_block|
|
||||
# results = Profiler.run(&action_block)
|
||||
# controller.response.sub! "</body>", "#{results}</body>"
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def authorize
|
||||
# # Redirect to login unless authenticated.
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Filter Chain Halting
|
||||
#
|
||||
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||
# before a controller action is run. This is useful, for example, to deny
|
||||
# access to unauthenticated users or to redirect from HTTP to HTTPS.
|
||||
# Simply call render or redirect. After filters will not be executed if the filter
|
||||
# chain is halted.
|
||||
#
|
||||
# Around filters halt the request unless the action block is called.
|
||||
# Given these filters
|
||||
# after_filter :after
|
||||
# around_filter :around
|
||||
# before_filter :before
|
||||
#
|
||||
# The filter chain will look like:
|
||||
#
|
||||
# ...
|
||||
# . \
|
||||
# . #around (code before yield)
|
||||
# . . \
|
||||
# . . #before (actual filter code is run)
|
||||
# . . . \
|
||||
# . . . execute controller action
|
||||
# . . . /
|
||||
# . . ...
|
||||
# . . /
|
||||
# . #around (code after yield)
|
||||
# . /
|
||||
# #after (actual filter code is run, unless the around filter does not yield)
|
||||
#
|
||||
# If +around+ returns before yielding, +after+ will still not be run. The +before+
|
||||
# filter and controller action will not be run. If +before+ renders or redirects,
|
||||
# the second half of +around+ and will still run but +after+ and the
|
||||
# action will not. If +around+ fails to yield, +after+ will not be run.
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def append_before_filter(*filters, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def prepend_before_filter(*filters, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_before_filter since it's the most common.
|
||||
alias :before_filter :append_before_filter
|
||||
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def append_after_filter(*filters, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def prepend_after_filter(*filters, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
alias :after_filter :append_after_filter
|
||||
|
||||
# If you <tt>append_around_filter A.new, B.new</tt>, the filter chain looks like
|
||||
#
|
||||
# B#before
|
||||
# A#before
|
||||
# # run the action
|
||||
# A#after
|
||||
# B#after
|
||||
#
|
||||
# With around filters which yield to the action block, +before+ and +after+
|
||||
# are the code before and after the yield.
|
||||
def append_around_filter(*filters, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# If you <tt>prepend_around_filter A.new, B.new</tt>, the filter chain looks like:
|
||||
#
|
||||
# A#before
|
||||
# B#before
|
||||
# # run the action
|
||||
# B#after
|
||||
# A#after
|
||||
#
|
||||
# With around filters which yield to the action block, +before+ and +after+
|
||||
# are the code before and after the yield.
|
||||
def prepend_around_filter(*filters, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# Shorthand for +append_around_filter+ since it's the most common.
|
||||
alias :around_filter :append_around_filter
|
||||
|
||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_before_filter(*filters)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:before?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_after_filter(*filters)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:after?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||
# filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
|
||||
# it will match any before, after or yielding around filter.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_filter(*filters)
|
||||
filter_chain.skip_filter_in_chain(*filters)
|
||||
end
|
||||
|
||||
# Returns an array of Filter objects for this controller.
|
||||
def filter_chain
|
||||
if chain = read_inheritable_attribute('filter_chain')
|
||||
return chain
|
||||
else
|
||||
write_inheritable_attribute('filter_chain', FilterChain.new)
|
||||
return filter_chain
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all the before filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def before_filters #:nodoc:
|
||||
filter_chain.select(&:before?).map(&:method)
|
||||
end
|
||||
|
||||
# Returns all the after filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def after_filters #:nodoc:
|
||||
filter_chain.select(&:after?).map(&:method)
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
alias_method_chain :perform_action, :filters
|
||||
alias_method_chain :process, :filters
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
|
||||
private
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter
|
||||
when BeforeFilter
|
||||
filter.call(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when AroundFilter
|
||||
yielded = false
|
||||
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter
|
||||
when AfterFilter
|
||||
seen_after_filter = true
|
||||
filter.call(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
end
|
||||
|
||||
index = index.next
|
||||
end
|
||||
|
||||
index.next
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
172
vendor/rails/actionpack/lib/action_controller/flash.rb
vendored
Normal file
172
vendor/rails/actionpack/lib/action_controller/flash.rb
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
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 <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
||||
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def create
|
||||
# # save post
|
||||
# flash[:notice] = "Successfully created post"
|
||||
# redirect_to :action => "display", :params => { :id => post.id }
|
||||
# end
|
||||
#
|
||||
# def display
|
||||
# # doesn't need to assign the flash notice to the template, that's done automatically
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# display.erb
|
||||
# <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
|
||||
#
|
||||
# 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
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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 (<tt>[]=</tt>).
|
||||
# When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
|
||||
# vanish when the current action is done.
|
||||
#
|
||||
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
|
||||
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
|
||||
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> 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)
|
||||
def use(k=nil, v=true)
|
||||
unless k.nil?
|
||||
@used[k] = v
|
||||
else
|
||||
keys.each{ |key| use(key, v) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods #:nodoc:
|
||||
protected
|
||||
def reset_session_with_flash
|
||||
reset_session_without_flash
|
||||
remove_instance_variable(:@_flash)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
|
||||
# <tt>flash["notice"] = "hello"</tt> to put a new one.
|
||||
# Note that if sessions are disabled only flash.now will work.
|
||||
def flash(refresh = false) #:doc:
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if session.is_a?(Hash)
|
||||
# don't put flash in session if disabled
|
||||
FlashHash.new
|
||||
else
|
||||
# otherwise, session is a CGI::Session or a TestSession
|
||||
# so make sure it gets retrieved from/saved to session storage after request processing
|
||||
session["flash"] ||= FlashHash.new
|
||||
end
|
||||
end
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
private
|
||||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
||||
assign_shortcuts_without_flash(request, response)
|
||||
flash(:refresh)
|
||||
flash.sweep if @_session && !component_request?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
33
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
module Http
|
||||
class Headers < ::Hash
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def initialize(*args)
|
||||
if args.size == 1 && args[0].is_a?(Hash)
|
||||
super()
|
||||
update(args[0])
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def [](header_name)
|
||||
if include?(header_name)
|
||||
super
|
||||
else
|
||||
super(env_name(header_name))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Converts a HTTP header name to an environment variable name.
|
||||
def env_name(header_name)
|
||||
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
|
||||
end
|
||||
memoize :env_name
|
||||
end
|
||||
end
|
||||
end
|
||||
221
vendor/rails/actionpack/lib/action_controller/helpers.rb
vendored
Normal file
221
vendor/rails/actionpack/lib/action_controller/helpers.rb
vendored
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
# FIXME: helper { ... } is broken on Ruby 1.9
|
||||
module ActionController #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
|
||||
|
||||
def self.included(base)
|
||||
# Initialize the base module to aggregate its helpers.
|
||||
base.class_inheritable_accessor :master_helper_module
|
||||
base.master_helper_module = Module.new
|
||||
|
||||
# Extend base with class methods to declare helpers.
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
# Wrap inherited to create a new master helper module for subclasses.
|
||||
class << self
|
||||
alias_method_chain :inherited, :helper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
|
||||
# +numbers+ and Active Record 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., <tt>MyController</tt> will automatically
|
||||
# include <tt>MyHelper</tt>.
|
||||
#
|
||||
# Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> 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 <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
|
||||
#
|
||||
# <% @events.each do |event| -%>
|
||||
# <p>
|
||||
# <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
||||
# </p>
|
||||
# <% 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 ClassMethods
|
||||
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
|
||||
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
||||
# available to the templates.
|
||||
def add_template_helper(helper_module) #:nodoc:
|
||||
master_helper_module.module_eval { include helper_module }
|
||||
end
|
||||
|
||||
# The +helper+ class method can take a series of helper module names, a block, or both.
|
||||
#
|
||||
# * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>.
|
||||
# * <tt>&block</tt>: 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 <tt>:all</tt>, the controller will include all helpers from
|
||||
# <tt>app/helpers/**/*.rb</tt> 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)
|
||||
args.flatten.each do |arg|
|
||||
case arg
|
||||
when Module
|
||||
add_template_helper(arg)
|
||||
when :all
|
||||
helper(all_application_helpers)
|
||||
when String, Symbol
|
||||
file_name = arg.to_s.underscore + '_helper'
|
||||
class_name = file_name.camelize
|
||||
|
||||
begin
|
||||
require_dependency(file_name)
|
||||
rescue LoadError => load_error
|
||||
requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
|
||||
if requiree == file_name
|
||||
msg = "Missing helper file helpers/#{file_name}.rb"
|
||||
raise LoadError.new(msg).copy_blame!(load_error)
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
add_template_helper(class_name.constantize)
|
||||
else
|
||||
raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
|
||||
end
|
||||
end
|
||||
|
||||
# Evaluate block in template class if given.
|
||||
master_helper_module.module_eval(&block) if block_given?
|
||||
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 -%>
|
||||
def helper_method(*methods)
|
||||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
def #{method}(*args, &block)
|
||||
controller.send(%(#{method}), *args, &block)
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
|
||||
# Declares helper accessors for controller attributes. For example, the
|
||||
# following adds new +name+ and <tt>name=</tt> instance methods to a
|
||||
# controller and makes them available to the view:
|
||||
# helper_attr :name
|
||||
# attr_accessor :name
|
||||
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
|
||||
unless @helper_proxy
|
||||
@helper_proxy = ActionView::Base.new
|
||||
@helper_proxy.extend master_helper_module
|
||||
else
|
||||
@helper_proxy
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def default_helper_module!
|
||||
unless name.blank?
|
||||
module_name = name.sub(/Controller$|$/, 'Helper')
|
||||
module_path = module_name.split('::').map { |m| m.underscore }.join('/')
|
||||
require_dependency module_path
|
||||
helper module_name.constantize
|
||||
end
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing? module_path
|
||||
rescue NameError => e
|
||||
raise unless e.missing_name? module_name
|
||||
end
|
||||
|
||||
def inherited_with_helper(child)
|
||||
inherited_without_helper(child)
|
||||
|
||||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.__send__ :include, master_helper_module
|
||||
child.__send__ :default_helper_module!
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
|
||||
end
|
||||
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
|
||||
124
vendor/rails/actionpack/lib/action_controller/http_authentication.rb
vendored
Normal file
124
vendor/rails/actionpack/lib/action_controller/http_authentication.rb
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
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
|
||||
#
|
||||
#
|
||||
# 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
|
||||
end
|
||||
end
|
||||
631
vendor/rails/actionpack/lib/action_controller/integration.rb
vendored
Normal file
631
vendor/rails/actionpack/lib/action_controller/integration.rb
vendored
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
require 'active_support/test_case'
|
||||
require 'action_controller/dispatcher'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
module ActionController
|
||||
module Integration #:nodoc:
|
||||
# An integration Session instance represents a set of requests and responses
|
||||
# performed sequentially by some virtual user. Becase you can instantiate
|
||||
# multiple sessions and run them side-by-side, you can also mimic (to some
|
||||
# limited extent) multiple simultaneous users interacting with your system.
|
||||
#
|
||||
# Typically, you will instantiate a new session using IntegrationTest#open_session,
|
||||
# rather than instantiating Integration::Session directly.
|
||||
class Session
|
||||
include Test::Unit::Assertions
|
||||
include ActionController::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
# The integer HTTP status code of the last request.
|
||||
attr_reader :status
|
||||
|
||||
# The status message that accompanied the status code of the last request.
|
||||
attr_reader :status_message
|
||||
|
||||
# The URI of the last request.
|
||||
attr_reader :path
|
||||
|
||||
# The hostname used in the last request.
|
||||
attr_accessor :host
|
||||
|
||||
# The remote_addr used in the last request.
|
||||
attr_accessor :remote_addr
|
||||
|
||||
# The Accept header to send.
|
||||
attr_accessor :accept
|
||||
|
||||
# A map of the cookies returned by the last response, and which will be
|
||||
# sent with the next request.
|
||||
attr_reader :cookies
|
||||
|
||||
# A map of the headers returned by the last response.
|
||||
attr_reader :headers
|
||||
|
||||
# A reference to the controller instance used by the last request.
|
||||
attr_reader :controller
|
||||
|
||||
# A reference to the request instance used by the last request.
|
||||
attr_reader :request
|
||||
|
||||
# A reference to the response instance used by the last request.
|
||||
attr_reader :response
|
||||
|
||||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
class MultiPartNeededException < Exception
|
||||
end
|
||||
|
||||
# Create and initialize a new Session instance.
|
||||
def initialize
|
||||
reset!
|
||||
end
|
||||
|
||||
# Resets the instance. This can be used to reset the state information
|
||||
# in an existing session instance, so it can be used from a clean-slate
|
||||
# condition.
|
||||
#
|
||||
# session.reset!
|
||||
def reset!
|
||||
@status = @path = @headers = nil
|
||||
@result = @status_message = nil
|
||||
@https = false
|
||||
@cookies = {}
|
||||
@controller = @request = @response = nil
|
||||
@request_count = 0
|
||||
|
||||
self.host = "www.example.com"
|
||||
self.remote_addr = "127.0.0.1"
|
||||
self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
||||
|
||||
unless defined? @named_routes_configured
|
||||
# install the named routes in this session instance.
|
||||
klass = class<<self; self; end
|
||||
Routing::Routes.install_helpers(klass)
|
||||
|
||||
# the helpers are made protected by default--we make them public for
|
||||
# easier access during testing and troubleshooting.
|
||||
klass.module_eval { public *Routing::Routes.named_routes.helpers }
|
||||
@named_routes_configured = true
|
||||
end
|
||||
end
|
||||
|
||||
# Specify whether or not the session should mimic a secure HTTPS request.
|
||||
#
|
||||
# session.https!
|
||||
# session.https!(false)
|
||||
def https!(flag=true)
|
||||
@https = flag
|
||||
end
|
||||
|
||||
# Return +true+ if the session is mimicking a secure HTTPS request.
|
||||
#
|
||||
# if session.https?
|
||||
# ...
|
||||
# end
|
||||
def https?
|
||||
@https
|
||||
end
|
||||
|
||||
# Set the host name to use in the next request.
|
||||
#
|
||||
# session.host! "www.example.com"
|
||||
def host!(name)
|
||||
@host = name
|
||||
end
|
||||
|
||||
# Follow a single redirect response. If the last response was not a
|
||||
# redirect, an exception will be raised. Otherwise, the redirect is
|
||||
# performed on the location header.
|
||||
def follow_redirect!
|
||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||
get(interpret_uri(headers['location'].first))
|
||||
status
|
||||
end
|
||||
|
||||
# Performs a request using the specified method, following any subsequent
|
||||
# redirect. Note that the redirects are followed until the response is
|
||||
# not a redirect--this means you may run into an infinite loop if your
|
||||
# redirect loops back to itself.
|
||||
def request_via_redirect(http_method, path, parameters = nil, headers = nil)
|
||||
send(http_method, path, parameters, headers)
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
end
|
||||
|
||||
# Performs a GET request, following any subsequent redirect.
|
||||
# See +request_via_redirect+ for more information.
|
||||
def get_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:get, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a POST request, following any subsequent redirect.
|
||||
# See +request_via_redirect+ for more information.
|
||||
def post_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:post, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a PUT request, following any subsequent redirect.
|
||||
# See +request_via_redirect+ for more information.
|
||||
def put_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:put, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a DELETE request, following any subsequent redirect.
|
||||
# See +request_via_redirect+ for more information.
|
||||
def delete_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:delete, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Returns +true+ if the last response was a redirect.
|
||||
def redirect?
|
||||
status/100 == 3
|
||||
end
|
||||
|
||||
# Performs a GET request with the given parameters.
|
||||
#
|
||||
# - +path+: The URI (as a String) on which you want to perform a GET request.
|
||||
# - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
|
||||
# a Hash, or a String that is appropriately encoded
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
|
||||
# automatically be upcased, with the prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# This method returns an AbstractResponse object, which one can use to inspect
|
||||
# the details of the response. Furthermore, if this method was called from an
|
||||
# ActionController::IntegrationTest object, then that object's <tt>@response</tt>
|
||||
# instance variable will point to the same response object.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
|
||||
# +put+, +delete+, and +head+.
|
||||
def get(path, parameters = nil, headers = nil)
|
||||
process :get, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a POST request with the given parameters. See get() for more details.
|
||||
def post(path, parameters = nil, headers = nil)
|
||||
process :post, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a PUT request with the given parameters. See get() for more details.
|
||||
def put(path, parameters = nil, headers = nil)
|
||||
process :put, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a DELETE request with the given parameters. See get() for more details.
|
||||
def delete(path, parameters = nil, headers = nil)
|
||||
process :delete, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a HEAD request with the given parameters. See get() for more details.
|
||||
def head(path, parameters = nil, headers = nil)
|
||||
process :head, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs an XMLHttpRequest request with the given parameters, mirroring
|
||||
# a request from the Prototype library.
|
||||
#
|
||||
# The request_method is :get, :post, :put, :delete or :head; the
|
||||
# parameters are +nil+, a hash, or a url-encoded or multipart string;
|
||||
# the headers are a hash. Keys are automatically upcased and prefixed
|
||||
# with 'HTTP_' if not already.
|
||||
def xml_http_request(request_method, path, parameters = nil, headers = nil)
|
||||
headers ||= {}
|
||||
headers['X-Requested-With'] = 'XMLHttpRequest'
|
||||
headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
|
||||
process(request_method, path, parameters, headers)
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
# Returns the URL for the given options, according to the rules specified
|
||||
# in the application's routes.
|
||||
def url_for(options)
|
||||
controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
|
||||
end
|
||||
|
||||
private
|
||||
# Tailors the session based on the given URI, setting the HTTPS value
|
||||
# and the hostname.
|
||||
def interpret_uri(path)
|
||||
location = URI.parse(path)
|
||||
https! URI::HTTPS === location if location.scheme
|
||||
host! location.host if location.host
|
||||
location.query ? "#{location.path}?#{location.query}" : location.path
|
||||
end
|
||||
|
||||
# Performs the actual request.
|
||||
def process(method, path, parameters = nil, headers = nil)
|
||||
data = requestify(parameters)
|
||||
path = interpret_uri(path) if path =~ %r{://}
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
@path = path
|
||||
env = {}
|
||||
|
||||
if method == :get
|
||||
env["QUERY_STRING"] = data
|
||||
data = nil
|
||||
end
|
||||
|
||||
env.update(
|
||||
"REQUEST_METHOD" => method.to_s.upcase,
|
||||
"REQUEST_URI" => path,
|
||||
"HTTP_HOST" => host,
|
||||
"REMOTE_ADDR" => remote_addr,
|
||||
"SERVER_PORT" => (https? ? "443" : "80"),
|
||||
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
||||
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
||||
"HTTP_COOKIE" => encode_cookies,
|
||||
"HTTPS" => https? ? "on" : "off",
|
||||
"HTTP_ACCEPT" => accept
|
||||
)
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
unless ActionController::Base.respond_to?(:clear_last_instantiation!)
|
||||
ActionController::Base.module_eval { include ControllerCapture }
|
||||
end
|
||||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
|
||||
@status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
|
||||
@request_count += 1
|
||||
|
||||
@controller = ActionController::Base.last_instantiation
|
||||
@request = @controller.request
|
||||
@response = @controller.response
|
||||
|
||||
# Decorate the response with the standard behavior of the TestResponse
|
||||
# so that things like assert_response can be used in integration
|
||||
# tests.
|
||||
@response.extend(TestResponseBehavior)
|
||||
|
||||
@html_document = nil
|
||||
|
||||
# Inject status back in for backwords compatibility with CGI
|
||||
@headers['Status'] = @status
|
||||
|
||||
@status, @status_message = @status.split(/ /)
|
||||
@status = @status.to_i
|
||||
|
||||
cgi_headers = Hash.new { |h,k| h[k] = [] }
|
||||
@headers.each do |key, value|
|
||||
cgi_headers[key.downcase] << value
|
||||
end
|
||||
cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
|
||||
@headers = cgi_headers
|
||||
|
||||
@response.headers['cookie'] ||= []
|
||||
(@headers['set-cookie'] || []).each do |cookie|
|
||||
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
|
||||
# Fake CGI cookie header
|
||||
# DEPRECATE: Use response.headers["Set-Cookie"] instead
|
||||
@response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
|
||||
end
|
||||
|
||||
return status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
||||
return status
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
cookies.inject("") do |string, (name, value)|
|
||||
string << "#{name}=#{value}; "
|
||||
end
|
||||
end
|
||||
|
||||
# Get a temporary URL writer object
|
||||
def generic_url_rewriter
|
||||
env = {
|
||||
'REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off"
|
||||
}
|
||||
ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
|
||||
end
|
||||
|
||||
def name_with_prefix(prefix, name)
|
||||
prefix ? "#{prefix}[#{name}]" : name.to_s
|
||||
end
|
||||
|
||||
# Convert the given parameters to a request string. The parameters may
|
||||
# be a string, +nil+, or a Hash.
|
||||
def requestify(parameters, prefix=nil)
|
||||
if TestUploadedFile === parameters
|
||||
raise MultiPartNeededException
|
||||
elsif Hash === parameters
|
||||
return nil if parameters.empty?
|
||||
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
||||
elsif Array === parameters
|
||||
parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
|
||||
elsif prefix.nil?
|
||||
parameters
|
||||
else
|
||||
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_body(params, boundary)
|
||||
multipart_requestify(params).map do |key, value|
|
||||
if value.respond_to?(:original_filename)
|
||||
File.open(value.path) do |f|
|
||||
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
||||
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
|
||||
Content-Type: #{value.content_type}\r
|
||||
Content-Length: #{File.stat(value.path).size}\r
|
||||
\r
|
||||
#{f.read}\r
|
||||
EOF
|
||||
end
|
||||
else
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"\r
|
||||
\r
|
||||
#{value}\r
|
||||
EOF
|
||||
end
|
||||
end.join("")+"--#{boundary}--\r"
|
||||
end
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
# can capture the controller used to satisfy a request.
|
||||
module ControllerCapture #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
class << self
|
||||
alias_method_chain :new, :capture
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
mattr_accessor :last_instantiation
|
||||
|
||||
def clear_last_instantiation!
|
||||
self.last_instantiation = nil
|
||||
end
|
||||
|
||||
def new_with_capture(*args)
|
||||
controller = new_without_capture(*args)
|
||||
self.last_instantiation ||= controller
|
||||
controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Runner
|
||||
# Reset the current session. This is useful for testing multiple sessions
|
||||
# in a single test case.
|
||||
def reset!
|
||||
@integration_session = open_session
|
||||
end
|
||||
|
||||
%w(get post put head delete cookies assigns
|
||||
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.__send__(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Open a new session instance. If a block is given, the new session is
|
||||
# yielded to the block before being returned.
|
||||
#
|
||||
# session = open_session do |sess|
|
||||
# sess.extend(CustomAssertions)
|
||||
# end
|
||||
#
|
||||
# By default, a single session is automatically created for you, but you
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session
|
||||
session = Integration::Session.new
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||
if self.class.respond_to?(:fixture_table_names)
|
||||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
|
||||
end
|
||||
end
|
||||
|
||||
# delegate add_assertion to the test case
|
||||
extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
|
||||
session.extend(extras)
|
||||
session.delegate = self
|
||||
session.test_result = @_result
|
||||
|
||||
yield session if block_given?
|
||||
session
|
||||
end
|
||||
|
||||
# Copy the instance variables from the current session instance into the
|
||||
# test instance.
|
||||
def copy_session_variables! #:nodoc:
|
||||
return unless @integration_session
|
||||
%w(controller response request).each do |var|
|
||||
instance_variable_set("@#{var}", @integration_session.__send__(var))
|
||||
end
|
||||
end
|
||||
|
||||
# Delegate unhandled messages to the current session instance.
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
returning @integration_session.__send__(sym, *args, &block) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# An IntegrationTest is one that spans multiple controllers and actions,
|
||||
# tying them all together to ensure they work together as expected. It tests
|
||||
# more completely than either unit or functional tests do, exercising the
|
||||
# entire stack, from the dispatcher to the database.
|
||||
#
|
||||
# At its simplest, you simply extend IntegrationTest and write your tests
|
||||
# using the get/post methods:
|
||||
#
|
||||
# require "#{File.dirname(__FILE__)}/test_helper"
|
||||
#
|
||||
# class ExampleTest < ActionController::IntegrationTest
|
||||
# fixtures :people
|
||||
#
|
||||
# def test_login
|
||||
# # get the login page
|
||||
# get "/login"
|
||||
# assert_equal 200, status
|
||||
#
|
||||
# # post the login and follow through to the home page
|
||||
# post "/login", :username => people(:jamis).username,
|
||||
# :password => people(:jamis).password
|
||||
# follow_redirect!
|
||||
# assert_equal 200, status
|
||||
# assert_equal "/home", path
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# However, you can also have multiple session instances open per test, and
|
||||
# even extend those instances with assertions and methods to create a very
|
||||
# powerful testing DSL that is specific for your application. You can even
|
||||
# reference any named routes you happen to have defined!
|
||||
#
|
||||
# require "#{File.dirname(__FILE__)}/test_helper"
|
||||
#
|
||||
# class AdvancedTest < ActionController::IntegrationTest
|
||||
# fixtures :people, :rooms
|
||||
#
|
||||
# def test_login_and_speak
|
||||
# jamis, david = login(:jamis), login(:david)
|
||||
# room = rooms(:office)
|
||||
#
|
||||
# jamis.enter(room)
|
||||
# jamis.speak(room, "anybody home?")
|
||||
#
|
||||
# david.enter(room)
|
||||
# david.speak(room, "hello!")
|
||||
# end
|
||||
#
|
||||
# private
|
||||
#
|
||||
# module CustomAssertions
|
||||
# def enter(room)
|
||||
# # reference a named route, for maximum internal consistency!
|
||||
# get(room_url(:id => room.id))
|
||||
# assert(...)
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# def speak(room, message)
|
||||
# xml_http_request "/say/#{room.id}", :message => message
|
||||
# assert(...)
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def login(who)
|
||||
# open_session do |sess|
|
||||
# sess.extend(CustomAssertions)
|
||||
# who = people(who)
|
||||
# sess.post "/login", :username => who.username,
|
||||
# :password => who.password
|
||||
# assert(...)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
class IntegrationTest < ActiveSupport::TestCase
|
||||
include Integration::Runner
|
||||
|
||||
# Work around a bug in test/unit caused by the default test being named
|
||||
# as a symbol (:default_test), which causes regex test filters
|
||||
# (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
|
||||
# symbols.
|
||||
def initialize(name) #:nodoc:
|
||||
super(name.to_s)
|
||||
end
|
||||
|
||||
# Work around test/unit's requirement that every subclass of TestCase have
|
||||
# at least one test method. Note that this implementation extends to all
|
||||
# subclasses, as well, so subclasses of IntegrationTest may also exist
|
||||
# without any test methods.
|
||||
def run(*args) #:nodoc:
|
||||
return if @method_name == "default_test"
|
||||
super
|
||||
end
|
||||
|
||||
# Because of how use_instantiated_fixtures and use_transactional_fixtures
|
||||
# are defined, we need to treat them as special cases. Otherwise, users
|
||||
# would potentially have to set their values for both Test::Unit::TestCase
|
||||
# ActionController::IntegrationTest, since by the time the value is set on
|
||||
# TestCase, IntegrationTest has already been defined and cannot inherit
|
||||
# changes to those variables. So, we make those two attributes copy-on-write.
|
||||
|
||||
class << self
|
||||
def use_transactional_fixtures=(flag) #:nodoc:
|
||||
@_use_transactional_fixtures = true
|
||||
@use_transactional_fixtures = flag
|
||||
end
|
||||
|
||||
def use_instantiated_fixtures=(flag) #:nodoc:
|
||||
@_use_instantiated_fixtures = true
|
||||
@use_instantiated_fixtures = flag
|
||||
end
|
||||
|
||||
def use_transactional_fixtures #:nodoc:
|
||||
@_use_transactional_fixtures ?
|
||||
@use_transactional_fixtures :
|
||||
superclass.use_transactional_fixtures
|
||||
end
|
||||
|
||||
def use_instantiated_fixtures #:nodoc:
|
||||
@_use_instantiated_fixtures ?
|
||||
@use_instantiated_fixtures :
|
||||
superclass.use_instantiated_fixtures
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
284
vendor/rails/actionpack/lib/action_controller/layout.rb
vendored
Normal file
284
vendor/rails/actionpack/lib/action_controller/layout.rb
vendored
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
module ActionController #:nodoc:
|
||||
module Layout #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
class << self
|
||||
alias_method_chain :inherited, :layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
#
|
||||
# NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
|
||||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
#
|
||||
# == 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:
|
||||
#
|
||||
# <h1><%= @page_title %></h1>
|
||||
# <%= 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:
|
||||
#
|
||||
# <h1>Welcome</h1>
|
||||
# Off-world colonies offers you a chance to start a new life
|
||||
#
|
||||
# == Automatic layout assignment
|
||||
#
|
||||
# If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
|
||||
# set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
|
||||
# <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
|
||||
# the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
|
||||
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
||||
# no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
|
||||
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
|
||||
# Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
|
||||
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
|
||||
# class has a layout with the same name.
|
||||
#
|
||||
# == Inheritance for layouts
|
||||
#
|
||||
# Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
# layout "bank_standard"
|
||||
#
|
||||
# class InformationController < BankController
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# == 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 <tt>app/views/layouts/</tt>.
|
||||
# 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
|
||||
# <tt>:only</tt> and <tt>:except</tt> 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 <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
||||
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
||||
#
|
||||
# == 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 <tt>:layout</tt> option to the <tt>render</tt> 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 ClassMethods
|
||||
# If a layout is specified, all rendered actions will have their result rendered
|
||||
# when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
|
||||
# performance and have access to them as any normal template would.
|
||||
def layout(template_name, conditions = {}, auto = false)
|
||||
add_layout_conditions(conditions)
|
||||
write_inheritable_attribute(:layout, template_name)
|
||||
write_inheritable_attribute(:auto_layout, auto)
|
||||
end
|
||||
|
||||
def layout_conditions #:nodoc:
|
||||
@layout_conditions ||= read_inheritable_attribute(:layout_conditions)
|
||||
end
|
||||
|
||||
def default_layout(format) #:nodoc:
|
||||
layout = read_inheritable_attribute(:layout)
|
||||
return layout unless read_inheritable_attribute(:auto_layout)
|
||||
@default_layout ||= {}
|
||||
@default_layout[format] ||= default_layout_with_format(format, layout)
|
||||
@default_layout[format]
|
||||
end
|
||||
|
||||
def layout_list #:nodoc:
|
||||
Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
|
||||
end
|
||||
|
||||
private
|
||||
def inherited_with_layout(child)
|
||||
inherited_without_layout(child)
|
||||
unless child.name.blank?
|
||||
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
|
||||
child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
end
|
||||
end
|
||||
|
||||
def add_layout_conditions(conditions)
|
||||
write_inheritable_hash(:layout_conditions, normalize_conditions(conditions))
|
||||
end
|
||||
|
||||
def normalize_conditions(conditions)
|
||||
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
||||
end
|
||||
|
||||
def default_layout_with_format(format, layout)
|
||||
list = layout_list
|
||||
if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
(!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil
|
||||
else
|
||||
layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
|
||||
# is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
|
||||
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
||||
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
||||
def active_layout(passed_layout = nil)
|
||||
layout = passed_layout || self.class.default_layout(default_template_format)
|
||||
active_layout = case layout
|
||||
when String then layout
|
||||
when Symbol then __send__(layout)
|
||||
when Proc then layout.call(self)
|
||||
end
|
||||
|
||||
# Explicitly passed layout names with slashes are looked up relative to the template root,
|
||||
# but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
|
||||
# to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
|
||||
if active_layout
|
||||
if active_layout.include?('/') && ! layout_directory?(active_layout)
|
||||
active_layout
|
||||
else
|
||||
"layouts/#{active_layout}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def candidate_for_layout?(options)
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
|
||||
!@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
|
||||
end
|
||||
|
||||
def pick_layout(options)
|
||||
if options.has_key?(:layout)
|
||||
case layout = options.delete(:layout)
|
||||
when FalseClass
|
||||
nil
|
||||
when NilClass, TrueClass
|
||||
active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
|
||||
else
|
||||
active_layout(layout)
|
||||
end
|
||||
else
|
||||
active_layout if action_has_layout? && candidate_for_layout?(options)
|
||||
end
|
||||
end
|
||||
|
||||
def action_has_layout?
|
||||
if conditions = self.class.layout_conditions
|
||||
case
|
||||
when only = conditions[:only]
|
||||
only.include?(action_name)
|
||||
when except = conditions[:except]
|
||||
!except.include?(action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def layout_directory?(layout_name)
|
||||
@template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false
|
||||
rescue ActionView::MissingTemplate
|
||||
false
|
||||
end
|
||||
|
||||
def default_template_format
|
||||
response.template.template_format
|
||||
end
|
||||
end
|
||||
end
|
||||
178
vendor/rails/actionpack/lib/action_controller/mime_responds.rb
vendored
Normal file
178
vendor/rails/actionpack/lib/action_controller/mime_responds.rb
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
module ActionController #:nodoc:
|
||||
module MimeResponds #:nodoc:
|
||||
def self.included(base)
|
||||
base.module_eval do
|
||||
include ActionController::MimeResponds::InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# 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:
|
||||
#
|
||||
# <person>
|
||||
# <id>...</id>
|
||||
# ...
|
||||
# <company>
|
||||
# <id>...</id>
|
||||
# <name>...</name>
|
||||
# ...
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
# 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):
|
||||
#
|
||||
# <person>
|
||||
# <name>...</name>
|
||||
# <company>
|
||||
# <name>...</name>
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
# 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
|
||||
def respond_to(*types, &block)
|
||||
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
|
||||
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
|
||||
responder = Responder.new(self)
|
||||
block.call(responder)
|
||||
responder.respond
|
||||
end
|
||||
end
|
||||
|
||||
class Responder #:nodoc:
|
||||
def initialize(controller)
|
||||
@controller = controller
|
||||
@request = controller.request
|
||||
@response = controller.response
|
||||
|
||||
if ActionController::Base.use_accept_header
|
||||
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
|
||||
else
|
||||
@mime_type_priority = [@request.format]
|
||||
end
|
||||
|
||||
@order = []
|
||||
@responses = {}
|
||||
end
|
||||
|
||||
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] ||= Proc.new do
|
||||
@response.template.template_format = mime_type.to_sym
|
||||
@response.content_type = mime_type.to_s
|
||||
block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
|
||||
end
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
if args.any?
|
||||
args.each { |type| send(type, &block) }
|
||||
else
|
||||
custom(@mime_type_priority.first, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
mime_constant = symbol.to_s.upcase
|
||||
|
||||
if Mime::SET.include?(Mime.const_get(mime_constant))
|
||||
custom(Mime.const_get(mime_constant), &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond
|
||||
for priority in @mime_type_priority
|
||||
if priority == Mime::ALL
|
||||
@responses[@order.first].call
|
||||
return
|
||||
else
|
||||
if @responses[priority]
|
||||
@responses[priority].call
|
||||
return # mime type match found, be happy and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @order.include?(Mime::ALL)
|
||||
@responses[Mime::ALL].call
|
||||
else
|
||||
@controller.send :head, :not_acceptable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
204
vendor/rails/actionpack/lib/action_controller/mime_type.rb
vendored
Normal file
204
vendor/rails/actionpack/lib/action_controller/mime_type.rb
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
require 'set'
|
||||
|
||||
module Mime
|
||||
SET = []
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
||||
|
||||
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
# def show
|
||||
# @post = Post.find(params[:id])
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
class Type
|
||||
@@html_types = Set.new [:html, :all]
|
||||
cattr_reader :html_types
|
||||
|
||||
# These are the content types which browsers can generate without using ajax, flash, etc
|
||||
# i.e. following a link, getting an image or posting a form. CSRF protection
|
||||
# only needs to protect against these types.
|
||||
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
|
||||
cattr_reader :browser_generated_types
|
||||
|
||||
|
||||
@@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml]
|
||||
def self.unverifiable_types
|
||||
ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller)
|
||||
@@unverifiable_types
|
||||
end
|
||||
|
||||
# A simple helper class used in parsing the accept header
|
||||
class AcceptItem #:nodoc:
|
||||
attr_accessor :order, :name, :q
|
||||
|
||||
def initialize(order, name, q=nil)
|
||||
@order = order
|
||||
@name = name.strip
|
||||
q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
|
||||
@q = ((q || 1.0).to_f * 100).to_i
|
||||
end
|
||||
|
||||
def to_s
|
||||
@name
|
||||
end
|
||||
|
||||
def <=>(item)
|
||||
result = item.q <=> q
|
||||
result = order <=> item.order if result == 0
|
||||
result
|
||||
end
|
||||
|
||||
def ==(item)
|
||||
name == (item.respond_to?(:name) ? item.name : item)
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def lookup(string)
|
||||
LOOKUP[string]
|
||||
end
|
||||
|
||||
def lookup_by_extension(extension)
|
||||
EXTENSION_LOOKUP[extension]
|
||||
end
|
||||
|
||||
# Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
|
||||
# rendering different HTML versions depending on the user agent, like an iPhone.
|
||||
def register_alias(string, symbol, extension_synonyms = [])
|
||||
register(string, symbol, [], extension_synonyms, true)
|
||||
end
|
||||
|
||||
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
|
||||
Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
|
||||
|
||||
SET << Mime.const_get(symbol.to_s.upcase)
|
||||
|
||||
([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
|
||||
([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
|
||||
end
|
||||
|
||||
def parse(accept_header)
|
||||
if accept_header !~ /,/
|
||||
[Mime::Type.lookup(accept_header)]
|
||||
else
|
||||
# keep track of creation order to keep the subsequent sort stable
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params, q = header.split(/;\s*q=/)
|
||||
if params
|
||||
params.strip!
|
||||
list << AcceptItem.new(index, params, q) unless params.empty?
|
||||
end
|
||||
end
|
||||
list.sort!
|
||||
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index(Mime::XML.to_s)
|
||||
|
||||
if text_xml && app_xml
|
||||
# set the q value to the max of the two
|
||||
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
|
||||
|
||||
# make sure app_xml is ahead of text_xml in the list
|
||||
if app_xml > text_xml
|
||||
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
|
||||
app_xml, text_xml = text_xml, app_xml
|
||||
end
|
||||
|
||||
# delete text_xml from the list
|
||||
list.delete_at(text_xml)
|
||||
|
||||
elsif text_xml
|
||||
list[text_xml].name = Mime::XML.to_s
|
||||
end
|
||||
|
||||
# Look for more specific XML-based types and sort them ahead of app/xml
|
||||
|
||||
if app_xml
|
||||
idx = app_xml
|
||||
app_xml_type = list[app_xml]
|
||||
|
||||
while(idx < list.length)
|
||||
type = list[idx]
|
||||
break if type.q < app_xml_type.q
|
||||
if type.name =~ /\+xml$/
|
||||
list[app_xml], list[idx] = list[idx], list[app_xml]
|
||||
app_xml = idx
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
||||
list
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(string, symbol = nil, synonyms = [])
|
||||
@symbol, @synonyms = symbol, synonyms
|
||||
@string = string
|
||||
end
|
||||
|
||||
def to_s
|
||||
@string
|
||||
end
|
||||
|
||||
def to_str
|
||||
to_s
|
||||
end
|
||||
|
||||
def to_sym
|
||||
@symbol || @string.to_sym
|
||||
end
|
||||
|
||||
def ===(list)
|
||||
if list.is_a?(Array)
|
||||
(@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def ==(mime_type)
|
||||
return false if mime_type.blank?
|
||||
(@synonyms + [ self ]).any? do |synonym|
|
||||
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
|
||||
# ActionController::RequestForgeryProtection.
|
||||
def verify_request?
|
||||
browser_generated?
|
||||
end
|
||||
|
||||
def html?
|
||||
@@html_types.include?(to_sym) || @string =~ /html/
|
||||
end
|
||||
|
||||
def browser_generated?
|
||||
@@browser_generated_types.include?(to_sym)
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *args)
|
||||
if method.to_s =~ /(\w+)\?$/
|
||||
$1.downcase.to_sym == to_sym
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'action_controller/mime_types'
|
||||
21
vendor/rails/actionpack/lib/action_controller/mime_types.rb
vendored
Normal file
21
vendor/rails/actionpack/lib/action_controller/mime_types.rb
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Build list of Mime types for HTTP responses
|
||||
# http://www.iana.org/assignments/media-types/
|
||||
|
||||
Mime::Type.register "*/*", :all
|
||||
Mime::Type.register "text/plain", :text, [], %w(txt)
|
||||
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
|
||||
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||
Mime::Type.register "text/css", :css
|
||||
Mime::Type.register "text/calendar", :ics
|
||||
Mime::Type.register "text/csv", :csv
|
||||
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
|
||||
Mime::Type.register "application/rss+xml", :rss
|
||||
Mime::Type.register "application/atom+xml", :atom
|
||||
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
|
||||
|
||||
Mime::Type.register "multipart/form-data", :multipart_form
|
||||
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
|
||||
|
||||
# http://www.ietf.org/rfc/rfc4627.txt
|
||||
# http://www.json.org/JSONRequest.html
|
||||
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
|
||||
16
vendor/rails/actionpack/lib/action_controller/performance_test.rb
vendored
Normal file
16
vendor/rails/actionpack/lib/action_controller/performance_test.rb
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
require 'action_controller/integration'
|
||||
require 'active_support/testing/performance'
|
||||
require 'active_support/testing/default'
|
||||
|
||||
module ActionController
|
||||
# An integration test that runs a code profiler on your test methods.
|
||||
# Profiling output for combinations of each test method, measurement, and
|
||||
# output format are written to your tmp/performance directory.
|
||||
#
|
||||
# By default, process_time is measured and both flat and graph_html output
|
||||
# formats are written, so you'll have two output files per test method.
|
||||
class PerformanceTest < ActionController::IntegrationTest
|
||||
include ActiveSupport::Testing::Performance
|
||||
include ActiveSupport::Testing::Default
|
||||
end
|
||||
end
|
||||
198
vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb
vendored
Normal file
198
vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
module ActionController
|
||||
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
||||
# given an Active Record model instance. They are to be used in combination with
|
||||
# ActionController::Resources.
|
||||
#
|
||||
# These methods are useful when you want to generate correct URL or path to a RESTful
|
||||
# resource without having to know the exact type of the record in question.
|
||||
#
|
||||
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
||||
#
|
||||
# polymorphic_url([:admin, @article, @comment])
|
||||
#
|
||||
# results in:
|
||||
#
|
||||
# admin_article_comment_url(@article, @comment)
|
||||
#
|
||||
# == Usage within the framework
|
||||
#
|
||||
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
|
||||
#
|
||||
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
||||
# <tt>url_for(@article)</tt>;
|
||||
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
||||
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
||||
# action;
|
||||
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
||||
# <tt>redirect_to(post)</tt> in your controllers;
|
||||
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
||||
# for feed entries.
|
||||
#
|
||||
# == Prefixed polymorphic helpers
|
||||
#
|
||||
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
||||
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
||||
# in options. Those are:
|
||||
#
|
||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
||||
# * <tt>formatted_polymorphic_url</tt>, <tt>formatted_polymorphic_path</tt>
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||
# formatted_polymorphic_path([@post, :pdf]) # => "/posts/1.pdf"
|
||||
module PolymorphicRoutes
|
||||
# Constructs a call to a named RESTful route for the given record and returns the
|
||||
# resulting URL string. For example:
|
||||
#
|
||||
# # calls post_url(post)
|
||||
# polymorphic_url(post) # => "http://example.com/posts/1"
|
||||
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
|
||||
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
|
||||
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
||||
# <tt>:new</tt>, <tt>:edit</tt>, or <tt>:formatted</tt>. Default is no prefix.
|
||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||
# Default is <tt>:url</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # an Article record
|
||||
# polymorphic_url(record) # same as article_url(record)
|
||||
#
|
||||
# # a Comment record
|
||||
# polymorphic_url(record) # same as comment_url(record)
|
||||
#
|
||||
# # it recognizes new records and maps to the collection
|
||||
# record = Comment.new
|
||||
# polymorphic_url(record) # same as comments_url()
|
||||
#
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
if record_or_hash_or_array.kind_of?(Array)
|
||||
record_or_hash_or_array = record_or_hash_or_array.compact
|
||||
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
|
||||
end
|
||||
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
format = extract_format(record_or_hash_or_array, options)
|
||||
namespace = extract_namespace(record_or_hash_or_array)
|
||||
|
||||
args = case record_or_hash_or_array
|
||||
when Hash; [ record_or_hash_or_array ]
|
||||
when Array; record_or_hash_or_array.dup
|
||||
else [ record_or_hash_or_array ]
|
||||
end
|
||||
|
||||
inflection =
|
||||
case
|
||||
when options[:action].to_s == "new"
|
||||
args.pop
|
||||
:singular
|
||||
when record.respond_to?(:new_record?) && record.new_record?
|
||||
args.pop
|
||||
:plural
|
||||
else
|
||||
:singular
|
||||
end
|
||||
|
||||
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
||||
args << format if format
|
||||
|
||||
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
|
||||
|
||||
url_options = options.except(:action, :routing_type, :format)
|
||||
unless url_options.empty?
|
||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||
end
|
||||
|
||||
__send__(named_route, *args)
|
||||
end
|
||||
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
options[:routing_type] = :path
|
||||
polymorphic_url(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
%w(edit new formatted).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__
|
||||
def #{action}_polymorphic_url(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
|
||||
end
|
||||
|
||||
def #{action}_polymorphic_path(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
|
||||
end
|
||||
EOT
|
||||
end
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
options[:routing_type] || :url
|
||||
end
|
||||
|
||||
def build_named_route_call(records, namespace, inflection, options = {})
|
||||
unless records.is_a?(Array)
|
||||
record = extract_record(records)
|
||||
route = ''
|
||||
else
|
||||
record = records.pop
|
||||
route = records.inject("") do |string, parent|
|
||||
if parent.is_a?(Symbol) || parent.is_a?(String)
|
||||
string << "#{parent}_"
|
||||
else
|
||||
string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if record.is_a?(Symbol) || record.is_a?(String)
|
||||
route << "#{record}_"
|
||||
else
|
||||
route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_"
|
||||
end
|
||||
|
||||
action_prefix(options) + namespace + route + routing_type(options).to_s
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
case record_or_hash_or_array
|
||||
when Array; record_or_hash_or_array.last
|
||||
when Hash; record_or_hash_or_array[:id]
|
||||
else record_or_hash_or_array
|
||||
end
|
||||
end
|
||||
|
||||
def extract_format(record_or_hash_or_array, options)
|
||||
if options[:action].to_s == "formatted" && record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.pop
|
||||
elsif options[:format]
|
||||
options[:format]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Remove the first symbols from the array and return the url prefix
|
||||
# implied by those symbols.
|
||||
def extract_namespace(record_or_hash_or_array)
|
||||
return "" unless record_or_hash_or_array.is_a?(Array)
|
||||
|
||||
namespace_keys = []
|
||||
while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
|
||||
namespace_keys << record_or_hash_or_array.shift
|
||||
end
|
||||
|
||||
namespace_keys.map {|k| "#{k}_"}.join
|
||||
end
|
||||
end
|
||||
end
|
||||
303
vendor/rails/actionpack/lib/action_controller/rack_process.rb
vendored
Normal file
303
vendor/rails/actionpack/lib/action_controller/rack_process.rb
vendored
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
require 'action_controller/cgi_ext'
|
||||
require 'action_controller/session/cookie_store'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class RackRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :session_options
|
||||
attr_reader :cgi
|
||||
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true,
|
||||
:session_http_only=> true
|
||||
}
|
||||
|
||||
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
|
||||
@session_options = session_options
|
||||
@env = env
|
||||
@cgi = CGIWrapper.new(self)
|
||||
super()
|
||||
end
|
||||
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
PATH_TRANSLATED REMOTE_HOST
|
||||
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
|
||||
SERVER_NAME SERVER_PROTOCOL
|
||||
|
||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
||||
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
||||
@env[env]
|
||||
end
|
||||
end
|
||||
|
||||
def query_string
|
||||
qs = super
|
||||
if !qs.blank?
|
||||
qs
|
||||
else
|
||||
@env['QUERY_STRING']
|
||||
end
|
||||
end
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@env['rack.input']
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@env.key?(key)
|
||||
end
|
||||
|
||||
def cookies
|
||||
return {} unless @env["HTTP_COOKIE"]
|
||||
|
||||
unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
|
||||
@env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
|
||||
@env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
|
||||
end
|
||||
|
||||
@env["rack.request.cookie_hash"]
|
||||
end
|
||||
|
||||
def server_port
|
||||
@env['SERVER_PORT'].to_i
|
||||
end
|
||||
|
||||
def server_software
|
||||
@env['SERVER_SOFTWARE'].split("/").first
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
end
|
||||
end
|
||||
@session
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
private
|
||||
# Delete an old session if it exists then create a new one.
|
||||
def new_session
|
||||
if @session_options == false
|
||||
Hash.new
|
||||
else
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
||||
end
|
||||
end
|
||||
|
||||
def cookie_only?
|
||||
session_options_with_string_keys['cookie_only']
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
end_msg
|
||||
end
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
class RackResponse < AbstractResponse #:nodoc:
|
||||
def initialize(request)
|
||||
@cgi = request.cgi
|
||||
@writer = lambda { |x| @body << x }
|
||||
@block = nil
|
||||
super()
|
||||
end
|
||||
|
||||
# Retrieve status from instance variable if has already been delete
|
||||
def status
|
||||
@status || super
|
||||
end
|
||||
|
||||
def out(output = $stdout, &block)
|
||||
# Nasty hack because CGI sessions are closed after the normal
|
||||
# prepare! statement
|
||||
set_cookies!
|
||||
|
||||
@block = block
|
||||
@status = headers.delete("Status")
|
||||
if [204, 304].include?(status.to_i)
|
||||
headers.delete("Content-Type")
|
||||
[status, headers.to_hash, []]
|
||||
else
|
||||
[status, headers.to_hash, self]
|
||||
end
|
||||
end
|
||||
alias to_a out
|
||||
|
||||
def each(&callback)
|
||||
if @body.respond_to?(:call)
|
||||
@writer = lambda { |x| callback.call(x) }
|
||||
@body.call(self, self)
|
||||
elsif @body.is_a?(String)
|
||||
@body.each_line(&callback)
|
||||
else
|
||||
@body.each(&callback)
|
||||
end
|
||||
|
||||
@writer = callback
|
||||
@block.call(self) if @block
|
||||
end
|
||||
|
||||
def write(str)
|
||||
@writer.call str.to_s
|
||||
str
|
||||
end
|
||||
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
|
||||
def empty?
|
||||
@block == nil && @body.empty?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
super
|
||||
|
||||
convert_language!
|
||||
convert_expires!
|
||||
set_status!
|
||||
# set_cookies!
|
||||
end
|
||||
|
||||
private
|
||||
def convert_language!
|
||||
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
||||
end
|
||||
|
||||
def convert_expires!
|
||||
headers["Expires"] = headers.delete("") if headers["expires"]
|
||||
end
|
||||
|
||||
def convert_content_type!
|
||||
super
|
||||
headers['Content-Type'] = headers.delete('type') || "text/html"
|
||||
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
||||
end
|
||||
|
||||
def set_content_length!
|
||||
super
|
||||
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
|
||||
end
|
||||
|
||||
def set_status!
|
||||
self.status ||= "200 OK"
|
||||
end
|
||||
|
||||
def set_cookies!
|
||||
# Convert 'cookie' header to 'Set-Cookie' headers.
|
||||
# Because Set-Cookie header can appear more the once in the response body,
|
||||
# we store it in a line break separated string that will be translated to
|
||||
# multiple Set-Cookie header by the handler.
|
||||
if cookie = headers.delete('cookie')
|
||||
cookies = []
|
||||
|
||||
case cookie
|
||||
when Array then cookie.each { |c| cookies << c.to_s }
|
||||
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
||||
else cookies << cookie.to_s
|
||||
end
|
||||
|
||||
@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
|
||||
|
||||
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CGIWrapper < ::CGI
|
||||
attr_reader :output_cookies
|
||||
|
||||
def initialize(request, *args)
|
||||
@request = request
|
||||
@args = *args
|
||||
@input = request.body
|
||||
|
||||
super *args
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= @request.params
|
||||
end
|
||||
|
||||
def cookies
|
||||
@request.cookies
|
||||
end
|
||||
|
||||
def query_string
|
||||
@request.query_string
|
||||
end
|
||||
|
||||
# Used to wrap the normal args variable used inside CGI.
|
||||
def args
|
||||
@args
|
||||
end
|
||||
|
||||
# Used to wrap the normal env_table variable used inside CGI.
|
||||
def env_table
|
||||
@request.env
|
||||
end
|
||||
|
||||
# Used to wrap the normal stdinput variable used inside CGI.
|
||||
def stdinput
|
||||
@input
|
||||
end
|
||||
end
|
||||
end
|
||||
104
vendor/rails/actionpack/lib/action_controller/record_identifier.rb
vendored
Normal file
104
vendor/rails/actionpack/lib/action_controller/record_identifier.rb
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
module ActionController
|
||||
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
|
||||
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
|
||||
# the view actions to a higher logical level. Example:
|
||||
#
|
||||
# # routes
|
||||
# map.resources :posts
|
||||
#
|
||||
# # view
|
||||
# <% div_for(post) do %> <div id="post_45" class="post">
|
||||
# <%= post.body %> What a wonderful world!
|
||||
# <% end %> </div>
|
||||
#
|
||||
# # controller
|
||||
# def destroy
|
||||
# post = Post.find(params[:id])
|
||||
# post.destroy
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
|
||||
# format.js do
|
||||
# # Calls: new Effect.fade('post_45');
|
||||
# render(:update) { |page| page[post].visual_effect(:fade) }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
|
||||
# that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
|
||||
# convention and allows you to write less code if you follow it.
|
||||
module RecordIdentifier
|
||||
extend self
|
||||
|
||||
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"
|
||||
# dom_class(Person) # => "person"
|
||||
#
|
||||
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
|
||||
#
|
||||
# dom_class(post, :edit) # => "edit_post"
|
||||
# dom_class(Person, :edit) # => "edit_person"
|
||||
def dom_class(record_or_class, prefix = nil)
|
||||
singular = singular_class_name(record_or_class)
|
||||
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
|
||||
end
|
||||
|
||||
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
||||
# If no id is found, prefix with "new_" instead. Examples:
|
||||
#
|
||||
# dom_id(Post.find(45)) # => "post_45"
|
||||
# dom_id(Post.new) # => "new_post"
|
||||
#
|
||||
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
||||
#
|
||||
# dom_id(Post.find(45), :edit) # => "edit_post_45"
|
||||
def dom_id(record, prefix = nil)
|
||||
if record_id = record.id
|
||||
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
|
||||
else
|
||||
dom_class(record, prefix || NEW)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the plural class name of a record or class. Examples:
|
||||
#
|
||||
# plural_class_name(post) # => "posts"
|
||||
# plural_class_name(Highrise::Person) # => "highrise_people"
|
||||
def plural_class_name(record_or_class)
|
||||
model_name_from_record_or_class(record_or_class).plural
|
||||
end
|
||||
|
||||
# Returns the singular class name of a record or class. Examples:
|
||||
#
|
||||
# singular_class_name(post) # => "post"
|
||||
# singular_class_name(Highrise::Person) # => "highrise_person"
|
||||
def singular_class_name(record_or_class)
|
||||
model_name_from_record_or_class(record_or_class).singular
|
||||
end
|
||||
|
||||
private
|
||||
def model_name_from_record_or_class(record_or_class)
|
||||
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
|
||||
end
|
||||
end
|
||||
end
|
||||
872
vendor/rails/actionpack/lib/action_controller/request.rb
vendored
Executable file
872
vendor/rails/actionpack/lib/action_controller/request.rb
vendored
Executable file
|
|
@ -0,0 +1,872 @@
|
|||
require 'tempfile'
|
||||
require 'stringio'
|
||||
require 'strscan'
|
||||
|
||||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
# CgiRequest and TestRequest provide concrete implementations.
|
||||
class AbstractRequest
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def self.relative_url_root=(relative_url_root)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
|
||||
"You can now set it with config.action_controller.relative_url_root=", caller)
|
||||
ActionController::Base.relative_url_root=relative_url_root
|
||||
end
|
||||
|
||||
HTTP_METHODS = %w(get head put post delete options)
|
||||
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
|
||||
|
||||
# The hash of environment variables for this request,
|
||||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
attr_reader :env
|
||||
|
||||
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
|
||||
def request_method
|
||||
method = @env['REQUEST_METHOD']
|
||||
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
|
||||
|
||||
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
||||
end
|
||||
memoize :request_method
|
||||
|
||||
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
|
||||
# equivalent from the application's perspective.
|
||||
def method
|
||||
request_method == :head ? :get : request_method
|
||||
end
|
||||
|
||||
# Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
|
||||
def get?
|
||||
method == :get
|
||||
end
|
||||
|
||||
# Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
|
||||
def post?
|
||||
request_method == :post
|
||||
end
|
||||
|
||||
# Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
|
||||
def put?
|
||||
request_method == :put
|
||||
end
|
||||
|
||||
# Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
|
||||
def delete?
|
||||
request_method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
|
||||
# this \method checks the actual HTTP \method directly.
|
||||
def head?
|
||||
request_method == :head
|
||||
end
|
||||
|
||||
# Provides access to the request's HTTP headers, for example:
|
||||
#
|
||||
# request.headers["Content-Type"] # => "text/plain"
|
||||
def headers
|
||||
ActionController::Http::Headers.new(@env)
|
||||
end
|
||||
memoize :headers
|
||||
|
||||
# Returns the content length of the request as an integer.
|
||||
def content_length
|
||||
@env['CONTENT_LENGTH'].to_i
|
||||
end
|
||||
memoize :content_length
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post \format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def content_type
|
||||
Mime::Type.lookup(content_type_without_parameters)
|
||||
end
|
||||
memoize :content_type
|
||||
|
||||
# Returns the accepted MIME type for the request.
|
||||
def accepts
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
|
||||
if header.empty?
|
||||
[content_type, Mime::ALL].compact
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
memoize :accepts
|
||||
|
||||
def if_modified_since
|
||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
memoize :if_modified_since
|
||||
|
||||
def if_none_match
|
||||
env['HTTP_IF_NONE_MATCH']
|
||||
end
|
||||
|
||||
def not_modified?(modified_at)
|
||||
if_modified_since && modified_at && if_modified_since >= modified_at
|
||||
end
|
||||
|
||||
def etag_matches?(etag)
|
||||
if_none_match && if_none_match == etag
|
||||
end
|
||||
|
||||
# Check response freshness (Last-Modified and ETag) against request
|
||||
# If-Modified-Since and If-None-Match conditions. If both headers are
|
||||
# supplied, both must match, or the request is not considered fresh.
|
||||
def fresh?(response)
|
||||
case
|
||||
when if_modified_since && if_none_match
|
||||
not_modified?(response.last_modified) && etag_matches?(response.etag)
|
||||
when if_modified_since
|
||||
not_modified?(response.last_modified)
|
||||
when if_none_match
|
||||
etag_matches?(response.etag)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Mime type for the \format used in the request.
|
||||
#
|
||||
# GET /posts/5.xml | request.format => Mime::XML
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
|
||||
def format
|
||||
@format ||=
|
||||
if parameters[:format]
|
||||
Mime::Type.lookup_by_extension(parameters[:format])
|
||||
elsif ActionController::Base.use_accept_header
|
||||
accepts.first
|
||||
elsif xhr?
|
||||
Mime::Type.lookup_by_extension("js")
|
||||
else
|
||||
Mime::Type.lookup_by_extension("html")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Sets the \format by string extension, which can be used to force custom formats
|
||||
# that are not controlled by the extension.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
# end
|
||||
# end
|
||||
def format=(extension)
|
||||
parameters[:format] = extension.to_s
|
||||
@format = Mime::Type.lookup_by_extension(parameters[:format])
|
||||
end
|
||||
|
||||
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
|
||||
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
|
||||
# otherwise.
|
||||
def template_format
|
||||
parameter_format = parameters[:format]
|
||||
|
||||
if parameter_format
|
||||
parameter_format
|
||||
elsif xhr?
|
||||
:js
|
||||
else
|
||||
:html
|
||||
end
|
||||
end
|
||||
|
||||
def cache_format
|
||||
parameters[:format]
|
||||
end
|
||||
|
||||
# Returns true if the request's "X-Requested-With" header contains
|
||||
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
|
||||
# every Ajax request.)
|
||||
def xml_http_request?
|
||||
!(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
|
||||
end
|
||||
alias xhr? :xml_http_request?
|
||||
|
||||
# Which IP addresses are "trusted proxies" that can be stripped from
|
||||
# the right-hand-side of X-Forwarded-For
|
||||
TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
|
||||
|
||||
# Determines originating IP address. REMOTE_ADDR is the standard
|
||||
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
|
||||
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
|
||||
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
# delimited list in the case of multiple chained proxies; the last
|
||||
# address which is not trusted is the originating IP.
|
||||
def remote_ip
|
||||
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
|
||||
|
||||
unless remote_addr_list.blank?
|
||||
not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
|
||||
return not_trusted_addrs.first unless not_trusted_addrs.empty?
|
||||
end
|
||||
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
|
||||
|
||||
if @env.include? 'HTTP_CLIENT_IP'
|
||||
if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
|
||||
# We don't know which came from the proxy, and which from the user
|
||||
raise ActionControllerError.new(<<EOM)
|
||||
IP spoofing attack?!
|
||||
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
|
||||
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
|
||||
EOM
|
||||
end
|
||||
|
||||
return @env['HTTP_CLIENT_IP']
|
||||
end
|
||||
|
||||
if remote_ips
|
||||
while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
|
||||
remote_ips.pop
|
||||
end
|
||||
|
||||
return remote_ips.last.strip
|
||||
end
|
||||
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
memoize :remote_ip
|
||||
|
||||
# Returns the lowercase name of the HTTP server software.
|
||||
def server_software
|
||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
memoize :server_software
|
||||
|
||||
|
||||
# Returns the complete URL used for this request.
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
memoize :url
|
||||
|
||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
def protocol
|
||||
ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
memoize :protocol
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
# Returns the \host for this request, such as "example.com".
|
||||
def raw_host_with_port
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
else
|
||||
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
raw_host_with_port.sub(/:\d+$/, '')
|
||||
end
|
||||
memoize :host
|
||||
|
||||
# Returns a \host:\port string for this request, such as "example.com" or
|
||||
# "example.com:8080".
|
||||
def host_with_port
|
||||
"#{host}#{port_string}"
|
||||
end
|
||||
memoize :host_with_port
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
def port
|
||||
if raw_host_with_port =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
memoize :port
|
||||
|
||||
# Returns the standard \port number for this request's protocol.
|
||||
def standard_port
|
||||
case protocol
|
||||
when 'https://' then 443
|
||||
else 80
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a \port suffix like ":8080" if the \port number of this request
|
||||
# is not the default HTTP \port 80 or HTTPS \port 443.
|
||||
def port_string
|
||||
port == standard_port ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil unless named_host?(host)
|
||||
|
||||
host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless named_host?(host)
|
||||
parts = host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
if uri = @env['REQUEST_URI']
|
||||
uri.split('?', 2)[1] || ''
|
||||
else
|
||||
@env['QUERY_STRING'] || ''
|
||||
end
|
||||
end
|
||||
memoize :query_string
|
||||
|
||||
# Returns the request URI, accounting for server idiosyncrasies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
uri = @env['PATH_INFO'].to_s
|
||||
|
||||
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = uri.sub(/#{script_filename}\//, '')
|
||||
end
|
||||
|
||||
env_qs = @env['QUERY_STRING'].to_s
|
||||
uri += "?#{env_qs}" unless env_qs.empty?
|
||||
|
||||
if uri.blank?
|
||||
@env.delete('REQUEST_URI')
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
memoize :request_uri
|
||||
|
||||
# Returns the interpreted \path to requested resource after all the installation
|
||||
# directory of this application was taken into account.
|
||||
def path
|
||||
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
memoize :path
|
||||
|
||||
# Read the request \body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
def raw_post
|
||||
unless env.include? 'RAW_POST_DATA'
|
||||
env['RAW_POST_DATA'] = body.read(content_length)
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
end
|
||||
env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns both GET and POST \parameters in a single hash.
|
||||
def parameters
|
||||
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
@path_parameters = parameters
|
||||
@symbolized_path_parameters = @parameters = nil
|
||||
end
|
||||
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
|
||||
def symbolized_path_parameters
|
||||
@symbolized_path_parameters ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the \parameters used to form the \path of the request.
|
||||
# Returned hash keys are strings:
|
||||
#
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@path_parameters ||= {}
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
body_stream
|
||||
end
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def referrer
|
||||
@env['HTTP_REFERER']
|
||||
end
|
||||
alias referer referrer
|
||||
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
|
||||
#--
|
||||
# Must be implemented in the concrete request
|
||||
#++
|
||||
|
||||
def body_stream #:nodoc:
|
||||
end
|
||||
|
||||
def cookies #:nodoc:
|
||||
end
|
||||
|
||||
def session #:nodoc:
|
||||
end
|
||||
|
||||
def session=(session) #:nodoc:
|
||||
@session = session
|
||||
end
|
||||
|
||||
def reset_session #:nodoc:
|
||||
end
|
||||
|
||||
protected
|
||||
# The raw content type string. Use when you need parameters such as
|
||||
# charset or boundary which aren't included in the content_type MIME type.
|
||||
# Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
|
||||
def content_type_with_parameters
|
||||
content_type_from_legacy_post_data_format_header ||
|
||||
env['CONTENT_TYPE'].to_s
|
||||
end
|
||||
|
||||
# The raw content type string with its parameters stripped off.
|
||||
def content_type_without_parameters
|
||||
self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
memoize :content_type_without_parameters
|
||||
|
||||
private
|
||||
def content_type_from_legacy_post_data_format_header
|
||||
if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
|
||||
case x_post_format.to_s.downcase
|
||||
when 'yaml'; 'application/x-yaml'
|
||||
when 'xml'; 'application/xml'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_formatted_request_parameters
|
||||
return {} if content_length.zero?
|
||||
|
||||
content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
|
||||
|
||||
# Don't parse params for unknown requests.
|
||||
return {} if content_type.blank?
|
||||
|
||||
mime_type = Mime::Type.lookup(content_type)
|
||||
strategy = ActionController::Base.param_parsers[mime_type]
|
||||
|
||||
# Only multipart form parsing expects a stream.
|
||||
body = (strategy && strategy != :multipart_form) ? raw_post : self.body
|
||||
|
||||
case strategy
|
||||
when Proc
|
||||
strategy.call(body)
|
||||
when :url_encoded_form
|
||||
self.class.clean_up_ajax_request_body! body
|
||||
self.class.parse_query_parameters(body)
|
||||
when :multipart_form
|
||||
self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
|
||||
when :xml_simple, :xml_node
|
||||
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
|
||||
when :yaml
|
||||
YAML.load(body)
|
||||
when :json
|
||||
if body.blank?
|
||||
{}
|
||||
else
|
||||
data = ActiveSupport::JSON.decode(body)
|
||||
data = {:_json => data} unless data.is_a?(Hash)
|
||||
data.with_indifferent_access
|
||||
end
|
||||
else
|
||||
{}
|
||||
end
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
raise
|
||||
{ "body" => body,
|
||||
"content_type" => content_type_with_parameters,
|
||||
"content_length" => content_length,
|
||||
"exception" => "#{e.message} (#{e.class})",
|
||||
"backtrace" => e.backtrace }
|
||||
end
|
||||
|
||||
def named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse_query_parameters(query_string)
|
||||
return {} if query_string.blank?
|
||||
|
||||
pairs = query_string.split('&').collect do |chunk|
|
||||
next if chunk.empty?
|
||||
key, value = chunk.split('=', 2)
|
||||
next if key.empty?
|
||||
value = value.nil? ? nil : CGI.unescape(value)
|
||||
[ CGI.unescape(key), value ]
|
||||
end.compact
|
||||
|
||||
UrlEncodedPairParser.new(pairs).result
|
||||
end
|
||||
|
||||
def parse_request_parameters(params)
|
||||
parser = UrlEncodedPairParser.new
|
||||
|
||||
params = params.dup
|
||||
until params.empty?
|
||||
for key, value in params
|
||||
if key.blank?
|
||||
params.delete key
|
||||
elsif !key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parser.result[key] = get_typed_value(value[0])
|
||||
params.delete key
|
||||
elsif value.is_a?(Array)
|
||||
parser.parse(key, get_typed_value(value.shift))
|
||||
params.delete key if value.empty?
|
||||
else
|
||||
raise TypeError, "Expected array, found #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parser.result
|
||||
end
|
||||
|
||||
def parse_multipart_form_parameters(body, boundary, body_size, env)
|
||||
parse_request_parameters(read_multipart(body, boundary, body_size, env))
|
||||
end
|
||||
|
||||
def extract_multipart_boundary(content_type_with_parameters)
|
||||
if content_type_with_parameters =~ MULTIPART_BOUNDARY
|
||||
['multipart/form-data', $1.dup]
|
||||
else
|
||||
extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_content_type_without_parameters(content_type_with_parameters)
|
||||
$1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
|
||||
end
|
||||
|
||||
def clean_up_ajax_request_body!(body)
|
||||
body.chop! if body[-1] == 0
|
||||
body.gsub!(/&_=$/, '')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def get_typed_value(value)
|
||||
case value
|
||||
when String
|
||||
value
|
||||
when NilClass
|
||||
''
|
||||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
if value.respond_to? :original_filename
|
||||
# Uploaded file
|
||||
if value.original_filename
|
||||
value
|
||||
# Multipart param
|
||||
else
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
end
|
||||
# Unknown value, neither string nor multipart.
|
||||
else
|
||||
raise "Unknown form value: #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
||||
|
||||
EOL = "\015\012"
|
||||
|
||||
def read_multipart(body, boundary, body_size, env)
|
||||
params = Hash.new([])
|
||||
boundary = "--" + boundary
|
||||
quoted_boundary = Regexp.quote(boundary)
|
||||
buf = ""
|
||||
bufsize = 10 * 1024
|
||||
boundary_end=""
|
||||
|
||||
# start multipart/form-data
|
||||
body.binmode if defined? body.binmode
|
||||
case body
|
||||
when File
|
||||
body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
|
||||
when StringIO
|
||||
body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
|
||||
end
|
||||
boundary_size = boundary.size + EOL.size
|
||||
body_size -= boundary_size
|
||||
status = body.read(boundary_size)
|
||||
if nil == status
|
||||
raise EOFError, "no content body"
|
||||
elsif boundary + EOL != status
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
|
||||
loop do
|
||||
head = nil
|
||||
content =
|
||||
if 10240 < body_size
|
||||
UploadedTempfile.new("CGI")
|
||||
else
|
||||
UploadedStringIO.new
|
||||
end
|
||||
content.binmode if defined? content.binmode
|
||||
|
||||
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
|
||||
|
||||
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
||||
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
||||
head = $1.dup
|
||||
""
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
if head and ( (EOL + boundary + EOL).size < buf.size )
|
||||
content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
||||
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
||||
end
|
||||
|
||||
c = if bufsize < body_size
|
||||
body.read(bufsize)
|
||||
else
|
||||
body.read(body_size)
|
||||
end
|
||||
if c.nil? || c.empty?
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
buf.concat(c)
|
||||
body_size -= c.size
|
||||
end
|
||||
|
||||
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
||||
content.print $1
|
||||
if "--" == $2
|
||||
body_size = -1
|
||||
end
|
||||
boundary_end = $2.dup
|
||||
""
|
||||
end
|
||||
|
||||
content.rewind
|
||||
|
||||
head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
|
||||
if filename = $1 || $2
|
||||
if /Mac/ni.match(env['HTTP_USER_AGENT']) and
|
||||
/Mozilla/ni.match(env['HTTP_USER_AGENT']) and
|
||||
(not /MSIE/ni.match(env['HTTP_USER_AGENT']))
|
||||
filename = CGI.unescape(filename)
|
||||
end
|
||||
content.original_path = filename.dup
|
||||
end
|
||||
|
||||
head =~ /Content-Type: ([^\r]*)/ni
|
||||
content.content_type = $1.dup if $1
|
||||
|
||||
head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
|
||||
name = $1.dup if $1
|
||||
|
||||
if params.has_key?(name)
|
||||
params[name].push(content)
|
||||
else
|
||||
params[name] = [content]
|
||||
end
|
||||
break if body_size == -1
|
||||
end
|
||||
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
||||
|
||||
begin
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
rescue Errno::ESPIPE
|
||||
# Handles exceptions raised by input streams that cannot be rewound
|
||||
# such as when using plain CGI under Apache
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UrlEncodedPairParser < StringScanner #:nodoc:
|
||||
attr_reader :top, :parent, :result
|
||||
|
||||
def initialize(pairs = [])
|
||||
super('')
|
||||
@result = {}
|
||||
pairs.each { |key, value| parse(key, value) }
|
||||
end
|
||||
|
||||
KEY_REGEXP = %r{([^\[\]=&]+)}
|
||||
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
||||
|
||||
# Parse the query string
|
||||
def parse(key, value)
|
||||
self.string = key
|
||||
@top, @parent = result, nil
|
||||
|
||||
# First scan the bare key
|
||||
key = scan(KEY_REGEXP) or return
|
||||
key = post_key_check(key)
|
||||
|
||||
# Then scan as many nestings as present
|
||||
until eos?
|
||||
r = scan(BRACKETED_KEY_REGEXP) or return
|
||||
key = self[1]
|
||||
key = post_key_check(key)
|
||||
end
|
||||
|
||||
bind(key, value)
|
||||
end
|
||||
|
||||
private
|
||||
# After we see a key, we must look ahead to determine our next action. Cases:
|
||||
#
|
||||
# [] follows the key. Then the value must be an array.
|
||||
# = follows the key. (A value comes next)
|
||||
# & or the end of string follows the key. Then the key is a flag.
|
||||
# otherwise, a hash follows the key.
|
||||
def post_key_check(key)
|
||||
if scan(/\[\]/) # a[b][] indicates that b is an array
|
||||
container(key, Array)
|
||||
nil
|
||||
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
||||
container(key, Hash)
|
||||
nil
|
||||
else # End of key? We do nothing.
|
||||
key
|
||||
end
|
||||
end
|
||||
|
||||
# Add a container to the stack.
|
||||
def container(key, klass)
|
||||
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
||||
value = bind(key, klass.new)
|
||||
type_conflict! klass, value unless value.is_a?(klass)
|
||||
push(value)
|
||||
end
|
||||
|
||||
# Push a value onto the 'stack', which is actually only the top 2 items.
|
||||
def push(value)
|
||||
@parent, @top = @top, value
|
||||
end
|
||||
|
||||
# Bind a key (which may be nil for items in an array) to the provided value.
|
||||
def bind(key, value)
|
||||
if top.is_a? Array
|
||||
if key
|
||||
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
||||
top[-1][key] = value
|
||||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
value = top[key]
|
||||
end
|
||||
else
|
||||
top << value
|
||||
end
|
||||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
||||
top[key] ||= value
|
||||
return top[key]
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
||||
end
|
||||
end
|
||||
|
||||
module UploadedFile
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
attr_accessor :original_path, :content_type
|
||||
alias_method :local_path, :path
|
||||
end
|
||||
end
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
unless defined? @original_filename
|
||||
@original_filename =
|
||||
unless original_path.blank?
|
||||
if original_path =~ /^(?:.*[:\\\/])?(.*)/m
|
||||
$1
|
||||
else
|
||||
File.basename original_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@original_filename
|
||||
end
|
||||
end
|
||||
|
||||
class UploadedStringIO < StringIO
|
||||
include UploadedFile
|
||||
end
|
||||
|
||||
class UploadedTempfile < Tempfile
|
||||
include UploadedFile
|
||||
end
|
||||
end
|
||||
140
vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb
vendored
Normal file
140
vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
module ActionController #:nodoc:
|
||||
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
module RequestForgeryProtection
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
class_inheritable_accessor :request_forgery_protection_options
|
||||
self.request_forgery_protection_options = {}
|
||||
helper_method :form_authenticity_token
|
||||
helper_method :protect_against_forgery?
|
||||
end
|
||||
base.extend(ClassMethods)
|
||||
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 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 <tt>protect_from_forgery</tt> 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 <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
|
||||
# use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and
|
||||
# set its value to what is returned by <tt>form_authenticity_token</tt>. 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
|
||||
# # uses the cookie session store (then you don't need a separate :secret)
|
||||
# protect_from_forgery :except => :index
|
||||
#
|
||||
# # uses one of the other session stores that uses a session_id value.
|
||||
# protect_from_forgery :secret => 'my-little-pony', :except => :index
|
||||
#
|
||||
# # you can disable csrf protection on controller-by-controller basis:
|
||||
# skip_before_filter :verify_authenticity_token
|
||||
# end
|
||||
#
|
||||
# Valid Options:
|
||||
#
|
||||
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
|
||||
# * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
|
||||
# Leave this off if you are using the cookie session store.
|
||||
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
|
||||
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)
|
||||
request_forgery_protection_options.update(options)
|
||||
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 and AJAX 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 ||
|
||||
!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. Pass a <tt>:secret</tt> option
|
||||
# in +protect_from_forgery+ to add a custom salt to the hash.
|
||||
def form_authenticity_token
|
||||
@form_authenticity_token ||= if !session.respond_to?(:session_id)
|
||||
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
||||
elsif request_forgery_protection_options[:secret]
|
||||
authenticity_token_from_session_id
|
||||
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
||||
authenticity_token_from_cookie_session
|
||||
else
|
||||
raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
||||
end
|
||||
end
|
||||
|
||||
# Generates a unique digest using the session_id and the CSRF secret.
|
||||
def authenticity_token_from_session_id
|
||||
key = if request_forgery_protection_options[:secret].respond_to?(:call)
|
||||
request_forgery_protection_options[:secret].call(@session)
|
||||
else
|
||||
request_forgery_protection_options[:secret]
|
||||
end
|
||||
digest = request_forgery_protection_options[:digest] ||= 'SHA1'
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
|
||||
end
|
||||
|
||||
# No secret was given, so assume this is a cookie session store.
|
||||
def authenticity_token_from_cookie_session
|
||||
session[:csrf_id] ||= CGI::Session.generate_unique_id
|
||||
session.dbman.generate_digest(session[:csrf_id])
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
allow_forgery_protection && request_forgery_protection_token
|
||||
end
|
||||
end
|
||||
end
|
||||
169
vendor/rails/actionpack/lib/action_controller/request_profiler.rb
vendored
Normal file
169
vendor/rails/actionpack/lib/action_controller/request_profiler.rb
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
require 'optparse'
|
||||
require 'action_controller/integration'
|
||||
|
||||
module ActionController
|
||||
class RequestProfiler
|
||||
# Wrap up the integration session runner.
|
||||
class Sandbox
|
||||
include Integration::Runner
|
||||
|
||||
def self.benchmark(n, script)
|
||||
new(script).benchmark(n)
|
||||
end
|
||||
|
||||
def initialize(script_path)
|
||||
@quiet = false
|
||||
define_run_method(script_path)
|
||||
reset!
|
||||
end
|
||||
|
||||
def benchmark(n, profiling = false)
|
||||
@quiet = true
|
||||
print ' '
|
||||
|
||||
result = Benchmark.realtime do
|
||||
n.times do |i|
|
||||
run(profiling)
|
||||
print_progress(i)
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
result
|
||||
ensure
|
||||
@quiet = false
|
||||
end
|
||||
|
||||
def say(message)
|
||||
puts " #{message}" unless @quiet
|
||||
end
|
||||
|
||||
private
|
||||
def define_run_method(script_path)
|
||||
script = File.read(script_path)
|
||||
|
||||
source = <<-end_source
|
||||
def run(profiling = false)
|
||||
if profiling
|
||||
RubyProf.resume do
|
||||
#{script}
|
||||
end
|
||||
else
|
||||
#{script}
|
||||
end
|
||||
|
||||
old_request_count = request_count
|
||||
reset!
|
||||
self.request_count = old_request_count
|
||||
end
|
||||
end_source
|
||||
|
||||
instance_eval source, script_path, 1
|
||||
end
|
||||
|
||||
def print_progress(i)
|
||||
print "\n " if i % 60 == 0
|
||||
print ' ' if i % 10 == 0
|
||||
print '.'
|
||||
$stdout.flush
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options = {})
|
||||
@options = default_options.merge(options)
|
||||
end
|
||||
|
||||
|
||||
def self.run(args = nil, options = {})
|
||||
profiler = new(options)
|
||||
profiler.parse_options(args) if args
|
||||
profiler.run
|
||||
end
|
||||
|
||||
def run
|
||||
sandbox = Sandbox.new(options[:script])
|
||||
|
||||
puts 'Warming up once'
|
||||
|
||||
elapsed = warmup(sandbox)
|
||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
|
||||
puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
|
||||
|
||||
options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
|
||||
end
|
||||
|
||||
def profile(sandbox)
|
||||
load_ruby_prof
|
||||
|
||||
benchmark(sandbox, true)
|
||||
results = RubyProf.stop
|
||||
|
||||
show_profile_results results
|
||||
results
|
||||
end
|
||||
|
||||
def benchmark(sandbox, profiling = false)
|
||||
sandbox.request_count = 0
|
||||
elapsed = sandbox.benchmark(options[:n], profiling).to_f
|
||||
count = sandbox.request_count.to_i
|
||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
|
||||
end
|
||||
|
||||
def warmup(sandbox)
|
||||
Benchmark.realtime { sandbox.run(false) }
|
||||
end
|
||||
|
||||
def default_options
|
||||
{ :n => 100, :open => 'open %s &' }
|
||||
end
|
||||
|
||||
# Parse command-line options
|
||||
def parse_options(args)
|
||||
OptionParser.new do |opt|
|
||||
opt.banner = "USAGE: #{$0} [options] [session script path]"
|
||||
|
||||
opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
|
||||
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
|
||||
opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
|
||||
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
|
||||
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
|
||||
|
||||
opt.parse args
|
||||
|
||||
if args.empty?
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
options[:script] = args.pop
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def load_ruby_prof
|
||||
begin
|
||||
gem 'ruby-prof', '>= 0.6.1'
|
||||
require 'ruby-prof'
|
||||
if mode = options[:measure]
|
||||
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
||||
end
|
||||
rescue LoadError
|
||||
abort '`gem install ruby-prof` to use the profiler'
|
||||
end
|
||||
end
|
||||
|
||||
def show_profile_results(results)
|
||||
File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
|
||||
RubyProf::GraphHtmlPrinter.new(results).print(file)
|
||||
`#{options[:open] % file.path}` if options[:open]
|
||||
end
|
||||
|
||||
File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
|
||||
RubyProf::FlatPrinter.new(results).print(file)
|
||||
`#{options[:open] % file.path}` if options[:open]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
163
vendor/rails/actionpack/lib/action_controller/rescue.rb
vendored
Normal file
163
vendor/rails/actionpack/lib/action_controller/rescue.rb
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
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 <tt>local_request?</tt> method in your own controller.
|
||||
# Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
|
||||
module Rescue
|
||||
LOCALHOST = '127.0.0.1'.freeze
|
||||
|
||||
DEFAULT_RESCUE_RESPONSE = :internal_server_error
|
||||
DEFAULT_RESCUE_RESPONSES = {
|
||||
'ActionController::RoutingError' => :not_found,
|
||||
'ActionController::UnknownAction' => :not_found,
|
||||
'ActiveRecord::RecordNotFound' => :not_found,
|
||||
'ActiveRecord::StaleObjectError' => :conflict,
|
||||
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
|
||||
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
|
||||
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
||||
'ActionController::NotImplemented' => :not_implemented,
|
||||
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
|
||||
}
|
||||
|
||||
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
|
||||
DEFAULT_RESCUE_TEMPLATES = {
|
||||
'ActionView::MissingTemplate' => 'missing_template',
|
||||
'ActionController::RoutingError' => 'routing_error',
|
||||
'ActionController::UnknownAction' => 'unknown_action',
|
||||
'ActionView::TemplateError' => 'template_error'
|
||||
}
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.cattr_accessor :rescue_responses
|
||||
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
||||
base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
|
||||
|
||||
base.cattr_accessor :rescue_templates
|
||||
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
|
||||
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
|
||||
|
||||
base.extend(ClassMethods)
|
||||
base.send :include, ActiveSupport::Rescuable
|
||||
|
||||
base.class_eval do
|
||||
alias_method_chain :perform_action, :rescue
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def process_with_exception(request, response, exception) #:nodoc:
|
||||
new.process(request, response, :rescue_action, exception)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Exception handler called when the performance of an action raises an exception.
|
||||
def rescue_action(exception)
|
||||
rescue_with_handler(exception) || rescue_action_without_handler(exception)
|
||||
end
|
||||
|
||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
||||
def log_error(exception) #:doc:
|
||||
ActiveSupport::Deprecation.silence do
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
|
||||
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.
|
||||
def rescue_action_in_public(exception) #:doc:
|
||||
render_optional_error_file response_code_for_rescue(exception)
|
||||
end
|
||||
|
||||
# Attempts to render a static error page based on the <tt>status_code</tt> thrown,
|
||||
# or just return headers if no such file exists. For example, if a 500 error is
|
||||
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
|
||||
# If the file doesn't exist, the body of the response will be left empty.
|
||||
def render_optional_error_file(status_code)
|
||||
status = interpret_status(status_code)
|
||||
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
||||
if File.exist?(path)
|
||||
render :file => path, :status => status
|
||||
else
|
||||
head status
|
||||
end
|
||||
end
|
||||
|
||||
# True if the request came from localhost, 127.0.0.1. Override this
|
||||
# method if you wish to redefine the meaning of a local request to
|
||||
# include remote IP addresses or other criteria.
|
||||
def local_request? #:doc:
|
||||
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
|
||||
end
|
||||
|
||||
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||
# a controller action.
|
||||
def rescue_action_locally(exception)
|
||||
@template.instance_variable_set("@exception", exception)
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
|
||||
|
||||
response.content_type = Mime::HTML
|
||||
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
end
|
||||
|
||||
def rescue_action_without_handler(exception)
|
||||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
else
|
||||
rescue_action_in_public(exception)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_action_with_rescue #:nodoc:
|
||||
perform_action_without_rescue
|
||||
rescue Exception => exception
|
||||
rescue_action(exception)
|
||||
end
|
||||
|
||||
def rescues_path(template_name)
|
||||
"#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
|
||||
end
|
||||
|
||||
def template_path_for_local_rescue(exception)
|
||||
rescues_path(rescue_templates[exception.class.name])
|
||||
end
|
||||
|
||||
def response_code_for_rescue(exception)
|
||||
rescue_responses[exception.class.name]
|
||||
end
|
||||
|
||||
def clean_backtrace(exception)
|
||||
if backtrace = exception.backtrace
|
||||
if defined?(RAILS_ROOT)
|
||||
backtrace.map { |line| line.sub RAILS_ROOT, '' }
|
||||
else
|
||||
backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
675
vendor/rails/actionpack/lib/action_controller/resources.rb
vendored
Normal file
675
vendor/rails/actionpack/lib/action_controller/resources.rb
vendored
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
module ActionController
|
||||
# == Overview
|
||||
#
|
||||
# ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
|
||||
# is something that can be pointed at and it will respond with a representation of the data requested.
|
||||
# In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
|
||||
# requests XML data.
|
||||
#
|
||||
# RESTful design is based on the assumption that there are four generic verbs that a user of an
|
||||
# application can request from a \resource (the noun).
|
||||
#
|
||||
# \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
|
||||
# denotes the type of action that should take place.
|
||||
#
|
||||
# === The Different Methods and their Usage
|
||||
#
|
||||
# * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
|
||||
# * POST - Creation of \resources.
|
||||
# * PUT - Editing of attributes on a \resource.
|
||||
# * DELETE - Deletion of a \resource.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# # A GET request on the Posts resource is asking for all Posts
|
||||
# GET /posts
|
||||
#
|
||||
# # A GET request on a single Post resource is asking for that particular Post
|
||||
# GET /posts/1
|
||||
#
|
||||
# # A POST request on the Posts resource is asking for a Post to be created with the supplied details
|
||||
# POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
|
||||
#
|
||||
# # A PUT request on a single Post resource is asking for a Post to be updated
|
||||
# PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
|
||||
#
|
||||
# # A DELETE request on a single Post resource is asking for it to be deleted
|
||||
# DELETE /posts # with => { :id => 1 }
|
||||
#
|
||||
# By using the REST convention, users of our application can assume certain things about how the data
|
||||
# is requested and how it is returned. Rails simplifies the routing part of RESTful design by
|
||||
# supplying you with methods to create them in your routes.rb file.
|
||||
#
|
||||
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
module Resources
|
||||
INHERITABLE_OPTIONS = :namespace, :shallow, :actions
|
||||
|
||||
class Resource #:nodoc:
|
||||
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
|
||||
|
||||
attr_reader :collection_methods, :member_methods, :new_methods
|
||||
attr_reader :path_prefix, :name_prefix, :path_segment
|
||||
attr_reader :plural, :singular
|
||||
attr_reader :options
|
||||
|
||||
def initialize(entities, options)
|
||||
@plural ||= entities
|
||||
@singular ||= options[:singular] || plural.to_s.singularize
|
||||
@path_segment = options.delete(:as) || @plural
|
||||
|
||||
@options = options
|
||||
|
||||
arrange_actions
|
||||
add_default_actions
|
||||
set_allowed_actions
|
||||
set_prefixes
|
||||
end
|
||||
|
||||
def controller
|
||||
@controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
|
||||
end
|
||||
|
||||
def requirements(with_id = false)
|
||||
@requirements ||= @options[:requirements] || {}
|
||||
@id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
|
||||
|
||||
with_id ? @requirements.merge(@id_requirement) : @requirements
|
||||
end
|
||||
|
||||
def conditions
|
||||
@conditions ||= @options[:conditions] || {}
|
||||
end
|
||||
|
||||
def path
|
||||
@path ||= "#{path_prefix}/#{path_segment}"
|
||||
end
|
||||
|
||||
def new_path
|
||||
new_action = self.options[:path_names][:new] if self.options[:path_names]
|
||||
new_action ||= Base.resources_path_names[:new]
|
||||
@new_path ||= "#{path}/#{new_action}"
|
||||
end
|
||||
|
||||
def shallow_path_prefix
|
||||
@shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
|
||||
end
|
||||
|
||||
def member_path
|
||||
@member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
|
||||
end
|
||||
|
||||
def nesting_path_prefix
|
||||
@nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
|
||||
end
|
||||
|
||||
def shallow_name_prefix
|
||||
@shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
|
||||
end
|
||||
|
||||
def nesting_name_prefix
|
||||
"#{shallow_name_prefix}#{singular}_"
|
||||
end
|
||||
|
||||
def action_separator
|
||||
@action_separator ||= Base.resource_action_separator
|
||||
end
|
||||
|
||||
def uncountable?
|
||||
@singular.to_s == @plural.to_s
|
||||
end
|
||||
|
||||
def has_action?(action)
|
||||
!DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action)
|
||||
end
|
||||
|
||||
protected
|
||||
def arrange_actions
|
||||
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
|
||||
@member_methods = arrange_actions_by_methods(options.delete(:member))
|
||||
@new_methods = arrange_actions_by_methods(options.delete(:new))
|
||||
end
|
||||
|
||||
def add_default_actions
|
||||
add_default_action(member_methods, :get, :edit)
|
||||
add_default_action(new_methods, :get, :new)
|
||||
end
|
||||
|
||||
def set_allowed_actions
|
||||
only = @options.delete(:only)
|
||||
except = @options.delete(:except)
|
||||
|
||||
if only && except
|
||||
raise ArgumentError, 'Please supply either :only or :except, not both.'
|
||||
elsif only == :all || except == :none
|
||||
options[:actions] = DEFAULT_ACTIONS
|
||||
elsif only == :none || except == :all
|
||||
options[:actions] = []
|
||||
elsif only
|
||||
options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym)
|
||||
elsif except
|
||||
options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym)
|
||||
else
|
||||
# leave options[:actions] alone
|
||||
end
|
||||
end
|
||||
|
||||
def set_prefixes
|
||||
@path_prefix = options.delete(:path_prefix)
|
||||
@name_prefix = options.delete(:name_prefix)
|
||||
end
|
||||
|
||||
def arrange_actions_by_methods(actions)
|
||||
(actions || {}).inject({}) do |flipped_hash, (key, value)|
|
||||
(flipped_hash[value] ||= []) << key
|
||||
flipped_hash
|
||||
end
|
||||
end
|
||||
|
||||
def add_default_action(collection, method, action)
|
||||
(collection[method] ||= []).unshift(action)
|
||||
end
|
||||
end
|
||||
|
||||
class SingletonResource < Resource #:nodoc:
|
||||
def initialize(entity, options)
|
||||
@singular = @plural = entity
|
||||
options[:controller] ||= @singular.to_s.pluralize
|
||||
super
|
||||
end
|
||||
|
||||
alias_method :shallow_path_prefix, :path_prefix
|
||||
alias_method :shallow_name_prefix, :name_prefix
|
||||
alias_method :member_path, :path
|
||||
alias_method :nesting_path_prefix, :path
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers
|
||||
# for a collection \resource.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# map.resources :messages
|
||||
#
|
||||
# will map the following actions in the corresponding controller:
|
||||
#
|
||||
# class MessagesController < ActionController::Base
|
||||
# # GET messages_url
|
||||
# def index
|
||||
# # return all messages
|
||||
# end
|
||||
#
|
||||
# # GET new_message_url
|
||||
# def new
|
||||
# # return an HTML form for describing a new message
|
||||
# end
|
||||
#
|
||||
# # POST messages_url
|
||||
# def create
|
||||
# # create a new message
|
||||
# end
|
||||
#
|
||||
# # GET message_url(:id => 1)
|
||||
# def show
|
||||
# # find and return a specific message
|
||||
# end
|
||||
#
|
||||
# # GET edit_message_url(:id => 1)
|
||||
# def edit
|
||||
# # return an HTML form for editing a specific message
|
||||
# end
|
||||
#
|
||||
# # PUT message_url(:id => 1)
|
||||
# def update
|
||||
# # find and update a specific message
|
||||
# end
|
||||
#
|
||||
# # DELETE message_url(:id => 1)
|
||||
# def destroy
|
||||
# # delete a specific message
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Along with the routes themselves, +resources+ generates named routes for use in
|
||||
# controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
|
||||
#
|
||||
# Named Route Helpers
|
||||
# ============ =====================================================
|
||||
# messages messages_url, hash_for_messages_url,
|
||||
# messages_path, hash_for_messages_path
|
||||
#
|
||||
# message message_url(id), hash_for_message_url(id),
|
||||
# message_path(id), hash_for_message_path(id)
|
||||
#
|
||||
# new_message new_message_url, hash_for_new_message_url,
|
||||
# new_message_path, hash_for_new_message_path
|
||||
#
|
||||
# edit_message edit_message_url(id), hash_for_edit_message_url(id),
|
||||
# edit_message_path(id), hash_for_edit_message_path(id)
|
||||
#
|
||||
# You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
|
||||
#
|
||||
# redirect_to :controller => 'messages', :action => 'index'
|
||||
# # and
|
||||
# <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
|
||||
#
|
||||
# now become:
|
||||
#
|
||||
# redirect_to messages_url
|
||||
# # and
|
||||
# <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
|
||||
#
|
||||
# Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
|
||||
# form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
|
||||
#
|
||||
# <%= form_tag message_path(@message), :method => :put %>
|
||||
#
|
||||
# or
|
||||
#
|
||||
# <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
|
||||
#
|
||||
# or
|
||||
#
|
||||
# <% form_for @message do |f| %>
|
||||
#
|
||||
# which takes into account whether <tt>@message</tt> is a new record or not and generates the
|
||||
# path and method accordingly.
|
||||
#
|
||||
# The +resources+ method accepts the following options to customize the resulting routes:
|
||||
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
|
||||
# an array of any of the previous, or <tt>:any</tt> if the method does not matter.
|
||||
# These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
|
||||
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
|
||||
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
||||
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
||||
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
|
||||
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
|
||||
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
|
||||
# # products_path == '/productos'
|
||||
# map.resources :products, :as => 'productos' do |product|
|
||||
# # product_reviews_path(product) == '/productos/1234/comentarios'
|
||||
# product.resources :product_reviews, :as => 'comentarios'
|
||||
# end
|
||||
#
|
||||
# * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
|
||||
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
|
||||
#
|
||||
# You may directly specify the routing association with +has_one+ and +has_many+ like:
|
||||
#
|
||||
# map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
|
||||
#
|
||||
# This is the same as:
|
||||
#
|
||||
# map.resources :notes do |notes|
|
||||
# notes.resource :author
|
||||
# notes.resources :comments
|
||||
# notes.resources :attachments
|
||||
# end
|
||||
#
|
||||
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
|
||||
# # new_products_path == '/productos/nuevo'
|
||||
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
|
||||
#
|
||||
# You can also set default action names from an environment, like this:
|
||||
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
||||
#
|
||||
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
|
||||
#
|
||||
# Weblog comments usually belong to a post, so you might use +resources+ like:
|
||||
#
|
||||
# map.resources :articles
|
||||
# map.resources :comments, :path_prefix => '/articles/:article_id'
|
||||
#
|
||||
# You can nest +resources+ calls to set this automatically:
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments
|
||||
# end
|
||||
#
|
||||
# The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
|
||||
#
|
||||
# article_comments_url(@article)
|
||||
# article_comment_url(@article, @comment)
|
||||
#
|
||||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
|
||||
#
|
||||
# articles_comments_url(@comment.article_id, @comment)
|
||||
#
|
||||
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
|
||||
# Use this if you have named routes that may clash.
|
||||
#
|
||||
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
|
||||
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
|
||||
#
|
||||
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments, :name_prefix => nil
|
||||
# end
|
||||
#
|
||||
# This will yield named \resources like so:
|
||||
#
|
||||
# comments_url(@article)
|
||||
# comment_url(@article, @comment)
|
||||
#
|
||||
# * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
|
||||
# (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
|
||||
#
|
||||
# The <tt>:shallow</tt> option is inherited by any nested resource(s).
|
||||
#
|
||||
# For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
|
||||
#
|
||||
# map.resources :users, :shallow => true do |user|
|
||||
# user.resources :posts do |post|
|
||||
# post.resources :comments
|
||||
# end
|
||||
# end
|
||||
# # --> GET /users/1/posts (maps to the PostsController#index action as usual)
|
||||
# # also adds the usual named route called "user_posts"
|
||||
# # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
|
||||
# # also adds the named route called "post"
|
||||
# # --> GET /posts/2/comments (maps to the CommentsController#index action)
|
||||
# # also adds the named route called "post_comments"
|
||||
# # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
|
||||
# # also adds the named route called "comment"
|
||||
#
|
||||
# You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
|
||||
#
|
||||
# map.resources :users, :has_many => { :posts => :comments }, :shallow => true
|
||||
#
|
||||
# * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
|
||||
#
|
||||
# <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
|
||||
# list of action names. By default, routes are generated for all seven actions.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# map.resources :posts, :only => [:index, :show] do |post|
|
||||
# post.resources :comments, :except => [:update, :destroy]
|
||||
# end
|
||||
# # --> GET /posts (maps to the PostsController#index action)
|
||||
# # --> POST /posts (fails)
|
||||
# # --> GET /posts/1 (maps to the PostsController#show action)
|
||||
# # --> DELETE /posts/1 (fails)
|
||||
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
|
||||
# # --> PUT /posts/1/comments/1 (fails)
|
||||
#
|
||||
# The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
|
||||
#
|
||||
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# map.resources :messages, :path_prefix => "/thread/:thread_id"
|
||||
# # --> GET /thread/7/messages/1
|
||||
#
|
||||
# map.resources :messages, :collection => { :rss => :get }
|
||||
# # --> GET /messages/rss (maps to the #rss action)
|
||||
# # also adds a named route called "rss_messages"
|
||||
#
|
||||
# map.resources :messages, :member => { :mark => :post }
|
||||
# # --> POST /messages/1/mark (maps to the #mark action)
|
||||
# # also adds a named route called "mark_message"
|
||||
#
|
||||
# map.resources :messages, :new => { :preview => :post }
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
#
|
||||
# map.resources :messages, :new => { :new => :any, :preview => :post }
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
# # --> /messages/new can be invoked via any request method
|
||||
#
|
||||
# map.resources :messages, :controller => "categories",
|
||||
# :path_prefix => "/category/:category_id",
|
||||
# :name_prefix => "category_"
|
||||
# # --> GET /categories/7/messages/1
|
||||
# # has named route "category_message"
|
||||
#
|
||||
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
|
||||
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
|
||||
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
|
||||
def resources(*entities, &block)
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton \resource.
|
||||
# A singleton \resource is global to its current context. For unnested singleton \resources,
|
||||
# the \resource is global to the current user visiting the application, such as a user's
|
||||
# <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
|
||||
# \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
|
||||
# The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
|
||||
#
|
||||
# map.resources :projects do |project|
|
||||
# project.resource :project_manager
|
||||
# end
|
||||
#
|
||||
# See +resources+ for general conventions. These are the main differences:
|
||||
# * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
|
||||
# * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
|
||||
# * No default index route is created for the singleton \resource controller.
|
||||
# * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# map.resource :account
|
||||
#
|
||||
# maps these actions in the Accounts controller:
|
||||
#
|
||||
# class AccountsController < ActionController::Base
|
||||
# # GET new_account_url
|
||||
# def new
|
||||
# # return an HTML form for describing the new account
|
||||
# end
|
||||
#
|
||||
# # POST account_url
|
||||
# def create
|
||||
# # create an account
|
||||
# end
|
||||
#
|
||||
# # GET account_url
|
||||
# def show
|
||||
# # find and return the account
|
||||
# end
|
||||
#
|
||||
# # GET edit_account_url
|
||||
# def edit
|
||||
# # return an HTML form for editing the account
|
||||
# end
|
||||
#
|
||||
# # PUT account_url
|
||||
# def update
|
||||
# # find and update the account
|
||||
# end
|
||||
#
|
||||
# # DELETE account_url
|
||||
# def destroy
|
||||
# # delete the account
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Along with the routes themselves, +resource+ generates named routes for
|
||||
# use in controllers and views. <tt>map.resource :account</tt> produces
|
||||
# these named routes and helpers:
|
||||
#
|
||||
# Named Route Helpers
|
||||
# ============ =============================================
|
||||
# account account_url, hash_for_account_url,
|
||||
# account_path, hash_for_account_path
|
||||
#
|
||||
# new_account new_account_url, hash_for_new_account_url,
|
||||
# new_account_path, hash_for_new_account_path
|
||||
#
|
||||
# edit_account edit_account_url, hash_for_edit_account_url,
|
||||
# edit_account_path, hash_for_edit_account_path
|
||||
def resource(*entities, &block)
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
private
|
||||
def map_resource(entities, options = {}, &block)
|
||||
resource = Resource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
map_collection_actions(map, resource)
|
||||
map_default_collection_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_singleton_resource(entities, options = {}, &block)
|
||||
resource = SingletonResource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
map_collection_actions(map, resource)
|
||||
map_default_singleton_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_associations(resource, options)
|
||||
map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
|
||||
|
||||
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
|
||||
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
|
||||
|
||||
Array(options[:has_one]).each do |association|
|
||||
resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
def map_has_many_associations(resource, associations, options)
|
||||
case associations
|
||||
when Hash
|
||||
associations.each do |association,has_many|
|
||||
map_has_many_associations(resource, association, options.merge(:has_many => has_many))
|
||||
end
|
||||
when Array
|
||||
associations.each do |association|
|
||||
map_has_many_associations(resource, association, options)
|
||||
end
|
||||
when Symbol, String
|
||||
resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
def map_collection_actions(map, resource)
|
||||
resource.collection_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
[method].flatten.each do |m|
|
||||
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_default_collection_actions(map, resource)
|
||||
index_route_name = "#{resource.name_prefix}#{resource.plural}"
|
||||
|
||||
if resource.uncountable?
|
||||
index_route_name << "_index"
|
||||
end
|
||||
|
||||
map_resource_routes(map, resource, :index, resource.path, index_route_name)
|
||||
map_resource_routes(map, resource, :create, resource.path, index_route_name)
|
||||
end
|
||||
|
||||
def map_default_singleton_actions(map, resource)
|
||||
map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
|
||||
end
|
||||
|
||||
def map_new_actions(map, resource)
|
||||
resource.new_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
route_path = resource.new_path
|
||||
route_name = "new_#{resource.name_prefix}#{resource.singular}"
|
||||
|
||||
unless action == :new
|
||||
route_path = "#{route_path}#{resource.action_separator}#{action}"
|
||||
route_name = "#{action}_#{route_name}"
|
||||
end
|
||||
|
||||
map_resource_routes(map, resource, action, route_path, route_name, method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_member_actions(map, resource)
|
||||
resource.member_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
[method].flatten.each do |m|
|
||||
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||
action_path ||= Base.resources_path_names[action] || action
|
||||
|
||||
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
|
||||
map_resource_routes(map, resource, :show, resource.member_path, route_path)
|
||||
map_resource_routes(map, resource, :update, resource.member_path, route_path)
|
||||
map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
|
||||
end
|
||||
|
||||
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
|
||||
if resource.has_action?(action)
|
||||
action_options = action_options_for(action, resource, method)
|
||||
formatted_route_path = "#{route_path}.:format"
|
||||
|
||||
if route_name && @set.named_routes[route_name.to_sym].nil?
|
||||
map.named_route(route_name, route_path, action_options)
|
||||
map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
|
||||
else
|
||||
map.connect(route_path, action_options)
|
||||
map.connect(formatted_route_path, action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_conditions_for(conditions, method)
|
||||
returning({:conditions => conditions.dup}) do |options|
|
||||
options[:conditions][:method] = method unless method == :any
|
||||
end
|
||||
end
|
||||
|
||||
def action_options_for(action, resource, method = nil)
|
||||
default_options = { :action => action.to_s }
|
||||
require_id = !resource.kind_of?(SingletonResource)
|
||||
|
||||
case default_options[:action]
|
||||
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
||||
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
|
||||
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
|
||||
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
|
||||
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
|
||||
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ActionController::Routing::RouteSet::Mapper
|
||||
include ActionController::Resources
|
||||
end
|
||||
190
vendor/rails/actionpack/lib/action_controller/response.rb
vendored
Normal file
190
vendor/rails/actionpack/lib/action_controller/response.rb
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
require 'digest/md5'
|
||||
|
||||
module ActionController # :nodoc:
|
||||
# Represents an HTTP response generated by a controller action. One can use an
|
||||
# ActionController::AbstractResponse object to retrieve the current state of the
|
||||
# response, or customize the response. An AbstractResponse object can either
|
||||
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
|
||||
# web browser) or a test response (i.e. one that is generated from integration
|
||||
# tests). See CgiResponse and TestResponse, respectively.
|
||||
#
|
||||
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
|
||||
# never be used directly in controllers. Controllers should use the methods defined
|
||||
# in ActionController::Base instead. For example, if you want to set the HTTP
|
||||
# response's content MIME type, then use ActionControllerBase#headers instead of
|
||||
# AbstractResponse#headers.
|
||||
#
|
||||
# Nevertheless, integration tests may want to inspect controller responses in more
|
||||
# detail, and that's when AbstractResponse can be useful for application developers.
|
||||
# Integration test methods such as ActionController::Integration::Session#get and
|
||||
# ActionController::Integration::Session#post return objects of type TestResponse
|
||||
# (which are of course also of type AbstractResponse).
|
||||
#
|
||||
# For example, the following demo integration "test" prints the body of the
|
||||
# controller response to the console:
|
||||
#
|
||||
# class DemoControllerTest < ActionController::IntegrationTest
|
||||
# def test_print_root_path_to_console
|
||||
# get('/')
|
||||
# puts @response.body
|
||||
# end
|
||||
# end
|
||||
class AbstractResponse
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :request
|
||||
|
||||
# The body content (e.g. HTML) of the response, as a String.
|
||||
attr_accessor :body
|
||||
# The headers of the response, as a Hash. It maps header names to header values.
|
||||
attr_accessor :headers
|
||||
attr_accessor :session, :cookies, :assigns, :template, :layout
|
||||
attr_accessor :redirected_to, :redirected_to_method_params
|
||||
|
||||
delegate :default_charset, :to => 'ActionController::Base'
|
||||
|
||||
def initialize
|
||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
||||
end
|
||||
|
||||
def status; headers['Status'] end
|
||||
def status=(status) headers['Status'] = status end
|
||||
|
||||
def location; headers['Location'] end
|
||||
def location=(url) headers['Location'] = url end
|
||||
|
||||
|
||||
# Sets the HTTP response's content MIME type. For example, in the controller
|
||||
# you could write this:
|
||||
#
|
||||
# response.content_type = "text/plain"
|
||||
#
|
||||
# If a character set has been defined for this response (see charset=) then
|
||||
# the character set information will also be included in the content type
|
||||
# information.
|
||||
def content_type=(mime_type)
|
||||
self.headers["Content-Type"] =
|
||||
if mime_type =~ /charset/ || (c = charset).nil?
|
||||
mime_type.to_s
|
||||
else
|
||||
"#{mime_type}; charset=#{c}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the response's content MIME type, or nil if content type has been set.
|
||||
def content_type
|
||||
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
# Set the charset of the Content-Type header. Set to nil to remove it.
|
||||
# If no content type is set, it defaults to HTML.
|
||||
def charset=(charset)
|
||||
headers["Content-Type"] =
|
||||
if charset
|
||||
"#{content_type || Mime::HTML}; charset=#{charset}"
|
||||
else
|
||||
content_type || Mime::HTML.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def charset
|
||||
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
|
||||
charset.blank? ? nil : charset.strip.split("=")[1]
|
||||
end
|
||||
|
||||
def last_modified
|
||||
if last = headers['Last-Modified']
|
||||
Time.httpdate(last)
|
||||
end
|
||||
end
|
||||
|
||||
def last_modified?
|
||||
headers.include?('Last-Modified')
|
||||
end
|
||||
|
||||
def last_modified=(utc_time)
|
||||
headers['Last-Modified'] = utc_time.httpdate
|
||||
end
|
||||
|
||||
def etag
|
||||
headers['ETag']
|
||||
end
|
||||
|
||||
def etag?
|
||||
headers.include?('ETag')
|
||||
end
|
||||
|
||||
def etag=(etag)
|
||||
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
||||
end
|
||||
|
||||
def redirect(url, status)
|
||||
self.status = status
|
||||
self.location = url.gsub(/[\r\n]/, '')
|
||||
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
headers["Content-Transfer-Encoding"] == "binary"
|
||||
end
|
||||
|
||||
def assign_default_content_type_and_charset!
|
||||
self.content_type ||= Mime::HTML
|
||||
self.charset ||= default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
assign_default_content_type_and_charset!
|
||||
handle_conditional_get!
|
||||
set_content_length!
|
||||
convert_content_type!
|
||||
end
|
||||
|
||||
private
|
||||
def handle_conditional_get!
|
||||
if etag? || last_modified?
|
||||
set_conditional_cache_control!
|
||||
elsif nonempty_ok_response?
|
||||
self.etag = body
|
||||
|
||||
if request && request.etag_matches?(etag)
|
||||
self.status = '304 Not Modified'
|
||||
self.body = ''
|
||||
end
|
||||
|
||||
set_conditional_cache_control!
|
||||
end
|
||||
end
|
||||
|
||||
def nonempty_ok_response?
|
||||
ok = !status || status[0..2] == '200'
|
||||
ok && body.is_a?(String) && !body.empty?
|
||||
end
|
||||
|
||||
def set_conditional_cache_control!
|
||||
if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
||||
def convert_content_type!
|
||||
if content_type = headers.delete("Content-Type")
|
||||
self.headers["type"] = content_type
|
||||
end
|
||||
if content_type = headers.delete("Content-type")
|
||||
self.headers["type"] = content_type
|
||||
end
|
||||
if content_type = headers.delete("content-type")
|
||||
self.headers["type"] = content_type
|
||||
end
|
||||
end
|
||||
|
||||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
||||
# for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
unless body.respond_to?(:call) || (status && status[0..2] == '304')
|
||||
self.headers["Content-Length"] ||= body.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
385
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
Normal file
385
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
require 'cgi'
|
||||
require 'uri'
|
||||
require 'action_controller/polymorphic_routes'
|
||||
require 'action_controller/routing/optimisations'
|
||||
require 'action_controller/routing/routing_ext'
|
||||
require 'action_controller/routing/route'
|
||||
require 'action_controller/routing/segments'
|
||||
require 'action_controller/routing/builder'
|
||||
require 'action_controller/routing/route_set'
|
||||
require 'action_controller/routing/recognition_optimisation'
|
||||
|
||||
module ActionController
|
||||
# == Routing
|
||||
#
|
||||
# The routing module provides URL rewriting in native Ruby. It's a way to
|
||||
# redirect incoming requests to controllers and actions. This replaces
|
||||
# mod_rewrite rules. Best of all, Rails' Routing works with any web server.
|
||||
# Routes are defined in <tt>config/routes.rb</tt>.
|
||||
#
|
||||
# Consider the following route, installed by Rails when you generate your
|
||||
# application:
|
||||
#
|
||||
# map.connect ':controller/:action/:id'
|
||||
#
|
||||
# This route states that it expects requests to consist of a
|
||||
# <tt>:controller</tt> followed by an <tt>:action</tt> that in turn is fed
|
||||
# some <tt>:id</tt>.
|
||||
#
|
||||
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
|
||||
# with:
|
||||
#
|
||||
# params = { :controller => 'blog',
|
||||
# :action => 'edit',
|
||||
# :id => '22'
|
||||
# }
|
||||
#
|
||||
# Think of creating routes as drawing a map for your requests. The map tells
|
||||
# them where to go based on some predefined pattern:
|
||||
#
|
||||
# ActionController::Routing::Routes.draw do |map|
|
||||
# Pattern 1 tells some request to go to one place
|
||||
# Pattern 2 tell them to go to another
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# The following symbols are special:
|
||||
#
|
||||
# :controller maps to your controller name
|
||||
# :action maps to an action with your controllers
|
||||
#
|
||||
# Other names simply map to a parameter as in the case of <tt>:id</tt>.
|
||||
#
|
||||
# == Route priority
|
||||
#
|
||||
# Not all routes are created equally. Routes have priority defined by the
|
||||
# order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
|
||||
# from top to bottom. The last route in that file is at the lowest priority
|
||||
# and will be applied last. If no route matches, 404 is returned.
|
||||
#
|
||||
# Within blocks, the empty pattern is at the highest priority.
|
||||
# In practice this works out nicely:
|
||||
#
|
||||
# ActionController::Routing::Routes.draw do |map|
|
||||
# map.with_options :controller => 'blog' do |blog|
|
||||
# blog.show '', :action => 'list'
|
||||
# end
|
||||
# map.connect ':controller/:action/:view'
|
||||
# end
|
||||
#
|
||||
# In this case, invoking blog controller (with an URL like '/blog/')
|
||||
# without parameters will activate the 'list' action by default.
|
||||
#
|
||||
# == Defaults routes and default parameters
|
||||
#
|
||||
# Setting a default route is straightforward in Rails - you simply append a
|
||||
# Hash at the end of your mapping to set any default parameters.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# ActionController::Routing:Routes.draw do |map|
|
||||
# map.connect ':controller/:action/:id', :controller => 'blog'
|
||||
# end
|
||||
#
|
||||
# This sets up +blog+ as the default controller if no other is specified.
|
||||
# This means visiting '/' would invoke the blog controller.
|
||||
#
|
||||
# More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
|
||||
#
|
||||
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
|
||||
#
|
||||
# Note: The default routes, as provided by the Rails generator, make all actions in every
|
||||
# controller accessible via GET requests. You should consider removing them or commenting
|
||||
# them out if you're using named routes and resources.
|
||||
#
|
||||
# == Named routes
|
||||
#
|
||||
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
|
||||
# allowing for easy reference within your source as +name_of_route_url+
|
||||
# for the full URL and +name_of_route_path+ for the URI path.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.login 'login', :controller => 'accounts', :action => 'login'
|
||||
#
|
||||
# # With render, redirect_to, tests, etc.
|
||||
# redirect_to login_url
|
||||
#
|
||||
# Arguments can be passed as well.
|
||||
#
|
||||
# redirect_to show_item_path(:id => 25)
|
||||
#
|
||||
# Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.root :controller => 'blogs'
|
||||
#
|
||||
# # would recognize http://www.example.com/ as
|
||||
# params = { :controller => 'blogs', :action => 'index' }
|
||||
#
|
||||
# # and provide these named routes
|
||||
# root_url # => 'http://www.example.com/'
|
||||
# root_path # => ''
|
||||
#
|
||||
# You can also specify an already-defined named route in your <tt>map.root</tt> call:
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.new_session :controller => 'sessions', :action => 'new'
|
||||
# map.root :new_session
|
||||
#
|
||||
# Note: when using +with_options+, the route is simply named after the
|
||||
# method you call on the block parameter rather than map.
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.with_options :controller => 'blog' do |blog|
|
||||
# blog.show '', :action => 'list'
|
||||
# blog.delete 'delete/:id', :action => 'delete',
|
||||
# blog.edit 'edit/:id', :action => 'edit'
|
||||
# end
|
||||
#
|
||||
# # provides named routes for show, delete, and edit
|
||||
# link_to @article.title, show_path(:id => @article.id)
|
||||
#
|
||||
# == Pretty URLs
|
||||
#
|
||||
# Routes can generate pretty URLs. For example:
|
||||
#
|
||||
# map.connect 'articles/:year/:month/:day',
|
||||
# :controller => 'articles',
|
||||
# :action => 'find_by_date',
|
||||
# :year => /\d{4}/,
|
||||
# :month => /\d{1,2}/,
|
||||
# :day => /\d{1,2}/
|
||||
#
|
||||
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
|
||||
# maps to
|
||||
#
|
||||
# params = {:year => '2005', :month => '11', :day => '06'}
|
||||
#
|
||||
# == Regular Expressions and parameters
|
||||
# You can specify a regular expression to define a format for a parameter.
|
||||
#
|
||||
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
||||
# :action => 'show', :postalcode => /\d{5}(-\d{4})?/
|
||||
#
|
||||
# or, more formally:
|
||||
#
|
||||
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
||||
# :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
|
||||
#
|
||||
# Formats can include the 'ignorecase' and 'extended syntax' regular
|
||||
# expression modifiers:
|
||||
#
|
||||
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
||||
# :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
|
||||
#
|
||||
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
||||
# :action => 'show',:requirements => {
|
||||
# :postalcode => /# Postcode format
|
||||
# \d{5} #Prefix
|
||||
# (-\d{4})? #Suffix
|
||||
# /x
|
||||
# }
|
||||
#
|
||||
# Using the multiline match modifier will raise an ArgumentError.
|
||||
# Encoding regular expression modifiers are silently ignored. The
|
||||
# match will always use the default encoding or ASCII.
|
||||
#
|
||||
# == Route globbing
|
||||
#
|
||||
# Specifying <tt>*[string]</tt> as part of a rule like:
|
||||
#
|
||||
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
||||
#
|
||||
# will glob all remaining parts of the route that were not recognized earlier. This idiom
|
||||
# must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
|
||||
# this case.
|
||||
#
|
||||
# == Route conditions
|
||||
#
|
||||
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
||||
#
|
||||
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
|
||||
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
||||
# <tt>:any</tt> means that any method can access the route.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# map.connect 'post/:id', :controller => 'posts', :action => 'show',
|
||||
# :conditions => { :method => :get }
|
||||
# map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
|
||||
# :conditions => { :method => :post }
|
||||
#
|
||||
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
||||
# URL will route to the <tt>show</tt> action.
|
||||
#
|
||||
# == Reloading routes
|
||||
#
|
||||
# You can reload routes if you feel you must:
|
||||
#
|
||||
# ActionController::Routing::Routes.reload
|
||||
#
|
||||
# This will clear all named routes and reload routes.rb if the file has been modified from
|
||||
# last load. To absolutely force reloading, use <tt>reload!</tt>.
|
||||
#
|
||||
# == Testing Routes
|
||||
#
|
||||
# The two main methods for testing your routes:
|
||||
#
|
||||
# === +assert_routing+
|
||||
#
|
||||
# def test_movie_route_properly_splits
|
||||
# opts = {:controller => "plugin", :action => "checkout", :id => "2"}
|
||||
# assert_routing "plugin/checkout/2", opts
|
||||
# end
|
||||
#
|
||||
# +assert_routing+ lets you test whether or not the route properly resolves into options.
|
||||
#
|
||||
# === +assert_recognizes+
|
||||
#
|
||||
# def test_route_has_options
|
||||
# opts = {:controller => "plugin", :action => "show", :id => "12"}
|
||||
# assert_recognizes opts, "/plugins/show/12"
|
||||
# end
|
||||
#
|
||||
# Note the subtle difference between the two: +assert_routing+ tests that
|
||||
# a URL fits options while +assert_recognizes+ tests that a URL
|
||||
# breaks into parameters properly.
|
||||
#
|
||||
# In tests you can simply pass the URL or named route to +get+ or +post+.
|
||||
#
|
||||
# def send_to_jail
|
||||
# get '/jail'
|
||||
# assert_response :success
|
||||
# assert_template "jail/front"
|
||||
# end
|
||||
#
|
||||
# def goes_to_login
|
||||
# get login_url
|
||||
# #...
|
||||
# end
|
||||
#
|
||||
# == View a list of all your routes
|
||||
#
|
||||
# Run <tt>rake routes</tt>.
|
||||
#
|
||||
module Routing
|
||||
SEPARATORS = %w( / . ? )
|
||||
|
||||
HTTP_METHODS = [:get, :head, :post, :put, :delete]
|
||||
|
||||
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
||||
|
||||
# The root paths which may contain controller files
|
||||
mattr_accessor :controller_paths
|
||||
self.controller_paths = []
|
||||
|
||||
# A helper module to hold URL related helpers.
|
||||
module Helpers
|
||||
include PolymorphicRoutes
|
||||
end
|
||||
|
||||
class << self
|
||||
# Expects an array of controller names as the first argument.
|
||||
# Executes the passed block with only the named controllers named available.
|
||||
# This method is used in internal Rails testing.
|
||||
def with_controllers(names)
|
||||
prior_controllers = @possible_controllers
|
||||
use_controllers! names
|
||||
yield
|
||||
ensure
|
||||
use_controllers! prior_controllers
|
||||
end
|
||||
|
||||
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
||||
# * "\\\" and "//" become "\\" or "/".
|
||||
# * "/foo/bar/../config" becomes "/foo/config".
|
||||
# The returned array is sorted by length, descending.
|
||||
def normalize_paths(paths)
|
||||
# do the hokey-pokey of path normalization...
|
||||
paths = paths.collect do |path|
|
||||
path = path.
|
||||
gsub("//", "/"). # replace double / chars with a single
|
||||
gsub("\\\\", "\\"). # replace double \ chars with a single
|
||||
gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
|
||||
|
||||
# eliminate .. paths where possible
|
||||
re = %r{[^/\\]+[/\\]\.\.[/\\]}
|
||||
path.gsub!(re, "") while path.match(re)
|
||||
path
|
||||
end
|
||||
|
||||
# start with longest path, first
|
||||
paths = paths.uniq.sort_by { |path| - path.length }
|
||||
end
|
||||
|
||||
# Returns the array of controller names currently available to ActionController::Routing.
|
||||
def possible_controllers
|
||||
unless @possible_controllers
|
||||
@possible_controllers = []
|
||||
|
||||
paths = controller_paths.select { |path| File.directory?(path) && path != "." }
|
||||
|
||||
seen_paths = Hash.new {|h, k| h[k] = true; false}
|
||||
normalize_paths(paths).each do |load_path|
|
||||
Dir["#{load_path}/**/*_controller.rb"].collect do |path|
|
||||
next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
|
||||
|
||||
controller_name = path[(load_path.length + 1)..-1]
|
||||
|
||||
controller_name.gsub!(/_controller\.rb\Z/, '')
|
||||
@possible_controllers << controller_name
|
||||
end
|
||||
end
|
||||
|
||||
# remove duplicates
|
||||
@possible_controllers.uniq!
|
||||
end
|
||||
@possible_controllers
|
||||
end
|
||||
|
||||
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
||||
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
||||
def use_controllers!(controller_names)
|
||||
@possible_controllers = controller_names
|
||||
end
|
||||
|
||||
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
||||
# Handles 4 scenarios:
|
||||
#
|
||||
# * stay in the previous controller:
|
||||
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
||||
#
|
||||
# * stay in the previous namespace:
|
||||
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
||||
#
|
||||
# * forced move to the root namespace:
|
||||
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
||||
#
|
||||
# * previous namespace is root:
|
||||
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
||||
#
|
||||
def controller_relative_to(controller, previous)
|
||||
if controller.nil? then previous
|
||||
elsif controller[0] == ?/ then controller[1..-1]
|
||||
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
|
||||
else controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Routes = RouteSet.new
|
||||
|
||||
ActiveSupport::Inflector.module_eval do
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
def inflections_with_route_reloading(&block)
|
||||
returning(inflections_without_route_reloading(&block)) {
|
||||
ActionController::Routing::Routes.reload! if block_given?
|
||||
}
|
||||
end
|
||||
|
||||
alias_method_chain :inflections, :route_reloading
|
||||
end
|
||||
end
|
||||
end
|
||||
194
vendor/rails/actionpack/lib/action_controller/routing/builder.rb
vendored
Normal file
194
vendor/rails/actionpack/lib/action_controller/routing/builder.rb
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class RouteBuilder #:nodoc:
|
||||
attr_reader :separators, :optional_separators
|
||||
attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
|
||||
|
||||
def initialize
|
||||
@separators = Routing::SEPARATORS
|
||||
@optional_separators = %w( / )
|
||||
|
||||
@separator_regexp = /[#{Regexp.escape(separators.join)}]/
|
||||
@nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
|
||||
@interval_regexp = /(.*?)(#{separator_regexp}|$)/
|
||||
end
|
||||
|
||||
# Accepts a "route path" (a string defining a route), and returns the array
|
||||
# of segments that corresponds to it. Note that the segment array is only
|
||||
# partially initialized--the defaults and requirements, for instance, need
|
||||
# to be set separately, via the +assign_route_options+ method, and the
|
||||
# <tt>optional?</tt> method for each segment will not be reliable until after
|
||||
# +assign_route_options+ is called, as well.
|
||||
def segments_for_route_path(path)
|
||||
rest, segments = path, []
|
||||
|
||||
until rest.empty?
|
||||
segment, rest = segment_for(rest)
|
||||
segments << segment
|
||||
end
|
||||
segments
|
||||
end
|
||||
|
||||
# A factory method that returns a new segment instance appropriate for the
|
||||
# format of the given string.
|
||||
def segment_for(string)
|
||||
segment =
|
||||
case string
|
||||
when /\A:(\w+)/
|
||||
key = $1.to_sym
|
||||
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
|
||||
when /\A\*(\w+)/
|
||||
PathSegment.new($1.to_sym, :optional => true)
|
||||
when /\A\?(.*?)\?/
|
||||
StaticSegment.new($1, :optional => true)
|
||||
when nonseparator_regexp
|
||||
StaticSegment.new($1)
|
||||
when separator_regexp
|
||||
DividerSegment.new($&, :optional => optional_separators.include?($&))
|
||||
end
|
||||
[segment, $~.post_match]
|
||||
end
|
||||
|
||||
# Split the given hash of options into requirement and default hashes. The
|
||||
# segments are passed alongside in order to distinguish between default values
|
||||
# and requirements.
|
||||
def divide_route_options(segments, options)
|
||||
options = options.except(:path_prefix, :name_prefix)
|
||||
|
||||
if options[:namespace]
|
||||
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
|
||||
end
|
||||
|
||||
requirements = (options.delete(:requirements) || {}).dup
|
||||
defaults = (options.delete(:defaults) || {}).dup
|
||||
conditions = (options.delete(:conditions) || {}).dup
|
||||
|
||||
validate_route_conditions(conditions)
|
||||
|
||||
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
||||
options.each do |key, value|
|
||||
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
||||
hash[key] = value
|
||||
end
|
||||
|
||||
[defaults, requirements, conditions]
|
||||
end
|
||||
|
||||
# Takes a hash of defaults and a hash of requirements, and assigns them to
|
||||
# the segments. Any unused requirements (which do not correspond to a segment)
|
||||
# are returned as a hash.
|
||||
def assign_route_options(segments, defaults, requirements)
|
||||
route_requirements = {} # Requirements that do not belong to a segment
|
||||
|
||||
segment_named = Proc.new do |key|
|
||||
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
|
||||
end
|
||||
|
||||
requirements.each do |key, requirement|
|
||||
segment = segment_named[key]
|
||||
if segment
|
||||
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
|
||||
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
||||
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
if requirement.multiline?
|
||||
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
segment.regexp = requirement
|
||||
else
|
||||
route_requirements[key] = requirement
|
||||
end
|
||||
end
|
||||
|
||||
defaults.each do |key, default|
|
||||
segment = segment_named[key]
|
||||
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
|
||||
segment.is_optional = true
|
||||
segment.default = default.to_param if default
|
||||
end
|
||||
|
||||
assign_default_route_options(segments)
|
||||
ensure_required_segments(segments)
|
||||
route_requirements
|
||||
end
|
||||
|
||||
# Assign default options, such as 'index' as a default for <tt>:action</tt>. This
|
||||
# method must be run *after* user supplied requirements and defaults have
|
||||
# been applied to the segments.
|
||||
def assign_default_route_options(segments)
|
||||
segments.each do |segment|
|
||||
next unless segment.is_a? DynamicSegment
|
||||
case segment.key
|
||||
when :action
|
||||
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
|
||||
segment.default ||= 'index'
|
||||
segment.is_optional = true
|
||||
end
|
||||
when :id
|
||||
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Makes sure that there are no optional segments that precede a required
|
||||
# segment. If any are found that precede a required segment, they are
|
||||
# made required.
|
||||
def ensure_required_segments(segments)
|
||||
allow_optional = true
|
||||
segments.reverse_each do |segment|
|
||||
allow_optional &&= segment.optional?
|
||||
if !allow_optional && segment.optional?
|
||||
unless segment.optionality_implied?
|
||||
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
|
||||
end
|
||||
segment.is_optional = false
|
||||
elsif allow_optional && segment.respond_to?(:default) && segment.default
|
||||
# if a segment has a default, then it is optional
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Construct and return a route with the given path and options.
|
||||
def build(path, options)
|
||||
# Wrap the path with slashes
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
path = "#{path}/" unless path[-1] == ?/
|
||||
|
||||
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
|
||||
|
||||
segments = segments_for_route_path(path)
|
||||
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||
requirements = assign_route_options(segments, defaults, requirements)
|
||||
|
||||
# TODO: Segments should be frozen on initialize
|
||||
segments.each { |segment| segment.freeze }
|
||||
|
||||
route = Route.new(segments, requirements, conditions)
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route.freeze
|
||||
end
|
||||
|
||||
private
|
||||
def validate_route_conditions(conditions)
|
||||
if method = conditions[:method]
|
||||
[method].flatten.each do |m|
|
||||
if m == :head
|
||||
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
||||
end
|
||||
|
||||
unless HTTP_METHODS.include?(m.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
130
vendor/rails/actionpack/lib/action_controller/routing/optimisations.rb
vendored
Normal file
130
vendor/rails/actionpack/lib/action_controller/routing/optimisations.rb
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# they've provided the right number of arguments, and have no
|
||||
# <tt>:requirements</tt>, we can just build up a string and return it.
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
module Optimisation
|
||||
def generate_optimisation_block(route, kind)
|
||||
return "" unless route.optimise?
|
||||
OPTIMISERS.inject("") do |memo, klazz|
|
||||
memo << klazz.new(route, kind).source_code
|
||||
memo
|
||||
end
|
||||
end
|
||||
|
||||
class Optimiser
|
||||
attr_reader :route, :kind
|
||||
GLOBAL_GUARD_CONDITIONS = [
|
||||
"(!defined?(default_url_options) || default_url_options.blank?)",
|
||||
"(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
|
||||
"defined?(request)",
|
||||
"request"
|
||||
]
|
||||
|
||||
def initialize(route, kind)
|
||||
@route = route
|
||||
@kind = kind
|
||||
end
|
||||
|
||||
def guard_conditions
|
||||
["false"]
|
||||
end
|
||||
|
||||
def generation_code
|
||||
'nil'
|
||||
end
|
||||
|
||||
def source_code
|
||||
if applicable?
|
||||
guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
|
||||
"return #{generation_code} if #{guard_condition}\n"
|
||||
else
|
||||
"\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
|
||||
# Issues around request.host etc.
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Given a route
|
||||
#
|
||||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls <tt>person_url(@person)</tt>, we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in +url_for+.
|
||||
class PositionalArguments < Optimiser
|
||||
def guard_conditions
|
||||
number_of_arguments = route.segment_keys.size
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
["args.size == 1", "!args.first.is_a?(Hash)"]
|
||||
else
|
||||
["args.size == #{number_of_arguments}"]
|
||||
end
|
||||
end
|
||||
|
||||
def generation_code
|
||||
elements = []
|
||||
idx = 0
|
||||
|
||||
if kind == :url
|
||||
elements << '#{request.protocol}'
|
||||
elements << '#{request.host_with_port}'
|
||||
end
|
||||
|
||||
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
|
||||
|
||||
# The last entry in <tt>route.segments</tt> appears to *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
# we don't include the trailing slashes, so skip them.
|
||||
(route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
|
||||
if segment.is_a?(DynamicSegment)
|
||||
elements << segment.interpolation_chunk("args[#{idx}].to_param")
|
||||
idx += 1
|
||||
else
|
||||
elements << segment.interpolation_chunk
|
||||
end
|
||||
end
|
||||
%("#{elements * ''}")
|
||||
end
|
||||
end
|
||||
|
||||
# This case is mostly the same as the positional arguments case
|
||||
# above, but it supports additional query parameters as the last
|
||||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_conditions
|
||||
["args.size == #{route.segment_keys.size + 1}"] +
|
||||
UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
|
||||
end
|
||||
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add a question mark and args.last.to_query on the end,
|
||||
# unless the last arg is empty
|
||||
def generation_code
|
||||
super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
|
||||
end
|
||||
|
||||
# To avoid generating "http://localhost/?host=foo.example.com" we
|
||||
# can't use this optimisation on routes without any segments
|
||||
def applicable?
|
||||
super && route.segment_keys.size > 0
|
||||
end
|
||||
end
|
||||
|
||||
OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
|
||||
end
|
||||
end
|
||||
end
|
||||
168
vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb
vendored
Normal file
168
vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# BEFORE: 0.191446860631307 ms/url
|
||||
# AFTER: 0.029847304022858 ms/url
|
||||
# Speed up: 6.4 times
|
||||
#
|
||||
# Route recognition is slow due to one-by-one iterating over
|
||||
# a whole routeset (each map.resources generates at least 14 routes)
|
||||
# and matching weird regexps on each step.
|
||||
#
|
||||
# We optimize this by skipping all URI segments that 100% sure can't
|
||||
# be matched, moving deeper in a tree of routes (where node == segment)
|
||||
# until first possible match is accured. In such case, we start walking
|
||||
# a flat list of routes, matching them with accurate matcher.
|
||||
# So, first step: search a segment tree for the first relevant index.
|
||||
# Second step: iterate routes starting with that index.
|
||||
#
|
||||
# How tree is walked? We can do a recursive tests, but it's smarter:
|
||||
# We just create a tree of if-s and elsif-s matching segments.
|
||||
#
|
||||
# We have segments of 3 flavors:
|
||||
# 1) nil (no segment, route finished)
|
||||
# 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
|
||||
# 3) const (like "/posts", "/comments")
|
||||
# 4) dynamic ("/:id", "file.:size.:extension")
|
||||
#
|
||||
# We split incoming string into segments and iterate over them.
|
||||
# When segment is nil, we drop immediately, on a current node index.
|
||||
# When segment is equal to some const, we step into branch.
|
||||
# If none constants matched, we step into 'dynamic' branch (it's a last).
|
||||
# If we can't match anything, we drop to last index on a level.
|
||||
#
|
||||
# Note: we maintain the original routes order, so we finish building
|
||||
# steps on a first dynamic segment.
|
||||
#
|
||||
#
|
||||
# Example. Given the routes:
|
||||
# 0 /posts/
|
||||
# 1 /posts/:id
|
||||
# 2 /posts/:id/comments
|
||||
# 3 /posts/blah
|
||||
# 4 /users/
|
||||
# 5 /users/:id
|
||||
# 6 /users/:id/profile
|
||||
#
|
||||
# request_uri = /users/123
|
||||
#
|
||||
# There will be only 4 iterations:
|
||||
# 1) segm test for /posts prefix, skip all /posts/* routes
|
||||
# 2) segm test for /users/
|
||||
# 3) segm test for /users/:id
|
||||
# (jump to list index = 5)
|
||||
# 4) full test for /users/:id => here we are!
|
||||
class RouteSet
|
||||
def recognize_path(path, environment={})
|
||||
result = recognize_optimized(path, environment) and return result
|
||||
|
||||
# Route was not recognized. Try to find out why (maybe wrong verb).
|
||||
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
|
||||
|
||||
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
||||
raise NotImplemented.new(*allows)
|
||||
elsif !allows.empty?
|
||||
raise MethodNotAllowed.new(*allows)
|
||||
else
|
||||
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def segment_tree(routes)
|
||||
tree = [0]
|
||||
|
||||
i = -1
|
||||
routes.each do |route|
|
||||
i += 1
|
||||
# not fast, but runs only once
|
||||
segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
|
||||
|
||||
node = tree
|
||||
segments.each do |seg|
|
||||
seg = :dynamic if seg && seg[0] == ?:
|
||||
node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
|
||||
node = node[node.size - 1][1]
|
||||
end
|
||||
end
|
||||
tree
|
||||
end
|
||||
|
||||
def generate_code(list, padding=' ', level = 0)
|
||||
# a digit
|
||||
return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
|
||||
|
||||
body = padding + "(seg = segments[#{level}]; \n"
|
||||
|
||||
i = 0
|
||||
was_nil = false
|
||||
list.each do |item|
|
||||
if Array === item
|
||||
i += 1
|
||||
start = (i == 1)
|
||||
final = (i == list.size)
|
||||
tag, sub = item
|
||||
if tag == :dynamic
|
||||
body += padding + "#{start ? 'if' : 'elsif'} true\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
break
|
||||
elsif tag == nil && !was_nil
|
||||
was_nil = true
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
else
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
body += padding + "else\n"
|
||||
body += padding + " #{list[0]}\n"
|
||||
body += padding + "end)\n"
|
||||
body
|
||||
end
|
||||
|
||||
# this must be really fast
|
||||
def to_plain_segments(str)
|
||||
str = str.dup
|
||||
str.sub!(/^\/+/,'')
|
||||
str.sub!(/\/+$/,'')
|
||||
segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
|
||||
segments << nil
|
||||
segments
|
||||
end
|
||||
|
||||
private
|
||||
def write_recognize_optimized!
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
|
||||
remove_recognize_optimized!
|
||||
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, '(recognize_optimized)', 1
|
||||
end
|
||||
|
||||
def clear_recognize_optimized!
|
||||
remove_recognize_optimized!
|
||||
write_recognize_optimized!
|
||||
end
|
||||
|
||||
def remove_recognize_optimized!
|
||||
if respond_to?(:recognize_optimized)
|
||||
class << self
|
||||
remove_method :recognize_optimized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
255
vendor/rails/actionpack/lib/action_controller/routing/route.rb
vendored
Normal file
255
vendor/rails/actionpack/lib/action_controller/routing/route.rb
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions, :optimise
|
||||
|
||||
def initialize(segments = [], requirements = {}, conditions = {})
|
||||
@segments = segments
|
||||
@requirements = requirements
|
||||
@conditions = conditions
|
||||
|
||||
if !significant_keys.include?(:action) && !requirements[:action]
|
||||
@requirements[:action] = "index"
|
||||
@significant_keys << :action
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
|
||||
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
@optimise = false
|
||||
else
|
||||
@optimise = true
|
||||
end
|
||||
end
|
||||
|
||||
# Indicates whether the routes should be optimised with the string interpolation
|
||||
# version of the named routes methods.
|
||||
def optimise?
|
||||
@optimise && ActionController::Base::optimise_named_routes
|
||||
end
|
||||
|
||||
def segment_keys
|
||||
segments.collect do |segment|
|
||||
segment.key if segment.respond_to? :key
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Build a query string from the keys of the given hash. If +only_keys+
|
||||
# is given (as an array), only the keys indicated will be used to build
|
||||
# the query string. The query string will correctly build array parameter
|
||||
# values.
|
||||
def build_query_string(hash, only_keys = nil)
|
||||
elements = []
|
||||
|
||||
(only_keys || hash.keys).each do |key|
|
||||
if value = hash[key]
|
||||
elements << value.to_query(key)
|
||||
end
|
||||
end
|
||||
|
||||
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
||||
end
|
||||
|
||||
# A route's parameter shell contains parameter values that are not in the
|
||||
# route's path, but should be placed in the recognized hash.
|
||||
#
|
||||
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
|
||||
#
|
||||
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
||||
#
|
||||
def parameter_shell
|
||||
@parameter_shell ||= returning({}) do |shell|
|
||||
requirements.each do |key, requirement|
|
||||
shell[key] = requirement unless requirement.is_a? Regexp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return an array containing all the keys that are used in this route. This
|
||||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= returning([]) do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
end
|
||||
end
|
||||
|
||||
# Return a hash of key/value pairs representing the keys in the route that
|
||||
# have defaults, or which are specified by non-regexp requirements.
|
||||
def defaults
|
||||
@defaults ||= returning({}) do |hash|
|
||||
segments.each do |segment|
|
||||
next unless segment.respond_to? :default
|
||||
hash[segment.key] = segment.default unless segment.default.nil?
|
||||
end
|
||||
requirements.each do |key,req|
|
||||
next if Regexp === req || req.nil?
|
||||
hash[key] = req
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def matches_controller_and_action?(controller, action)
|
||||
prepare_matching!
|
||||
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
||||
(@action_requirement.nil? || @action_requirement === action)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@to_s ||= begin
|
||||
segs = segments.inject("") { |str,s| str << s.to_s }
|
||||
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Route should be prepared and frozen on initialize
|
||||
def freeze
|
||||
unless frozen?
|
||||
write_generation!
|
||||
write_recognition!
|
||||
prepare_matching!
|
||||
|
||||
parameter_shell
|
||||
significant_keys
|
||||
defaults
|
||||
to_s
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation!
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# the query string. (Never use keys from the recalled request when building the
|
||||
# query string.)
|
||||
|
||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition!
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env = {})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += segment.number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys = nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall = {})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
def prepare_matching!
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
437
vendor/rails/actionpack/lib/action_controller/routing/route_set.rb
vendored
Normal file
437
vendor/rails/actionpack/lib/action_controller/routing/route_set.rb
vendored
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class RouteSet #:nodoc:
|
||||
# Mapper instances are used to build routes. The object passed to the draw
|
||||
# block in config/routes.rb is a Mapper instance.
|
||||
#
|
||||
# Mapper instances have relatively few instance methods, in order to avoid
|
||||
# clashes with named routes.
|
||||
class Mapper #:doc:
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
end
|
||||
|
||||
# Create an unnamed route with the provided +path+ and +options+. See
|
||||
# ActionController::Routing for an introduction to routes.
|
||||
def connect(path, options = {})
|
||||
@set.add_route(path, options)
|
||||
end
|
||||
|
||||
# Creates a named route called "root" for matching the root level request.
|
||||
def root(options = {})
|
||||
if options.is_a?(Symbol)
|
||||
if source_route = @set.named_routes.routes[options]
|
||||
options = source_route.defaults.merge({ :conditions => source_route.conditions })
|
||||
end
|
||||
end
|
||||
named_route("root", '', options)
|
||||
end
|
||||
|
||||
def named_route(name, path, options = {}) #:nodoc:
|
||||
@set.add_named_route(name, path, options)
|
||||
end
|
||||
|
||||
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
||||
# Example:
|
||||
#
|
||||
# map.namespace(:admin) do |admin|
|
||||
# admin.resources :products,
|
||||
# :has_many => [ :tags, :images, :variants ]
|
||||
# end
|
||||
#
|
||||
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
|
||||
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
|
||||
# Admin::TagsController.
|
||||
def namespace(name, options = {}, &block)
|
||||
if options[:namespace]
|
||||
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
||||
else
|
||||
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(route_name, *args, &proc) #:nodoc:
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@set.add_named_route(route_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# A NamedRouteCollection instance is a collection of named routes, and also
|
||||
# maintains an anonymous module that can be used to install helpers for the
|
||||
# named routes.
|
||||
class NamedRouteCollection #:nodoc:
|
||||
include Enumerable
|
||||
include ActionController::Routing::Optimisation
|
||||
attr_reader :routes, :helpers
|
||||
|
||||
def initialize
|
||||
clear!
|
||||
end
|
||||
|
||||
def clear!
|
||||
@routes = {}
|
||||
@helpers = []
|
||||
|
||||
@module ||= Module.new
|
||||
@module.instance_methods.each do |selector|
|
||||
@module.class_eval { remove_method selector }
|
||||
end
|
||||
end
|
||||
|
||||
def add(name, route)
|
||||
routes[name.to_sym] = route
|
||||
define_named_route_methods(name, route)
|
||||
end
|
||||
|
||||
def get(name)
|
||||
routes[name.to_sym]
|
||||
end
|
||||
|
||||
alias []= add
|
||||
alias [] get
|
||||
alias clear clear!
|
||||
|
||||
def each
|
||||
routes.each { |name, route| yield name, route }
|
||||
self
|
||||
end
|
||||
|
||||
def names
|
||||
routes.keys
|
||||
end
|
||||
|
||||
def length
|
||||
routes.length
|
||||
end
|
||||
|
||||
def reset!
|
||||
old_routes = routes.dup
|
||||
clear!
|
||||
old_routes.each do |name, route|
|
||||
add(name, route)
|
||||
end
|
||||
end
|
||||
|
||||
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
||||
reset! if regenerate
|
||||
Array(destinations).each do |dest|
|
||||
dest.__send__(:include, @module)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def url_helper_name(name, kind = :url)
|
||||
:"#{name}_#{kind}"
|
||||
end
|
||||
|
||||
def hash_access_name(name, kind = :url)
|
||||
:"hash_for_#{name}_#{kind}"
|
||||
end
|
||||
|
||||
def define_named_route_methods(name, route)
|
||||
{:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
|
||||
hash = route.defaults.merge(:use_route => name).merge(opts)
|
||||
define_hash_access route, name, kind, hash
|
||||
define_url_helper route, name, kind, hash
|
||||
end
|
||||
end
|
||||
|
||||
def define_hash_access(route, name, kind, options)
|
||||
selector = hash_access_name(name, kind)
|
||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
def #{selector}(options = nil)
|
||||
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
||||
end
|
||||
protected :#{selector}
|
||||
end_eval
|
||||
helpers << selector
|
||||
end
|
||||
|
||||
def define_url_helper(route, name, kind, options)
|
||||
selector = url_helper_name(name, kind)
|
||||
# The segment keys used for positional paramters
|
||||
|
||||
hash_access_method = hash_access_name(name, kind)
|
||||
|
||||
# allow ordered parameters to be associated with corresponding
|
||||
# dynamic segments, so you can do
|
||||
#
|
||||
# foo_url(bar, baz, bang)
|
||||
#
|
||||
# instead of
|
||||
#
|
||||
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
||||
#
|
||||
# Also allow options hash, so you can do
|
||||
#
|
||||
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
||||
#
|
||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
def #{selector}(*args)
|
||||
|
||||
#{generate_optimisation_block(route, kind)}
|
||||
|
||||
opts = if args.empty? || Hash === args.first
|
||||
args.first || {}
|
||||
else
|
||||
options = args.extract_options!
|
||||
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
|
||||
h[k] = v
|
||||
h
|
||||
end
|
||||
options.merge(args)
|
||||
end
|
||||
|
||||
url_for(#{hash_access_method}(opts))
|
||||
end
|
||||
protected :#{selector}
|
||||
end_eval
|
||||
helpers << selector
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :routes, :named_routes, :configuration_file
|
||||
|
||||
def initialize
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
|
||||
clear_recognize_optimized!
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to specify a different
|
||||
# RouteBuilder instance, so that other route DSL's can be created.
|
||||
def builder
|
||||
@builder ||= RouteBuilder.new
|
||||
end
|
||||
|
||||
def draw
|
||||
clear!
|
||||
yield Mapper.new(self)
|
||||
install_helpers
|
||||
end
|
||||
|
||||
def clear!
|
||||
routes.clear
|
||||
named_routes.clear
|
||||
@combined_regexp = nil
|
||||
@routes_by_controller = nil
|
||||
# This will force routing/recognition_optimization.rb
|
||||
# to refresh optimisations.
|
||||
clear_recognize_optimized!
|
||||
end
|
||||
|
||||
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
||||
Array(destinations).each { |d| d.module_eval { include Helpers } }
|
||||
named_routes.install(destinations, regenerate_code)
|
||||
end
|
||||
|
||||
def empty?
|
||||
routes.empty?
|
||||
end
|
||||
|
||||
def load!
|
||||
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
||||
clear!
|
||||
load_routes!
|
||||
end
|
||||
|
||||
# reload! will always force a reload whereas load checks the timestamp first
|
||||
alias reload! load!
|
||||
|
||||
def reload
|
||||
if @routes_last_modified && configuration_file
|
||||
mtime = File.stat(configuration_file).mtime
|
||||
# if it hasn't been changed, then just return
|
||||
return if mtime == @routes_last_modified
|
||||
# if it has changed then record the new time and fall to the load! below
|
||||
@routes_last_modified = mtime
|
||||
end
|
||||
load!
|
||||
end
|
||||
|
||||
def load_routes!
|
||||
if configuration_file
|
||||
load configuration_file
|
||||
@routes_last_modified = File.stat(configuration_file).mtime
|
||||
else
|
||||
add_route ":controller/:action/:id"
|
||||
end
|
||||
end
|
||||
|
||||
def add_route(path, options = {})
|
||||
route = builder.build(path, options)
|
||||
routes << route
|
||||
route
|
||||
end
|
||||
|
||||
def add_named_route(name, path, options = {})
|
||||
# TODO - is options EVER used?
|
||||
name = options[:name_prefix] + name.to_s if options[:name_prefix]
|
||||
named_routes[name.to_sym] = add_route(path, options)
|
||||
end
|
||||
|
||||
def options_as_params(options)
|
||||
# If an explicit :controller was given, always make :action explicit
|
||||
# too, so that action expiry works as expected for things like
|
||||
#
|
||||
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
|
||||
#
|
||||
# (the above is from the unit tests). In the above case, because the
|
||||
# controller was explicitly given, but no action, the action is implied to
|
||||
# be "index", not the recalled action of "show".
|
||||
#
|
||||
# great fun, eh?
|
||||
|
||||
options_as_params = options.clone
|
||||
options_as_params[:action] ||= 'index' if options[:controller]
|
||||
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
|
||||
options_as_params
|
||||
end
|
||||
|
||||
def build_expiry(options, recall)
|
||||
recall.inject({}) do |expiry, (key, recalled_value)|
|
||||
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
|
||||
expiry
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the path indicated by the arguments, and return an array of
|
||||
# the keys that were not used to generate it.
|
||||
def extra_keys(options, recall={})
|
||||
generate_extras(options, recall).last
|
||||
end
|
||||
|
||||
def generate_extras(options, recall={})
|
||||
generate(options, recall, :generate_extras)
|
||||
end
|
||||
|
||||
def generate(options, recall = {}, method=:generate)
|
||||
named_route_name = options.delete(:use_route)
|
||||
generate_all = options.delete(:generate_all)
|
||||
if named_route_name
|
||||
named_route = named_routes[named_route_name]
|
||||
options = named_route.parameter_shell.merge(options)
|
||||
end
|
||||
|
||||
options = options_as_params(options)
|
||||
expire_on = build_expiry(options, recall)
|
||||
|
||||
if options[:controller]
|
||||
options[:controller] = options[:controller].to_s
|
||||
end
|
||||
# if the controller has changed, make sure it changes relative to the
|
||||
# current controller module, if any. In other words, if we're currently
|
||||
# on admin/get, and the new controller is 'set', the new controller
|
||||
# should really be admin/set.
|
||||
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
|
||||
old_parts = recall[:controller].split('/')
|
||||
new_parts = options[:controller].split('/')
|
||||
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
|
||||
options[:controller] = parts.join('/')
|
||||
end
|
||||
|
||||
# drop the leading '/' on the controller name
|
||||
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
||||
merged = recall.merge(options)
|
||||
|
||||
if named_route
|
||||
path = named_route.generate(options, merged, expire_on)
|
||||
if path.nil?
|
||||
raise_named_route_error(options, named_route, named_route_name)
|
||||
else
|
||||
return path
|
||||
end
|
||||
else
|
||||
merged[:action] ||= 'index'
|
||||
options[:action] ||= 'index'
|
||||
|
||||
controller = merged[:controller]
|
||||
action = merged[:action]
|
||||
|
||||
raise RoutingError, "Need controller and action!" unless controller && action
|
||||
|
||||
if generate_all
|
||||
# Used by caching to expire all paths for a resource
|
||||
return routes.collect do |route|
|
||||
route.__send__(method, options, merged, expire_on)
|
||||
end.compact
|
||||
end
|
||||
|
||||
# don't use the recalled keys when determining which routes to check
|
||||
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
|
||||
|
||||
routes.each do |route|
|
||||
results = route.__send__(method, options, merged, expire_on)
|
||||
return results if results && (!results.is_a?(Array) || results.first)
|
||||
end
|
||||
end
|
||||
|
||||
raise RoutingError, "No route matches #{options.inspect}"
|
||||
end
|
||||
|
||||
# try to give a helpful error message when named route generation fails
|
||||
def raise_named_route_error(options, named_route, named_route_name)
|
||||
diff = named_route.requirements.diff(options)
|
||||
unless diff.empty?
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
|
||||
else
|
||||
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
||||
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
|
||||
end
|
||||
end
|
||||
|
||||
def recognize(request)
|
||||
params = recognize_path(request.path, extract_request_environment(request))
|
||||
request.path_parameters = params.with_indifferent_access
|
||||
"#{params[:controller].camelize}Controller".constantize
|
||||
end
|
||||
|
||||
def recognize_path(path, environment={})
|
||||
raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
|
||||
end
|
||||
|
||||
def routes_by_controller
|
||||
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
||||
controller_hash[controller] = Hash.new do |action_hash, action|
|
||||
action_hash[action] = Hash.new do |key_hash, keys|
|
||||
key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def routes_for(options, merged, expire_on)
|
||||
raise "Need controller and action!" unless controller && action
|
||||
controller = merged[:controller]
|
||||
merged = options if expire_on[:controller]
|
||||
action = merged[:action] || 'index'
|
||||
|
||||
routes_by_controller[controller][action][merged.keys]
|
||||
end
|
||||
|
||||
def routes_for_controller_and_action(controller, action)
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
(selected.length == routes.length) ? routes : selected
|
||||
end
|
||||
|
||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
selected.sort_by do |route|
|
||||
(keys - route.significant_keys).length
|
||||
end
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to extract further attributes
|
||||
# from the request, for use by route conditions and such.
|
||||
def extract_request_environment(request)
|
||||
{ :method => request.method }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
49
vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb
vendored
Normal file
49
vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
class Object
|
||||
def to_param
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Regexp #:nodoc:
|
||||
def number_of_captures
|
||||
Regexp.new("|#{source}").match('').captures.length
|
||||
end
|
||||
|
||||
def multiline?
|
||||
options & MULTILINE == MULTILINE
|
||||
end
|
||||
|
||||
class << self
|
||||
def optionalize(pattern)
|
||||
case unoptionalize(pattern)
|
||||
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
|
||||
else "(?:#{pattern})?"
|
||||
end
|
||||
end
|
||||
|
||||
def unoptionalize(pattern)
|
||||
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
|
||||
return $1 if regexp =~ pattern
|
||||
end
|
||||
return pattern
|
||||
end
|
||||
end
|
||||
end
|
||||
312
vendor/rails/actionpack/lib/action_controller/routing/segments.rb
vendored
Normal file
312
vendor/rails/actionpack/lib/action_controller/routing/segments.rb
vendored
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class Segment #:nodoc:
|
||||
RESERVED_PCHAR = ':@&=+$,;'
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
|
||||
# TODO: Convert :is_optional accessor to read only
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
def initialize
|
||||
@is_optional = false
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
Regexp.new(regexp_chunk).number_of_captures
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
nil
|
||||
end
|
||||
|
||||
# Continue generating string for the prior segments.
|
||||
def continue_string_structure(prior_segments)
|
||||
if prior_segments.empty?
|
||||
interpolation_statement(prior_segments)
|
||||
else
|
||||
new_priors = prior_segments[0..-2]
|
||||
prior_segments.last.string_structure(new_priors)
|
||||
end
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
URI.escape(value, UNSAFE_PCHAR)
|
||||
end
|
||||
|
||||
# Return a string interpolation statement for this segment and those before it.
|
||||
def interpolation_statement(prior_segments)
|
||||
chunks = prior_segments.collect { |s| s.interpolation_chunk }
|
||||
chunks << interpolation_chunk
|
||||
"\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
|
||||
end
|
||||
|
||||
# Return an if condition that is true if all the prior segments can be generated.
|
||||
# If there are no optional segments before this one, then nil is returned.
|
||||
def all_optionals_available_condition(prior_segments)
|
||||
optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
|
||||
optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
|
||||
end
|
||||
|
||||
# Recognition
|
||||
|
||||
def match_extraction(next_capture)
|
||||
nil
|
||||
end
|
||||
|
||||
# Warning
|
||||
|
||||
# Returns true if this segment is optional? because of a default. If so, then
|
||||
# no warning will be emitted regarding this segment.
|
||||
def optionality_implied?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_reader :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
def initialize(value = nil, options = {})
|
||||
super()
|
||||
@value = value
|
||||
@raw = options[:raw] if options.key?(:raw)
|
||||
@is_optional = options[:optional] if options.key?(:optional)
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
raw? ? value : super
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
chunk = Regexp.escape(value)
|
||||
optional? ? Regexp.optionalize(chunk) : chunk
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
0
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
escaped = Regexp.escape(value)
|
||||
if optional? && ! pattern.empty?
|
||||
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
|
||||
elsif optional?
|
||||
Regexp.optionalize escaped
|
||||
else
|
||||
escaped + pattern
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil, options = {})
|
||||
super(value, {:raw => true, :optional => true}.merge(options))
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_reader :key
|
||||
|
||||
# TODO: Convert these accessors to read only
|
||||
attr_accessor :default, :regexp
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
super()
|
||||
@key = key
|
||||
@default = options[:default] if options.key?(:default)
|
||||
@regexp = options[:regexp] if options.key?(:regexp)
|
||||
@is_optional = true if options[:optional] || options.key?(:default)
|
||||
end
|
||||
|
||||
def to_s
|
||||
":#{key}"
|
||||
end
|
||||
|
||||
# The local variable name that the value of this segment will be extracted to.
|
||||
def local_name
|
||||
"#{key}_value"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def value_check
|
||||
if default # Then we know it won't be nil
|
||||
"#{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
elsif optional?
|
||||
# If we have a regexp check that the value is not given, or that it matches.
|
||||
# If we have no regexp, return nil since we do not require a condition.
|
||||
"#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
else # Then it must be present, and if we have a regexp, it must match too.
|
||||
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
||||
end
|
||||
end
|
||||
|
||||
def expiry_statement
|
||||
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
s = extract_value
|
||||
vc = value_check
|
||||
s << "\nreturn [nil,nil] unless #{vc}" if vc
|
||||
s << "\n#{expiry_statement}"
|
||||
end
|
||||
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
if optional? # We have a conditional to do...
|
||||
# If we should not appear in the url, just write the code for the prior
|
||||
# segments. This occurs if our value is the default value, or, if we are
|
||||
# optional, if we have nil as our value.
|
||||
"if #{local_name} == #{default.inspect}\n" +
|
||||
continue_string_structure(prior_segments) +
|
||||
"\nelse\n" + # Otherwise, write the code up to here
|
||||
"#{interpolation_statement(prior_segments)}\nend"
|
||||
else
|
||||
interpolation_statement(prior_segments)
|
||||
end
|
||||
end
|
||||
|
||||
def value_regexp
|
||||
Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
if regexp
|
||||
if regexp_has_modifiers?
|
||||
"(#{regexp.to_s})"
|
||||
else
|
||||
"(#{regexp.source})"
|
||||
end
|
||||
else
|
||||
"([^#{Routing::SEPARATORS.join}]+)"
|
||||
end
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
if regexp
|
||||
regexp.number_of_captures + 1
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
pattern = "#{regexp_chunk}#{pattern}"
|
||||
optional? ? Regexp.optionalize(pattern) : pattern
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
# All non code-related keys (such as :id, :slug) are URI-unescaped as
|
||||
# path parameters.
|
||||
default_value = default ? default.inspect : nil
|
||||
%[
|
||||
value = if (m = match[#{next_capture}])
|
||||
URI.unescape(m)
|
||||
else
|
||||
#{default_value}
|
||||
end
|
||||
params[:#{key}] = value if value
|
||||
]
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
[:action, :id].include? key
|
||||
end
|
||||
|
||||
def regexp_has_modifiers?
|
||||
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
|
||||
end
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
def regexp_chunk
|
||||
possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
|
||||
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
1
|
||||
end
|
||||
|
||||
# Don't URI.escape the controller name since it may contain slashes.
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}.to_s}"
|
||||
end
|
||||
|
||||
# Make sure controller names like Admin/Content are correctly normalized to
|
||||
# admin/content
|
||||
def extract_value
|
||||
"#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
if default
|
||||
"params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
|
||||
else
|
||||
"params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PathSegment < DynamicSegment #:nodoc:
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}}"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def default
|
||||
''
|
||||
end
|
||||
|
||||
def default=(path)
|
||||
raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
regexp || "(.*)"
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
regexp ? regexp.number_of_captures : 1
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
|
||||
class Result < ::Array #:nodoc:
|
||||
def to_s() join '/' end
|
||||
def self.new_escaped(strings)
|
||||
new strings.collect {|str| URI.unescape str}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
340
vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb
vendored
Normal file
340
vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb
vendored
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'digest/md5'
|
||||
|
||||
class CGI
|
||||
class Session
|
||||
attr_reader :data
|
||||
|
||||
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
||||
def model
|
||||
@dbman.model if @dbman
|
||||
end
|
||||
|
||||
|
||||
# A session store backed by an Active Record class. A default class is
|
||||
# provided, but any object duck-typing to an Active Record Session class
|
||||
# with text +session_id+ and +data+ attributes is sufficient.
|
||||
#
|
||||
# The default assumes a +sessions+ tables with columns:
|
||||
# +id+ (numeric primary key),
|
||||
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
||||
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
||||
# The +session_id+ column should always be indexed for speedy lookups.
|
||||
# Session data is marshaled to the +data+ column in Base64 format.
|
||||
# If the data you write is larger than the column's size limit,
|
||||
# ActionController::SessionOverflowError will be raised.
|
||||
#
|
||||
# You may configure the table name, primary key, and data column.
|
||||
# For example, at the end of <tt>config/environment.rb</tt>:
|
||||
# CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
|
||||
# CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
|
||||
# CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
|
||||
# Note that setting the primary key to the +session_id+ frees you from
|
||||
# having a separate +id+ column if you don't want it. However, you must
|
||||
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
||||
# on ApplicationController is a good place.
|
||||
#
|
||||
# Since the default class is a simple Active Record, you get timestamps
|
||||
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
||||
# the +sessions+ table, making periodic session expiration a snap.
|
||||
#
|
||||
# You may provide your own session class implementation, whether a
|
||||
# feature-packed Active Record or a bare-metal high-performance SQL
|
||||
# store, by setting
|
||||
# CGI::Session::ActiveRecordStore.session_class = MySessionClass
|
||||
# You must implement these methods:
|
||||
# self.find_by_session_id(session_id)
|
||||
# initialize(hash_of_session_id_and_data)
|
||||
# attr_reader :session_id
|
||||
# attr_accessor :data
|
||||
# save
|
||||
# destroy
|
||||
#
|
||||
# The example SqlBypass class is a generic SQL session store. You may
|
||||
# use it as a basis for high-performance database-specific stores.
|
||||
class ActiveRecordStore
|
||||
# The default Active Record class.
|
||||
class Session < ActiveRecord::Base
|
||||
# Customizable data column name. Defaults to 'data'.
|
||||
cattr_accessor :data_column_name
|
||||
self.data_column_name = 'data'
|
||||
|
||||
before_save :marshal_data!
|
||||
before_save :raise_on_session_data_overflow!
|
||||
|
||||
class << self
|
||||
# Don't try to reload ARStore::Session in dev mode.
|
||||
def reloadable? #:nodoc:
|
||||
false
|
||||
end
|
||||
|
||||
def data_column_size_limit
|
||||
@data_column_size_limit ||= columns_hash[@@data_column_name].limit
|
||||
end
|
||||
|
||||
# Hook to set up sessid compatibility.
|
||||
def find_by_session_id(session_id)
|
||||
setup_sessid_compatibility!
|
||||
find_by_session_id(session_id)
|
||||
end
|
||||
|
||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
||||
|
||||
def create_table!
|
||||
connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{connection.quote_column_name('session_id')} TEXT UNIQUE,
|
||||
#{connection.quote_column_name(@@data_column_name)} TEXT(255)
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
|
||||
private
|
||||
# Compatibility with tables using sessid instead of session_id.
|
||||
def setup_sessid_compatibility!
|
||||
# Reset column info since it may be stale.
|
||||
reset_column_information
|
||||
if columns_hash['sessid']
|
||||
def self.find_by_session_id(*args)
|
||||
find_by_sessid(*args)
|
||||
end
|
||||
|
||||
define_method(:session_id) { sessid }
|
||||
define_method(:session_id=) { |session_id| self.sessid = session_id }
|
||||
else
|
||||
def self.find_by_session_id(session_id)
|
||||
find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
||||
end
|
||||
|
||||
attr_writer :data
|
||||
|
||||
# Has the session been loaded yet?
|
||||
def loaded?
|
||||
!! @data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def marshal_data!
|
||||
return false if !loaded?
|
||||
write_attribute(@@data_column_name, self.class.marshal(self.data))
|
||||
end
|
||||
|
||||
# Ensures that the data about to be stored in the database is not
|
||||
# larger than the data storage column. Raises
|
||||
# ActionController::SessionOverflowError.
|
||||
def raise_on_session_data_overflow!
|
||||
return false if !loaded?
|
||||
limit = self.class.data_column_size_limit
|
||||
if loaded? and limit and read_attribute(@@data_column_name).size > limit
|
||||
raise ActionController::SessionOverflowError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A barebones session store which duck-types with the default session
|
||||
# store but bypasses Active Record and issues SQL directly. This is
|
||||
# an example session model class meant as a basis for your own classes.
|
||||
#
|
||||
# The database connection, table name, and session id and data columns
|
||||
# are configurable class attributes. Marshaling and unmarshaling
|
||||
# are implemented as class methods that you may override. By default,
|
||||
# marshaling data is
|
||||
#
|
||||
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
||||
#
|
||||
# and unmarshaling data is
|
||||
#
|
||||
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
#
|
||||
# This marshaling behavior is intended to store the widest range of
|
||||
# binary session data in a +text+ column. For higher performance,
|
||||
# store in a +blob+ column instead and forgo the Base64 encoding.
|
||||
class SqlBypass
|
||||
# Use the ActiveRecord::Base.connection by default.
|
||||
cattr_accessor :connection
|
||||
|
||||
# The table name defaults to 'sessions'.
|
||||
cattr_accessor :table_name
|
||||
@@table_name = 'sessions'
|
||||
|
||||
# The session id field defaults to 'session_id'.
|
||||
cattr_accessor :session_id_column
|
||||
@@session_id_column = 'session_id'
|
||||
|
||||
# The data field defaults to 'data'.
|
||||
cattr_accessor :data_column
|
||||
@@data_column = 'data'
|
||||
|
||||
class << self
|
||||
|
||||
def connection
|
||||
@@connection ||= ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
# Look up a session by id and unmarshal its data if found.
|
||||
def find_by_session_id(session_id)
|
||||
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
|
||||
new(:session_id => session_id, :marshaled_data => record['data'])
|
||||
end
|
||||
end
|
||||
|
||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
||||
|
||||
def create_table!
|
||||
@@connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
|
||||
#{@@connection.quote_column_name(data_column)} TEXT
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
@@connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :session_id
|
||||
attr_writer :data
|
||||
|
||||
# Look for normal and marshaled data, self.find_by_session_id's way of
|
||||
# telling us to postpone unmarshaling until the data is requested.
|
||||
# We need to handle a normal data attribute in case of a new record.
|
||||
def initialize(attributes)
|
||||
@session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
|
||||
@new_record = @marshaled_data.nil?
|
||||
end
|
||||
|
||||
def new_record?
|
||||
@new_record
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
unless @data
|
||||
if @marshaled_data
|
||||
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
||||
else
|
||||
@data = {}
|
||||
end
|
||||
end
|
||||
@data
|
||||
end
|
||||
|
||||
def loaded?
|
||||
!! @data
|
||||
end
|
||||
|
||||
def save
|
||||
return false if !loaded?
|
||||
marshaled_data = self.class.marshal(data)
|
||||
|
||||
if @new_record
|
||||
@new_record = false
|
||||
@@connection.update <<-end_sql, 'Create session'
|
||||
INSERT INTO #{@@table_name} (
|
||||
#{@@connection.quote_column_name(@@session_id_column)},
|
||||
#{@@connection.quote_column_name(@@data_column)} )
|
||||
VALUES (
|
||||
#{@@connection.quote(session_id)},
|
||||
#{@@connection.quote(marshaled_data)} )
|
||||
end_sql
|
||||
else
|
||||
@@connection.update <<-end_sql, 'Update session'
|
||||
UPDATE #{@@table_name}
|
||||
SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless @new_record
|
||||
@@connection.delete <<-end_sql, 'Destroy session'
|
||||
DELETE FROM #{@@table_name}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# The class used for session storage. Defaults to
|
||||
# CGI::Session::ActiveRecordStore::Session.
|
||||
cattr_accessor :session_class
|
||||
self.session_class = Session
|
||||
|
||||
# Find or instantiate a session given a CGI::Session.
|
||||
def initialize(session, option = nil)
|
||||
session_id = session.session_id
|
||||
unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
|
||||
unless session.new_session
|
||||
raise CGI::Session::NoSession, 'uninitialized session'
|
||||
end
|
||||
@session = @@session_class.new(:session_id => session_id, :data => {})
|
||||
# session saving can be lazy again, because of improved component implementation
|
||||
# therefore next line gets commented out:
|
||||
# @session.save
|
||||
end
|
||||
end
|
||||
|
||||
# Access the underlying session model.
|
||||
def model
|
||||
@session
|
||||
end
|
||||
|
||||
# Restore session state. The session model handles unmarshaling.
|
||||
def restore
|
||||
if @session
|
||||
@session.data
|
||||
end
|
||||
end
|
||||
|
||||
# Save session store.
|
||||
def update
|
||||
if @session
|
||||
ActiveRecord::Base.silence { @session.save }
|
||||
end
|
||||
end
|
||||
|
||||
# Save and close the session store.
|
||||
def close
|
||||
if @session
|
||||
update
|
||||
@session = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Delete and close the session store.
|
||||
def delete
|
||||
if @session
|
||||
ActiveRecord::Base.silence { @session.destroy }
|
||||
@session = nil
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def logger
|
||||
ActionController::Base.logger rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
167
vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb
vendored
Normal file
167
vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'openssl' # to generate the HMAC message digest
|
||||
|
||||
# This cookie-based session store is the Rails default. Sessions typically
|
||||
# contain at most a user_id and flash message; both fit within the 4K cookie
|
||||
# size limit. Cookie-based sessions are dramatically faster than the
|
||||
# alternatives.
|
||||
#
|
||||
# If you have more than 4K of session data or don't want your data to be
|
||||
# visible to the user, pick another session store.
|
||||
#
|
||||
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
||||
# TamperedWithCookie is raised if the data integrity check fails.
|
||||
#
|
||||
# A message digest is included with the cookie to ensure data integrity:
|
||||
# a user cannot alter his +user_id+ without knowing the secret key included in
|
||||
# the hash. New apps are generated with a pregenerated secret in
|
||||
# config/environment.rb. Set your own for old apps you're upgrading.
|
||||
#
|
||||
# Session options:
|
||||
#
|
||||
# * <tt>:secret</tt>: An application-wide key string or block returning a string
|
||||
# called per generated digest. The block is called with the CGI::Session
|
||||
# instance as an argument. It's important that the secret is not vulnerable to
|
||||
# a dictionary attack. Therefore, you should choose a secret consisting of
|
||||
# random numbers and letters and more than 30 characters. Examples:
|
||||
#
|
||||
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
||||
# :secret => Proc.new { User.current_user.secret_key }
|
||||
#
|
||||
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
||||
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
||||
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
||||
#
|
||||
# To generate a secret key for an existing application, run
|
||||
# "rake secret" and set the key in config/environment.rb.
|
||||
#
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CGI::Session::CookieStore
|
||||
# Cookies can typically store 4096 bytes.
|
||||
MAX = 4096
|
||||
SECRET_MIN_LENGTH = 30 # characters
|
||||
|
||||
# Raised when storing more than 4K of session data.
|
||||
class CookieOverflow < StandardError; end
|
||||
|
||||
# Raised when the cookie fails its integrity check.
|
||||
class TamperedWithCookie < StandardError; end
|
||||
|
||||
# Called from CGI::Session only.
|
||||
def initialize(session, options = {})
|
||||
# The session_key option is required.
|
||||
if options['session_key'].blank?
|
||||
raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
|
||||
end
|
||||
|
||||
# The secret option is required.
|
||||
ensure_secret_secure(options['secret'])
|
||||
|
||||
# Keep the session and its secret on hand so we can read and write cookies.
|
||||
@session, @secret = session, options['secret']
|
||||
|
||||
# Message digest defaults to SHA1.
|
||||
@digest = options['digest'] || 'SHA1'
|
||||
|
||||
# Default cookie options derived from session settings.
|
||||
@cookie_options = {
|
||||
'name' => options['session_key'],
|
||||
'path' => options['session_path'],
|
||||
'domain' => options['session_domain'],
|
||||
'expires' => options['session_expires'],
|
||||
'secure' => options['session_secure'],
|
||||
'http_only' => options['session_http_only']
|
||||
}
|
||||
|
||||
# Set no_hidden and no_cookies since the session id is unused and we
|
||||
# set our own data cookie.
|
||||
options['no_hidden'] = true
|
||||
options['no_cookies'] = true
|
||||
end
|
||||
|
||||
# To prevent users from using something insecure like "Password" we make sure that the
|
||||
# secret they've provided is at least 30 characters in length.
|
||||
def ensure_secret_secure(secret)
|
||||
# There's no way we can do this check if they've provided a proc for the
|
||||
# secret.
|
||||
return true if secret.is_a?(Proc)
|
||||
|
||||
if secret.blank?
|
||||
raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
|
||||
end
|
||||
|
||||
if secret.length < SECRET_MIN_LENGTH
|
||||
raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
|
||||
end
|
||||
end
|
||||
|
||||
# Restore session data from the cookie.
|
||||
def restore
|
||||
@original = read_cookie
|
||||
@data = unmarshal(@original) || {}
|
||||
end
|
||||
|
||||
# Wait until close to write the session data cookie.
|
||||
def update; end
|
||||
|
||||
# Write the session data cookie if it was loaded and has changed.
|
||||
def close
|
||||
if defined?(@data) && !@data.blank?
|
||||
updated = marshal(@data)
|
||||
raise CookieOverflow if updated.size > MAX
|
||||
write_cookie('value' => updated) unless updated == @original
|
||||
end
|
||||
end
|
||||
|
||||
# Delete the session data by setting an expired cookie with no data.
|
||||
def delete
|
||||
@data = nil
|
||||
clear_old_cookie_value
|
||||
write_cookie('value' => nil, 'expires' => 1.year.ago)
|
||||
end
|
||||
|
||||
# Generate the HMAC keyed message digest. Uses SHA1 by default.
|
||||
def generate_digest(data)
|
||||
key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
|
||||
end
|
||||
|
||||
private
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
|
||||
"#{data}--#{generate_digest(data)}"
|
||||
end
|
||||
|
||||
# Unmarshal cookie data to a hash and verify its integrity.
|
||||
def unmarshal(cookie)
|
||||
if cookie
|
||||
data, digest = cookie.split('--')
|
||||
|
||||
# Do two checks to transparently support old double-escaped data.
|
||||
unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
|
||||
delete
|
||||
raise TamperedWithCookie
|
||||
end
|
||||
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
end
|
||||
end
|
||||
|
||||
# Read the session data cookie.
|
||||
def read_cookie
|
||||
@session.cgi.cookies[@cookie_options['name']].first
|
||||
end
|
||||
|
||||
# CGI likes to make you hack.
|
||||
def write_cookie(options)
|
||||
cookie = CGI::Cookie.new(@cookie_options.merge(options))
|
||||
@session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
|
||||
end
|
||||
|
||||
# Clear cookie value so subsequent new_session doesn't reload old data.
|
||||
def clear_old_cookie_value
|
||||
@session.cgi.cookies[@cookie_options['name']].clear
|
||||
end
|
||||
end
|
||||
32
vendor/rails/actionpack/lib/action_controller/session/drb_server.rb
vendored
Executable file
32
vendor/rails/actionpack/lib/action_controller/session/drb_server.rb
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# This is a really simple session storage daemon, basically just a hash,
|
||||
# which is enabled for DRb access.
|
||||
|
||||
require 'drb'
|
||||
|
||||
session_hash = Hash.new
|
||||
session_hash.instance_eval { @mutex = Mutex.new }
|
||||
|
||||
class <<session_hash
|
||||
def []=(key, value)
|
||||
@mutex.synchronize do
|
||||
super(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DRb.start_service('druby://127.0.0.1:9192', session_hash)
|
||||
DRb.thread.join
|
||||
35
vendor/rails/actionpack/lib/action_controller/session/drb_store.rb
vendored
Normal file
35
vendor/rails/actionpack/lib/action_controller/session/drb_store.rb
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'drb'
|
||||
|
||||
class CGI #:nodoc:all
|
||||
class Session
|
||||
class DRbStore
|
||||
@@session_data = DRbObject.new(nil, 'druby://localhost:9192')
|
||||
|
||||
def initialize(session, option=nil)
|
||||
@session_id = session.session_id
|
||||
end
|
||||
|
||||
def restore
|
||||
@h = @@session_data[@session_id] || {}
|
||||
end
|
||||
|
||||
def update
|
||||
@@session_data[@session_id] = @h
|
||||
end
|
||||
|
||||
def close
|
||||
update
|
||||
end
|
||||
|
||||
def delete
|
||||
@@session_data.delete(@session_id)
|
||||
end
|
||||
|
||||
def data
|
||||
@@session_data[@session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb
vendored
Normal file
98
vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# cgi/session/memcached.rb - persistent storage of marshalled session data
|
||||
#
|
||||
# == Overview
|
||||
#
|
||||
# This file provides the CGI::Session::MemCache class, which builds
|
||||
# persistence of storage data on top of the MemCache library. See
|
||||
# cgi/session.rb for more details on session storage managers.
|
||||
#
|
||||
|
||||
begin
|
||||
require 'cgi/session'
|
||||
require_library_or_gem 'memcache'
|
||||
|
||||
class CGI
|
||||
class Session
|
||||
# MemCache-based session storage class.
|
||||
#
|
||||
# This builds upon the top-level MemCache class provided by the
|
||||
# library file memcache.rb. Session data is marshalled and stored
|
||||
# in a memcached cache.
|
||||
class MemCacheStore
|
||||
def check_id(id) #:nodoc:#
|
||||
/[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
|
||||
end
|
||||
|
||||
# Create a new CGI::Session::MemCache instance
|
||||
#
|
||||
# This constructor is used internally by CGI::Session. The
|
||||
# user does not generally need to call it directly.
|
||||
#
|
||||
# +session+ is the session for which this instance is being
|
||||
# created. The session id must only contain alphanumeric
|
||||
# characters; automatically generated session ids observe
|
||||
# this requirement.
|
||||
#
|
||||
# +options+ is a hash of options for the initializer. The
|
||||
# following options are recognized:
|
||||
#
|
||||
# cache:: an instance of a MemCache client to use as the
|
||||
# session cache.
|
||||
#
|
||||
# expires:: an expiry time value to use for session entries in
|
||||
# the session cache. +expires+ is interpreted in seconds
|
||||
# relative to the current time if it’s less than 60*60*24*30
|
||||
# (30 days), or as an absolute Unix time (e.g., Time#to_i) if
|
||||
# greater. If +expires+ is +0+, or not passed on +options+,
|
||||
# the entry will never expire.
|
||||
#
|
||||
# This session's memcache entry will be created if it does
|
||||
# not exist, or retrieved if it does.
|
||||
def initialize(session, options = {})
|
||||
id = session.session_id
|
||||
unless check_id(id)
|
||||
raise ArgumentError, "session_id '%s' is invalid" % id
|
||||
end
|
||||
@cache = options['cache'] || MemCache.new('localhost')
|
||||
@expires = options['expires'] || 0
|
||||
@session_key = "session:#{id}"
|
||||
@session_data = {}
|
||||
# Add this key to the store if haven't done so yet
|
||||
unless @cache.get(@session_key)
|
||||
@cache.add(@session_key, @session_data, @expires)
|
||||
end
|
||||
end
|
||||
|
||||
# Restore session state from the session's memcache entry.
|
||||
#
|
||||
# Returns the session state as a hash.
|
||||
def restore
|
||||
@session_data = @cache[@session_key] || {}
|
||||
end
|
||||
|
||||
# Save session state to the session's memcache entry.
|
||||
def update
|
||||
@cache.set(@session_key, @session_data, @expires)
|
||||
end
|
||||
|
||||
# Update and close the session's memcache entry.
|
||||
def close
|
||||
update
|
||||
end
|
||||
|
||||
# Delete the session's memcache entry.
|
||||
def delete
|
||||
@cache.delete(@session_key)
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
# MemCache wasn't available so neither can the store be
|
||||
end
|
||||
162
vendor/rails/actionpack/lib/action_controller/session_management.rb
vendored
Normal file
162
vendor/rails/actionpack/lib/action_controller/session_management.rb
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
require 'action_controller/session/cookie_store'
|
||||
require 'action_controller/session/drb_store'
|
||||
require 'action_controller/session/mem_cache_store'
|
||||
if Object.const_defined?(:ActiveRecord)
|
||||
require 'action_controller/session/active_record_store'
|
||||
end
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module SessionManagement #:nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
alias_method_chain :process, :session_management_support
|
||||
alias_method_chain :process_cleanup, :session_management_support
|
||||
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 (<tt>:cookie_store</tt>),
|
||||
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
|
||||
# <tt>:p_store</tt>, <tt>:drb_store</tt>, <tt>:mem_cache_store</tt>, or
|
||||
# <tt>:memory_store</tt>) or your own custom class.
|
||||
def session_store=(store)
|
||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
|
||||
store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
|
||||
end
|
||||
|
||||
# Returns the session store class currently used.
|
||||
def session_store
|
||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
|
||||
end
|
||||
|
||||
# Returns the hash used to configure the session. Example use:
|
||||
#
|
||||
# ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
|
||||
def session_options
|
||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
|
||||
end
|
||||
|
||||
# Specify how sessions ought to be managed for a subset of the actions on
|
||||
# the controller. Like filters, you can specify <tt>:only</tt> and
|
||||
# <tt>:except</tt> clauses to restrict the subset, otherwise options
|
||||
# apply to all actions on this controller.
|
||||
#
|
||||
# The session options are inheritable, as well, so if you specify them in
|
||||
# a parent controller, they apply to controllers that extend the parent.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # turn off session management for all actions.
|
||||
# session :off
|
||||
#
|
||||
# # turn off session management for all actions _except_ foo and bar.
|
||||
# session :off, :except => %w(foo bar)
|
||||
#
|
||||
# # turn off session management for only the foo and bar actions.
|
||||
# session :off, :only => %w(foo bar)
|
||||
#
|
||||
# # the session will only work over HTTPS, but only for the foo action
|
||||
# session :only => :foo, :session_secure => true
|
||||
#
|
||||
# # the session by default uses HttpOnly sessions for security reasons.
|
||||
# # this can be switched off.
|
||||
# session :only => :foo, :session_http_only => false
|
||||
#
|
||||
# # the session will only be disabled for 'foo', and only if it is
|
||||
# # requested as a web service
|
||||
# session :off, :only => :foo,
|
||||
# :if => Proc.new { |req| req.parameters[:ws] }
|
||||
#
|
||||
# # the session will be disabled for non html/ajax requests
|
||||
# session :off,
|
||||
# :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
|
||||
#
|
||||
# # turn the session back on, useful when it was turned off in the
|
||||
# # application controller, and you need it on in another controller
|
||||
# session :on
|
||||
#
|
||||
# All session options described for ActionController::Base.process_cgi
|
||||
# are valid arguments.
|
||||
def session(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
options[:disabled] = false if args.delete(:on)
|
||||
options[:disabled] = true if !args.empty?
|
||||
options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
|
||||
options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
|
||||
if options[:only] && options[:except]
|
||||
raise ArgumentError, "only one of either :only or :except are allowed"
|
||||
end
|
||||
|
||||
write_inheritable_array(:session_options, [options])
|
||||
end
|
||||
|
||||
# So we can declare session options in the Rails initializer.
|
||||
alias_method :session=, :session
|
||||
|
||||
def cached_session_options #:nodoc:
|
||||
@session_options ||= read_inheritable_attribute(:session_options) || []
|
||||
end
|
||||
|
||||
def session_options_for(request, action) #:nodoc:
|
||||
if (session_options = cached_session_options).empty?
|
||||
{}
|
||||
else
|
||||
options = {}
|
||||
|
||||
action = action.to_s
|
||||
session_options.each do |opts|
|
||||
next if opts[:if] && !opts[:if].call(request)
|
||||
if opts[:only] && opts[:only].include?(action)
|
||||
options.merge!(opts)
|
||||
elsif opts[:except] && !opts[:except].include?(action)
|
||||
options.merge!(opts)
|
||||
elsif !opts[:only] && !opts[:except]
|
||||
options.merge!(opts)
|
||||
end
|
||||
end
|
||||
|
||||
if options.empty? then options
|
||||
else
|
||||
options.delete :only
|
||||
options.delete :except
|
||||
options.delete :if
|
||||
options[:disabled] ? false : options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
set_session_options(request)
|
||||
process_without_session_management_support(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
private
|
||||
def set_session_options(request)
|
||||
request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
|
||||
end
|
||||
|
||||
def process_cleanup_with_session_management_support
|
||||
clear_persistent_model_associations
|
||||
process_cleanup_without_session_management_support
|
||||
end
|
||||
|
||||
# Clear cached associations in session data so they don't overflow
|
||||
# the database field. Only applies to ActiveRecordStore since there
|
||||
# is not a standard way to iterate over session data.
|
||||
def clear_persistent_model_associations #:doc:
|
||||
if defined?(@_session) && @_session.respond_to?(:data)
|
||||
session_data = @_session.data
|
||||
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
88
vendor/rails/actionpack/lib/action_controller/status_codes.rb
vendored
Normal file
88
vendor/rails/actionpack/lib/action_controller/status_codes.rb
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
module ActionController
|
||||
module StatusCodes #:nodoc:
|
||||
# Defines the standard HTTP status codes, by integer, with their
|
||||
# corresponding default message texts.
|
||||
# Source: http://www.iana.org/assignments/http-status-codes
|
||||
STATUS_CODES = {
|
||||
100 => "Continue",
|
||||
101 => "Switching Protocols",
|
||||
102 => "Processing",
|
||||
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
202 => "Accepted",
|
||||
203 => "Non-Authoritative Information",
|
||||
204 => "No Content",
|
||||
205 => "Reset Content",
|
||||
206 => "Partial Content",
|
||||
207 => "Multi-Status",
|
||||
226 => "IM Used",
|
||||
|
||||
300 => "Multiple Choices",
|
||||
301 => "Moved Permanently",
|
||||
302 => "Found",
|
||||
303 => "See Other",
|
||||
304 => "Not Modified",
|
||||
305 => "Use Proxy",
|
||||
307 => "Temporary Redirect",
|
||||
|
||||
400 => "Bad Request",
|
||||
401 => "Unauthorized",
|
||||
402 => "Payment Required",
|
||||
403 => "Forbidden",
|
||||
404 => "Not Found",
|
||||
405 => "Method Not Allowed",
|
||||
406 => "Not Acceptable",
|
||||
407 => "Proxy Authentication Required",
|
||||
408 => "Request Timeout",
|
||||
409 => "Conflict",
|
||||
410 => "Gone",
|
||||
411 => "Length Required",
|
||||
412 => "Precondition Failed",
|
||||
413 => "Request Entity Too Large",
|
||||
414 => "Request-URI Too Long",
|
||||
415 => "Unsupported Media Type",
|
||||
416 => "Requested Range Not Satisfiable",
|
||||
417 => "Expectation Failed",
|
||||
422 => "Unprocessable Entity",
|
||||
423 => "Locked",
|
||||
424 => "Failed Dependency",
|
||||
426 => "Upgrade Required",
|
||||
|
||||
500 => "Internal Server Error",
|
||||
501 => "Not Implemented",
|
||||
502 => "Bad Gateway",
|
||||
503 => "Service Unavailable",
|
||||
504 => "Gateway Timeout",
|
||||
505 => "HTTP Version Not Supported",
|
||||
507 => "Insufficient Storage",
|
||||
510 => "Not Extended"
|
||||
}
|
||||
|
||||
# Provides a symbol-to-fixnum lookup for converting a symbol (like
|
||||
# :created or :not_implemented) into its corresponding HTTP status
|
||||
# code (like 200 or 501).
|
||||
SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) do |hash, (code, message)|
|
||||
hash[message.gsub(/ /, "").underscore.to_sym] = code
|
||||
hash
|
||||
end
|
||||
|
||||
# Given a status parameter, determine whether it needs to be converted
|
||||
# to a string. If it is a fixnum, use the STATUS_CODES hash to lookup
|
||||
# the default message. If it is a symbol, use the SYMBOL_TO_STATUS_CODE
|
||||
# hash to convert it.
|
||||
def interpret_status(status)
|
||||
case status
|
||||
when Fixnum then
|
||||
"#{status} #{STATUS_CODES[status]}".strip
|
||||
when Symbol then
|
||||
interpret_status(SYMBOL_TO_STATUS_CODE[status] ||
|
||||
"500 Unknown Status #{status.inspect}")
|
||||
else
|
||||
status.to_s
|
||||
end
|
||||
end
|
||||
private :interpret_status
|
||||
|
||||
end
|
||||
end
|
||||
162
vendor/rails/actionpack/lib/action_controller/streaming.rb
vendored
Normal file
162
vendor/rails/actionpack/lib/action_controller/streaming.rb
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
module ActionController #:nodoc:
|
||||
# Methods for sending files and streams to the browser instead of rendering.
|
||||
module Streaming
|
||||
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. <tt>send_file(params[:path])</tt> allows a malicious user to
|
||||
# download any file on your server.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# Defaults to <tt>File.basename(path)</tt>.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
|
||||
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:stream</tt> - 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+.
|
||||
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
|
||||
# Defaults to 4096.
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
|
||||
# the URL, which is necessary for i18n filenames on certain browsers
|
||||
# (setting <tt>:filename</tt> overrides this option).
|
||||
# * <tt>:x_sendfile</tt> - 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]
|
||||
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
|
||||
|
||||
# Send binary data to the user as a file download. May set content type, apparent file name,
|
||||
# and specify whether to show data inline or download as an attachment.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:status</tt> - 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.
|
||||
def send_data(data, options = {}) #:doc:
|
||||
logger.info "Sending data #{options[:filename]}" if logger
|
||||
send_file_headers! options.merge(:length => data.size)
|
||||
@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]
|
||||
|
||||
headers.update(
|
||||
'Content-Length' => options[:length],
|
||||
'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers
|
||||
'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
|
||||
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
|
||||
end
|
||||
end
|
||||
end
|
||||
24
vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
vendored
Normal file
24
vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<% unless @exception.blamed_files.blank? %>
|
||||
<% if (hide = @exception.blamed_files.length > 8) %>
|
||||
<a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
|
||||
<% end %>
|
||||
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
clean_params = request.parameters.clone
|
||||
clean_params.delete("action")
|
||||
clean_params.delete("controller")
|
||||
|
||||
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
||||
%>
|
||||
|
||||
<h2 style="margin-top: 30px">Request</h2>
|
||||
<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
|
||||
|
||||
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
|
||||
<div id="session_dump" style="display:none"><%= debug(request.session.instance_variable_get("@data")) %></div>
|
||||
|
||||
|
||||
<h2 style="margin-top: 30px">Response</h2>
|
||||
<p><b>Headers</b>: <pre><%=h response ? response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
|
||||
26
vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb
vendored
Normal file
26
vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<%
|
||||
traces = [
|
||||
["Application Trace", @exception.application_backtrace],
|
||||
["Framework Trace", @exception.framework_backtrace],
|
||||
["Full Trace", @exception.clean_backtrace]
|
||||
]
|
||||
names = traces.collect {|name, trace| name}
|
||||
%>
|
||||
|
||||
<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p>
|
||||
|
||||
<div id="traces">
|
||||
<% names.each do |name| %>
|
||||
<%
|
||||
show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';"
|
||||
hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"}
|
||||
%>
|
||||
<a href="#" onclick="<%= hide %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
||||
<% end %>
|
||||
|
||||
<% traces.each do |name, trace| %>
|
||||
<div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;">
|
||||
<pre><code><%= trace.join "\n" %></code></pre>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
11
vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
vendored
Normal file
11
vendor/rails/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<h1>
|
||||
<%=h @exception.class.to_s %>
|
||||
<% if request.parameters['controller'] %>
|
||||
in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
29
vendor/rails/actionpack/lib/action_controller/templates/rescues/layout.erb
vendored
Normal file
29
vendor/rails/actionpack/lib/action_controller/templates/rescues/layout.erb
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Action Controller: Exception caught</title>
|
||||
<style>
|
||||
body { background-color: #fff; color: #333; }
|
||||
|
||||
body, p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a { color: #000; }
|
||||
a:visited { color: #666; }
|
||||
a:hover { color: #fff; background-color:#000; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%= @contents %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
2
vendor/rails/actionpack/lib/action_controller/templates/rescues/missing_template.erb
vendored
Normal file
2
vendor/rails/actionpack/lib/action_controller/templates/rescues/missing_template.erb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<h1>Template is missing</h1>
|
||||
<p><%=h @exception.message %></p>
|
||||
10
vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.erb
vendored
Normal file
10
vendor/rails/actionpack/lib/action_controller/templates/rescues/routing_error.erb
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<h1>Routing Error</h1>
|
||||
<p><pre><%=h @exception.message %></pre></p>
|
||||
<% unless @exception.failures.empty? %><p>
|
||||
<h2>Failure reasons:</h2>
|
||||
<ol>
|
||||
<% @exception.failures.each do |route, reason| %>
|
||||
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</p><% end %>
|
||||
21
vendor/rails/actionpack/lib/action_controller/templates/rescues/template_error.erb
vendored
Normal file
21
vendor/rails/actionpack/lib/action_controller/templates/rescues/template_error.erb
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<h1>
|
||||
<%=h @exception.original_exception.class.to_s %> in
|
||||
<%=h request.parameters["controller"].capitalize if request.parameters["controller"]%>#<%=h request.parameters["action"] %>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised:
|
||||
<pre><code><%=h @exception.message %></code></pre>
|
||||
</p>
|
||||
|
||||
<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>):
|
||||
<pre><code><%=h @exception.source_extract %></code></pre></p>
|
||||
|
||||
<p><%=h @exception.sub_template_message %></p>
|
||||
|
||||
<% @real_exception = @exception
|
||||
@exception = @exception.original_exception || @exception %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
<% @exception = @real_exception %>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
2
vendor/rails/actionpack/lib/action_controller/templates/rescues/unknown_action.erb
vendored
Normal file
2
vendor/rails/actionpack/lib/action_controller/templates/rescues/unknown_action.erb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<h1>Unknown action</h1>
|
||||
<p><%=h @exception.message %></p>
|
||||
147
vendor/rails/actionpack/lib/action_controller/test_case.rb
vendored
Normal file
147
vendor/rails/actionpack/lib/action_controller/test_case.rb
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
require 'active_support/test_case'
|
||||
|
||||
module ActionController
|
||||
class NonInferrableControllerError < ActionControllerError
|
||||
def initialize(name)
|
||||
@name = name
|
||||
super "Unable to determine the controller to test from #{name}. " +
|
||||
"You'll need to specify it using 'tests YourController' in your " +
|
||||
"test case definition. This could mean that #{inferred_controller_name} does not exist " +
|
||||
"or it contains syntax errors"
|
||||
end
|
||||
|
||||
def inferred_controller_name
|
||||
@name.sub(/Test$/, '')
|
||||
end
|
||||
end
|
||||
|
||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||
# test a single controller action per test method. This should not be confused with
|
||||
# integration tests (see ActionController::IntegrationTest), which are more like
|
||||
# "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
|
||||
# different HTTP requests).
|
||||
#
|
||||
# == Basic example
|
||||
#
|
||||
# Functional tests are written as follows:
|
||||
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
|
||||
# an HTTP request.
|
||||
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
|
||||
# the controller's HTTP response, the database contents, etc.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# class BooksControllerTest < ActionController::TestCase
|
||||
# def test_create
|
||||
# # Simulate a POST response with the given HTTP parameters.
|
||||
# post(:create, :book => { :title => "Love Hina" })
|
||||
#
|
||||
# # Assert that the controller tried to redirect us to
|
||||
# # the created book's URI.
|
||||
# assert_response :found
|
||||
#
|
||||
# # Assert that the controller really put the book in the database.
|
||||
# assert_not_nil Book.find_by_title("Love Hina")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Special instance variables
|
||||
#
|
||||
# ActionController::TestCase will also automatically provide the following instance
|
||||
# variables for use in the tests:
|
||||
#
|
||||
# <b>@controller</b>::
|
||||
# The controller instance that will be tested.
|
||||
# <b>@request</b>::
|
||||
# An ActionController::TestRequest, representing the current HTTP
|
||||
# request. You can modify this object before sending the HTTP request. For example,
|
||||
# you might want to set some session properties before sending a GET request.
|
||||
# <b>@response</b>::
|
||||
# An ActionController::TestResponse object, representing the response
|
||||
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
||||
# after calling +post+. If the various assert methods are not sufficient, then you
|
||||
# may use this object to inspect the HTTP response in detail.
|
||||
#
|
||||
# (Earlier versions of Rails required each functional test to subclass
|
||||
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
|
||||
#
|
||||
# == Controller is automatically inferred
|
||||
#
|
||||
# ActionController::TestCase will automatically infer the controller under test
|
||||
# from the test class name. If the controller cannot be inferred from the test
|
||||
# class name, you can explicity set it with +tests+.
|
||||
#
|
||||
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
||||
# tests WidgetController
|
||||
# end
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
||||
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
||||
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
||||
# than 0.0.0.0.
|
||||
#
|
||||
# The exception is stored in the exception accessor for further inspection.
|
||||
module RaiseActionExceptions
|
||||
attr_accessor :exception
|
||||
|
||||
def rescue_action_without_handler(e)
|
||||
self.exception = e
|
||||
|
||||
if request.remote_addr == "0.0.0.0"
|
||||
raise(e)
|
||||
else
|
||||
super(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setup :setup_controller_request_and_response
|
||||
|
||||
@@controller_class = nil
|
||||
|
||||
class << self
|
||||
# Sets the controller class name. Useful if the name can't be inferred from test class.
|
||||
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
|
||||
def tests(controller_class)
|
||||
self.controller_class = controller_class
|
||||
end
|
||||
|
||||
def controller_class=(new_class)
|
||||
prepare_controller_class(new_class)
|
||||
write_inheritable_attribute(:controller_class, new_class)
|
||||
end
|
||||
|
||||
def controller_class
|
||||
if current_controller_class = read_inheritable_attribute(:controller_class)
|
||||
current_controller_class
|
||||
else
|
||||
self.controller_class = determine_default_controller_class(name)
|
||||
end
|
||||
end
|
||||
|
||||
def determine_default_controller_class(name)
|
||||
name.sub(/Test$/, '').constantize
|
||||
rescue NameError
|
||||
raise NonInferrableControllerError.new(name)
|
||||
end
|
||||
|
||||
def prepare_controller_class(new_class)
|
||||
new_class.send :include, RaiseActionExceptions
|
||||
end
|
||||
end
|
||||
|
||||
def setup_controller_request_and_response
|
||||
@controller = self.class.controller_class.new
|
||||
@controller.request = @request = TestRequest.new
|
||||
@response = TestResponse.new
|
||||
|
||||
@controller.params = {}
|
||||
@controller.send(:initialize_current_url)
|
||||
end
|
||||
|
||||
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
||||
def rescue_action_in_public!
|
||||
@request.remote_addr = '208.77.188.166' # example.com
|
||||
end
|
||||
end
|
||||
end
|
||||
531
vendor/rails/actionpack/lib/action_controller/test_process.rb
vendored
Normal file
531
vendor/rails/actionpack/lib/action_controller/test_process.rb
vendored
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
require 'action_controller/assertions'
|
||||
require 'action_controller/test_case'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
attr_reader :assigns
|
||||
|
||||
# Process a test request called with a TestRequest object.
|
||||
def self.process_test(request)
|
||||
new.process_test(request)
|
||||
end
|
||||
|
||||
def process_test(request) #:nodoc:
|
||||
process(request, TestResponse.new)
|
||||
end
|
||||
|
||||
def process_with_test(*args)
|
||||
returning process_without_test(*args) do
|
||||
@assigns = {}
|
||||
(instance_variable_names - @@protected_instance_variables).each do |var|
|
||||
value = instance_variable_get(var)
|
||||
@assigns[var[1..-1]] = value
|
||||
response.template.assigns[var[1..-1]] = value if response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias_method_chain :process, :test
|
||||
end
|
||||
|
||||
class TestRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session
|
||||
attr_accessor :host, :user_agent
|
||||
|
||||
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
|
||||
@query_parameters = query_parameters || {}
|
||||
@request_parameters = request_parameters || {}
|
||||
@session = session || TestSession.new
|
||||
|
||||
initialize_containers
|
||||
initialize_default_values
|
||||
|
||||
super()
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@session = TestSession.new
|
||||
end
|
||||
|
||||
# Wraps raw_post in a StringIO.
|
||||
def body_stream #:nodoc:
|
||||
StringIO.new(raw_post)
|
||||
end
|
||||
|
||||
# Either the RAW_POST_DATA environment variable or the URL-encoded request
|
||||
# parameters.
|
||||
def raw_post
|
||||
env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) }
|
||||
end
|
||||
|
||||
def port=(number)
|
||||
@env["SERVER_PORT"] = number.to_i
|
||||
port(true)
|
||||
end
|
||||
|
||||
def action=(action_name)
|
||||
@query_parameters.update({ "action" => action_name })
|
||||
@parameters = nil
|
||||
end
|
||||
|
||||
# Used to check AbstractRequest's request_uri functionality.
|
||||
# Disables the use of @path and @request_uri so superclass can handle those.
|
||||
def set_REQUEST_URI(value)
|
||||
@env["REQUEST_URI"] = value
|
||||
@request_uri = nil
|
||||
@path = nil
|
||||
request_uri(true)
|
||||
path(true)
|
||||
end
|
||||
|
||||
def request_uri=(uri)
|
||||
@request_uri = uri
|
||||
@path = uri.split("?").first
|
||||
end
|
||||
|
||||
def accept=(mime_types)
|
||||
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
|
||||
accepts(true)
|
||||
end
|
||||
|
||||
def if_modified_since=(last_modified)
|
||||
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
|
||||
end
|
||||
|
||||
def if_none_match=(etag)
|
||||
@env["HTTP_IF_NONE_MATCH"] = etag
|
||||
end
|
||||
|
||||
def remote_addr=(addr)
|
||||
@env['REMOTE_ADDR'] = addr
|
||||
end
|
||||
|
||||
def request_uri(*args)
|
||||
@request_uri || super
|
||||
end
|
||||
|
||||
def path(*args)
|
||||
@path || super
|
||||
end
|
||||
|
||||
def assign_parameters(controller_path, action, parameters)
|
||||
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
|
||||
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
|
||||
non_path_parameters = get? ? query_parameters : request_parameters
|
||||
parameters.each do |key, value|
|
||||
if value.is_a? Fixnum
|
||||
value = value.to_s
|
||||
elsif value.is_a? Array
|
||||
value = ActionController::Routing::PathSegment::Result.new(value)
|
||||
end
|
||||
|
||||
if extra_keys.include?(key.to_sym)
|
||||
non_path_parameters[key] = value
|
||||
else
|
||||
path_parameters[key.to_s] = value
|
||||
end
|
||||
end
|
||||
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
|
||||
end
|
||||
|
||||
def recycle!
|
||||
self.request_parameters = {}
|
||||
self.query_parameters = {}
|
||||
self.path_parameters = {}
|
||||
unmemoize_all
|
||||
end
|
||||
|
||||
private
|
||||
def initialize_containers
|
||||
@env, @cookies = {}, {}
|
||||
end
|
||||
|
||||
def initialize_default_values
|
||||
@host = "test.host"
|
||||
@request_uri = "/"
|
||||
@user_agent = "Rails Testing"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
@env["SERVER_PORT"] = 80
|
||||
@env['REQUEST_METHOD'] = "GET"
|
||||
end
|
||||
|
||||
def url_encoded_request_parameters
|
||||
params = self.request_parameters.dup
|
||||
|
||||
%w(controller action only_path).each do |k|
|
||||
params.delete(k)
|
||||
params.delete(k.to_sym)
|
||||
end
|
||||
|
||||
params.to_query
|
||||
end
|
||||
end
|
||||
|
||||
# A refactoring of TestResponse to allow the same behavior to be applied
|
||||
# to the "real" CgiResponse class in integration tests.
|
||||
module TestResponseBehavior #:nodoc:
|
||||
# The response code of the request
|
||||
def response_code
|
||||
status[0,3].to_i rescue 0
|
||||
end
|
||||
|
||||
# Returns a String to ensure compatibility with Net::HTTPResponse
|
||||
def code
|
||||
status.to_s.split(' ')[0]
|
||||
end
|
||||
|
||||
def message
|
||||
status.to_s.split(' ',2)[1]
|
||||
end
|
||||
|
||||
# Was the response successful?
|
||||
def success?
|
||||
(200..299).include?(response_code)
|
||||
end
|
||||
|
||||
# Was the URL not found?
|
||||
def missing?
|
||||
response_code == 404
|
||||
end
|
||||
|
||||
# Were we redirected?
|
||||
def redirect?
|
||||
(300..399).include?(response_code)
|
||||
end
|
||||
|
||||
# Was there a server-side error?
|
||||
def error?
|
||||
(500..599).include?(response_code)
|
||||
end
|
||||
|
||||
alias_method :server_error?, :error?
|
||||
|
||||
# Returns the redirection location or nil
|
||||
def redirect_url
|
||||
headers['Location']
|
||||
end
|
||||
|
||||
# Does the redirect location match this regexp pattern?
|
||||
def redirect_url_match?( pattern )
|
||||
return false if redirect_url.nil?
|
||||
p = Regexp.new(pattern) if pattern.class == String
|
||||
p = pattern if pattern.class == Regexp
|
||||
return false if p.nil?
|
||||
p.match(redirect_url) != nil
|
||||
end
|
||||
|
||||
# Returns the template of the file which was used to
|
||||
# render this response (or nil)
|
||||
def rendered_template
|
||||
template.instance_variable_get(:@_first_render)
|
||||
end
|
||||
|
||||
# A shortcut to the flash. Returns an empty hash if no session flash exists.
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
end
|
||||
|
||||
# Do we have a flash?
|
||||
def has_flash?
|
||||
!session['flash'].empty?
|
||||
end
|
||||
|
||||
# Do we have a flash that has contents?
|
||||
def has_flash_with_contents?
|
||||
!flash.empty?
|
||||
end
|
||||
|
||||
# Does the specified flash object exist?
|
||||
def has_flash_object?(name=nil)
|
||||
!flash[name].nil?
|
||||
end
|
||||
|
||||
# Does the specified object exist in the session?
|
||||
def has_session_object?(name=nil)
|
||||
!session[name].nil?
|
||||
end
|
||||
|
||||
# A shortcut to the template.assigns
|
||||
def template_objects
|
||||
template.assigns || {}
|
||||
end
|
||||
|
||||
# Does the specified template object exist?
|
||||
def has_template_object?(name=nil)
|
||||
!template_objects[name].nil?
|
||||
end
|
||||
|
||||
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
|
||||
#
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
def cookies
|
||||
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
|
||||
end
|
||||
|
||||
# Returns binary content (downloadable file), converted to a String
|
||||
def binary_content
|
||||
raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc)
|
||||
require 'stringio'
|
||||
|
||||
sio = StringIO.new
|
||||
body.call(self, sio)
|
||||
|
||||
sio.rewind
|
||||
sio.read
|
||||
end
|
||||
end
|
||||
|
||||
# Integration test methods such as ActionController::Integration::Session#get
|
||||
# and ActionController::Integration::Session#post return objects of class
|
||||
# TestResponse, which represent the HTTP response results of the requested
|
||||
# controller actions.
|
||||
#
|
||||
# See AbstractResponse for more information on controller response objects.
|
||||
class TestResponse < AbstractResponse
|
||||
include TestResponseBehavior
|
||||
|
||||
def recycle!
|
||||
headers.delete('ETag')
|
||||
headers.delete('Last-Modified')
|
||||
end
|
||||
end
|
||||
|
||||
class TestSession #:nodoc:
|
||||
attr_accessor :session_id
|
||||
|
||||
def initialize(attributes = nil)
|
||||
@session_id = ''
|
||||
@attributes = attributes.nil? ? nil : attributes.stringify_keys
|
||||
@saved_attributes = nil
|
||||
end
|
||||
|
||||
def data
|
||||
@attributes ||= @saved_attributes || {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
data[key.to_s]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
data[key.to_s] = value
|
||||
end
|
||||
|
||||
def update
|
||||
@saved_attributes = @attributes
|
||||
end
|
||||
|
||||
def delete
|
||||
@attributes = nil
|
||||
end
|
||||
|
||||
def close
|
||||
update
|
||||
delete
|
||||
end
|
||||
end
|
||||
|
||||
# Essentially generates a modified Tempfile object similar to the object
|
||||
# you'd get from the standard library CGI module in a multipart
|
||||
# request. This means you can use an ActionController::TestUploadedFile
|
||||
# object in the params of a test request in order to simulate
|
||||
# a file upload.
|
||||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
#
|
||||
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
||||
require 'tempfile'
|
||||
class TestUploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
||||
# The content type of the "uploaded" file
|
||||
attr_accessor :content_type
|
||||
|
||||
def initialize(path, content_type = Mime::TEXT, binary = false)
|
||||
raise "#{path} file does not exist" unless File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
|
||||
@tempfile = Tempfile.new(@original_filename)
|
||||
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
||||
@tempfile.binmode if binary
|
||||
FileUtils.copy_file(path, @tempfile.path)
|
||||
end
|
||||
|
||||
def path #:nodoc:
|
||||
@tempfile.path
|
||||
end
|
||||
|
||||
alias local_path path
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
module TestProcess
|
||||
def self.included(base)
|
||||
# execute the request simulating a specific HTTP method and set/volley the response
|
||||
# TODO: this should be un-DRY'ed for the sake of API documentation.
|
||||
%w( get post put delete head ).each do |method|
|
||||
base.class_eval <<-EOV, __FILE__, __LINE__
|
||||
def #{method}(action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
|
||||
process(action, parameters, session, flash)
|
||||
end
|
||||
EOV
|
||||
end
|
||||
end
|
||||
|
||||
# execute the request and set/volley the response
|
||||
def process(action, parameters = nil, session = nil, flash = nil)
|
||||
# Sanity check for required instance variables so we can give an
|
||||
# understandable error message.
|
||||
%w(@controller @request @response).each do |iv_name|
|
||||
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
|
||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||
end
|
||||
end
|
||||
|
||||
@request.recycle!
|
||||
@response.recycle!
|
||||
|
||||
@html_document = nil
|
||||
@request.env['REQUEST_METHOD'] ||= "GET"
|
||||
|
||||
@request.action = action.to_s
|
||||
|
||||
parameters ||= {}
|
||||
@request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
|
||||
|
||||
@request.session = ActionController::TestSession.new(session) unless session.nil?
|
||||
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
|
||||
build_request_uri(action, parameters)
|
||||
@controller.process(@request, @response)
|
||||
end
|
||||
|
||||
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
||||
@request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
returning __send__(request_method, action, parameters, session, flash) do
|
||||
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
||||
@request.env.delete 'HTTP_ACCEPT'
|
||||
end
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
def assigns(key = nil)
|
||||
if key.nil?
|
||||
@response.template.assigns
|
||||
else
|
||||
@response.template.assigns[key.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def session
|
||||
@response.session
|
||||
end
|
||||
|
||||
def flash
|
||||
@response.flash
|
||||
end
|
||||
|
||||
def cookies
|
||||
@response.cookies
|
||||
end
|
||||
|
||||
def redirect_to_url
|
||||
@response.redirect_url
|
||||
end
|
||||
|
||||
def build_request_uri(action, parameters)
|
||||
unless @request.env['REQUEST_URI']
|
||||
options = @controller.__send__(:rewrite_options, parameters)
|
||||
options.update(:only_path => true, :action => action)
|
||||
|
||||
url = ActionController::UrlRewriter.new(@request, parameters)
|
||||
@request.set_REQUEST_URI(url.rewrite(options))
|
||||
end
|
||||
end
|
||||
|
||||
def html_document
|
||||
xml = @response.content_type =~ /xml$/
|
||||
@html_document ||= HTML::Document.new(@response.body, false, xml)
|
||||
end
|
||||
|
||||
def find_tag(conditions)
|
||||
html_document.find(conditions)
|
||||
end
|
||||
|
||||
def find_all_tag(conditions)
|
||||
html_document.find_all(conditions)
|
||||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
@controller.send(selector, *args)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
|
||||
#
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
|
||||
#
|
||||
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
|
||||
# This will not affect other platforms:
|
||||
#
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
|
||||
def fixture_file_upload(path, mime_type = nil, binary = false)
|
||||
ActionController::TestUploadedFile.new(
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
mime_type,
|
||||
binary
|
||||
)
|
||||
end
|
||||
|
||||
# A helper to make it easier to test different route configurations.
|
||||
# This method temporarily replaces ActionController::Routing::Routes
|
||||
# with a new RouteSet instance.
|
||||
#
|
||||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using <tt>map.draw { map.connect ... }</tt>:
|
||||
#
|
||||
# with_routing do |set|
|
||||
# set.draw do |map|
|
||||
# map.connect ':controller/:action/:id'
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# map.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def with_routing
|
||||
real_routes = ActionController::Routing::Routes
|
||||
ActionController::Routing.module_eval { remove_const :Routes }
|
||||
|
||||
temporary_routes = ActionController::Routing::RouteSet.new
|
||||
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
|
||||
|
||||
yield temporary_routes
|
||||
ensure
|
||||
if ActionController::Routing.const_defined? :Routes
|
||||
ActionController::Routing.module_eval { remove_const :Routes }
|
||||
end
|
||||
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase #:nodoc:
|
||||
include ActionController::TestProcess
|
||||
end
|
||||
end
|
||||
end
|
||||
13
vendor/rails/actionpack/lib/action_controller/translation.rb
vendored
Normal file
13
vendor/rails/actionpack/lib/action_controller/translation.rb
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module ActionController
|
||||
module Translation
|
||||
def translate(*args)
|
||||
I18n.translate *args
|
||||
end
|
||||
alias :t :translate
|
||||
|
||||
def localize(*args)
|
||||
I18n.localize *args
|
||||
end
|
||||
alias :l :localize
|
||||
end
|
||||
end
|
||||
219
vendor/rails/actionpack/lib/action_controller/url_rewriter.rb
vendored
Normal file
219
vendor/rails/actionpack/lib/action_controller/url_rewriter.rb
vendored
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
module ActionController
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# See ActionController::Routing and ActionController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlWriter is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlWriter under the hood. And in particular,
|
||||
# they use the ActionController::UrlWriter#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlWriter also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that in two ways.
|
||||
#
|
||||
# The first way is to include ActionController::UrlWriter in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlWriter # !!!
|
||||
#
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# write_attribute('base_uri', users_path) # !!!
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The second way is to access them through ActionController::UrlWriter.
|
||||
# The autogenerated named routes methods are available as class methods:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# path = ActionController::UrlWriter.users_path # !!!
|
||||
# write_attribute('base_uri', path) # !!!
|
||||
# end
|
||||
# end
|
||||
module UrlWriter
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt>
|
||||
# pair is provided.
|
||||
mattr_accessor :default_url_options
|
||||
self.default_url_options = {}
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.install_helpers(base)
|
||||
base.mattr_accessor :default_url_options
|
||||
base.default_url_options ||= default_url_options
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targetted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in ActionController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
|
||||
unless options.delete(:only_path)
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://' unless url.match("://")
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
url << options.delete(:host)
|
||||
url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
else
|
||||
# Delete the unused options to prevent their appearance in the query string.
|
||||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
url << anchor if anchor
|
||||
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
|
||||
class UrlRewriter #:nodoc:
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
def rewrite(options = {})
|
||||
rewrite_url(options)
|
||||
end
|
||||
|
||||
def to_str
|
||||
"#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
|
||||
end
|
||||
|
||||
alias_method :to_s, :to_str
|
||||
|
||||
private
|
||||
# Given a path and options, returns a rewritten URL string
|
||||
def rewrite_url(options)
|
||||
rewritten_url = ""
|
||||
|
||||
unless options[:only_path]
|
||||
rewritten_url << (options[:protocol] || @request.protocol)
|
||||
rewritten_url << "://" unless rewritten_url.match("://")
|
||||
rewritten_url << rewrite_authentication(options)
|
||||
rewritten_url << (options[:host] || @request.host_with_port)
|
||||
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
end
|
||||
|
||||
path = rewrite_path(options)
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
||||
|
||||
rewritten_url
|
||||
end
|
||||
|
||||
# Given a Hash of options, generates a route
|
||||
def rewrite_path(options)
|
||||
options = options.symbolize_keys
|
||||
options.update(options[:params].symbolize_keys) if options[:params]
|
||||
|
||||
if (overwrite = options.delete(:overwrite_params))
|
||||
options.update(@parameters.symbolize_keys)
|
||||
options.update(overwrite.symbolize_keys)
|
||||
end
|
||||
|
||||
RESERVED_OPTIONS.each { |k| options.delete(k) }
|
||||
|
||||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
end
|
||||
|
||||
def rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
68
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
vendored
Normal file
68
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
require 'html/tokenizer'
|
||||
require 'html/node'
|
||||
require 'html/selector'
|
||||
require 'html/sanitizer'
|
||||
|
||||
module HTML #:nodoc:
|
||||
# A top-level HTMl document. You give it a body of text, and it will parse that
|
||||
# text into a tree of nodes.
|
||||
class Document #:nodoc:
|
||||
|
||||
# The root of the parsed document.
|
||||
attr_reader :root
|
||||
|
||||
# Create a new Document from the given text.
|
||||
def initialize(text, strict=false, xml=false)
|
||||
tokenizer = Tokenizer.new(text)
|
||||
@root = Node.new(nil)
|
||||
node_stack = [ @root ]
|
||||
while token = tokenizer.next
|
||||
node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
|
||||
|
||||
node_stack.last.children << node unless node.tag? && node.closing == :close
|
||||
if node.tag?
|
||||
if node_stack.length > 1 && node.closing == :close
|
||||
if node_stack.last.name == node.name
|
||||
if node_stack.last.children.empty?
|
||||
node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
|
||||
end
|
||||
node_stack.pop
|
||||
else
|
||||
open_start = node_stack.last.position - 20
|
||||
open_start = 0 if open_start < 0
|
||||
close_start = node.position - 20
|
||||
close_start = 0 if close_start < 0
|
||||
msg = <<EOF.strip
|
||||
ignoring attempt to close #{node_stack.last.name} with #{node.name}
|
||||
opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
|
||||
closed at byte #{node.position}, line #{node.line}
|
||||
attributes at open: #{node_stack.last.attributes.inspect}
|
||||
text around open: #{text[open_start,40].inspect}
|
||||
text around close: #{text[close_start,40].inspect}
|
||||
EOF
|
||||
strict ? raise(msg) : warn(msg)
|
||||
end
|
||||
elsif !node.childless?(xml) && node.closing != :close
|
||||
node_stack.push node
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Search the tree for (and return) the first node that matches the given
|
||||
# conditions. The conditions are interpreted differently for different node
|
||||
# types, see HTML::Text#find and HTML::Tag#find.
|
||||
def find(conditions)
|
||||
@root.find(conditions)
|
||||
end
|
||||
|
||||
# Search the tree for (and return) all nodes that match the given
|
||||
# conditions. The conditions are interpreted differently for different node
|
||||
# types, see HTML::Text#find and HTML::Tag#find.
|
||||
def find_all(conditions)
|
||||
@root.find_all(conditions)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
537
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
vendored
Normal file
537
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
vendored
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
require 'strscan'
|
||||
|
||||
module HTML #:nodoc:
|
||||
|
||||
class Conditions < Hash #:nodoc:
|
||||
def initialize(hash)
|
||||
super()
|
||||
hash = { :content => hash } unless Hash === hash
|
||||
hash = keys_to_symbols(hash)
|
||||
hash.each do |k,v|
|
||||
case k
|
||||
when :tag, :content then
|
||||
# keys are valid, and require no further processing
|
||||
when :attributes then
|
||||
hash[k] = keys_to_strings(v)
|
||||
when :parent, :child, :ancestor, :descendant, :sibling, :before,
|
||||
:after
|
||||
hash[k] = Conditions.new(v)
|
||||
when :children
|
||||
hash[k] = v = keys_to_symbols(v)
|
||||
v.each do |k,v2|
|
||||
case k
|
||||
when :count, :greater_than, :less_than
|
||||
# keys are valid, and require no further processing
|
||||
when :only
|
||||
v[k] = Conditions.new(v2)
|
||||
else
|
||||
raise "illegal key #{k.inspect} => #{v2.inspect}"
|
||||
end
|
||||
end
|
||||
else
|
||||
raise "illegal key #{k.inspect} => #{v.inspect}"
|
||||
end
|
||||
end
|
||||
update hash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def keys_to_strings(hash)
|
||||
hash.keys.inject({}) do |h,k|
|
||||
h[k.to_s] = hash[k]
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
def keys_to_symbols(hash)
|
||||
hash.keys.inject({}) do |h,k|
|
||||
raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
|
||||
h[k.to_sym] = hash[k]
|
||||
h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The base class of all nodes, textual and otherwise, in an HTML document.
|
||||
class Node #:nodoc:
|
||||
# The array of children of this node. Not all nodes have children.
|
||||
attr_reader :children
|
||||
|
||||
# The parent node of this node. All nodes have a parent, except for the
|
||||
# root node.
|
||||
attr_reader :parent
|
||||
|
||||
# The line number of the input where this node was begun
|
||||
attr_reader :line
|
||||
|
||||
# The byte position in the input where this node was begun
|
||||
attr_reader :position
|
||||
|
||||
# Create a new node as a child of the given parent.
|
||||
def initialize(parent, line=0, pos=0)
|
||||
@parent = parent
|
||||
@children = []
|
||||
@line, @position = line, pos
|
||||
end
|
||||
|
||||
# Return a textual representation of the node.
|
||||
def to_s
|
||||
s = ""
|
||||
@children.each { |child| s << child.to_s }
|
||||
s
|
||||
end
|
||||
|
||||
# Return false (subclasses must override this to provide specific matching
|
||||
# behavior.) +conditions+ may be of any type.
|
||||
def match(conditions)
|
||||
false
|
||||
end
|
||||
|
||||
# Search the children of this node for the first node for which #find
|
||||
# returns non +nil+. Returns the result of the #find call that succeeded.
|
||||
def find(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
@children.each do |child|
|
||||
node = child.find(conditions)
|
||||
return node if node
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Search for all nodes that match the given conditions, and return them
|
||||
# as an array.
|
||||
def find_all(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
matches = []
|
||||
matches << self if match(conditions)
|
||||
@children.each do |child|
|
||||
matches.concat child.find_all(conditions)
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
# Returns +false+. Subclasses may override this if they define a kind of
|
||||
# tag.
|
||||
def tag?
|
||||
false
|
||||
end
|
||||
|
||||
def validate_conditions(conditions)
|
||||
Conditions === conditions ? conditions : Conditions.new(conditions)
|
||||
end
|
||||
|
||||
def ==(node)
|
||||
return false unless self.class == node.class && children.size == node.children.size
|
||||
|
||||
equivalent = true
|
||||
|
||||
children.size.times do |i|
|
||||
equivalent &&= children[i] == node.children[i]
|
||||
end
|
||||
|
||||
equivalent
|
||||
end
|
||||
|
||||
class <<self
|
||||
def parse(parent, line, pos, content, strict=true)
|
||||
if content !~ /^<\S/
|
||||
Text.new(parent, line, pos, content)
|
||||
else
|
||||
scanner = StringScanner.new(content)
|
||||
|
||||
unless scanner.skip(/</)
|
||||
if strict
|
||||
raise "expected <"
|
||||
else
|
||||
return Text.new(parent, line, pos, content)
|
||||
end
|
||||
end
|
||||
|
||||
if scanner.skip(/!\[CDATA\[/)
|
||||
unless scanner.skip_until(/\]\]>/)
|
||||
if strict
|
||||
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
|
||||
else
|
||||
scanner.skip_until(/\Z/)
|
||||
end
|
||||
end
|
||||
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :close : nil )
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
|
||||
name.downcase!
|
||||
|
||||
unless closing
|
||||
scanner.skip(/\s*/)
|
||||
attributes = {}
|
||||
while attr = scanner.scan(/[-\w:]+/)
|
||||
value = true
|
||||
if scanner.scan(/\s*=\s*/)
|
||||
if delim = scanner.scan(/['"]/)
|
||||
value = ""
|
||||
while text = scanner.scan(/[^#{delim}\\]+|./)
|
||||
case text
|
||||
when "\\" then
|
||||
value << text
|
||||
value << scanner.getch
|
||||
when delim
|
||||
break
|
||||
else value << text
|
||||
end
|
||||
end
|
||||
else
|
||||
value = scanner.scan(/[^\s>\/]+/)
|
||||
end
|
||||
end
|
||||
attributes[attr.downcase] = value
|
||||
scanner.skip(/\s*/)
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :self : nil )
|
||||
end
|
||||
|
||||
unless scanner.scan(/\s*>/)
|
||||
if strict
|
||||
raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
|
||||
else
|
||||
# throw away all text until we find what we're looking for
|
||||
scanner.skip_until(/>/) or scanner.terminate
|
||||
end
|
||||
end
|
||||
|
||||
Tag.new(parent, line, pos, name, attributes, closing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A node that represents text, rather than markup.
|
||||
class Text < Node #:nodoc:
|
||||
|
||||
attr_reader :content
|
||||
|
||||
# Creates a new text node as a child of the given parent, with the given
|
||||
# content.
|
||||
def initialize(parent, line, pos, content)
|
||||
super(parent, line, pos)
|
||||
@content = content
|
||||
end
|
||||
|
||||
# Returns the content of this node.
|
||||
def to_s
|
||||
@content
|
||||
end
|
||||
|
||||
# Returns +self+ if this node meets the given conditions. Text nodes support
|
||||
# conditions of the following kinds:
|
||||
#
|
||||
# * if +conditions+ is a string, it must be a substring of the node's
|
||||
# content
|
||||
# * if +conditions+ is a regular expression, it must match the node's
|
||||
# content
|
||||
# * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
|
||||
# is either a string or a regexp, and which is interpreted as described
|
||||
# above.
|
||||
def find(conditions)
|
||||
match(conditions) && self
|
||||
end
|
||||
|
||||
# Returns non-+nil+ if this node meets the given conditions, or +nil+
|
||||
# otherwise. See the discussion of #find for the valid conditions.
|
||||
def match(conditions)
|
||||
case conditions
|
||||
when String
|
||||
@content == conditions
|
||||
when Regexp
|
||||
@content =~ conditions
|
||||
when Hash
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
# Text nodes only have :content, :parent, :ancestor
|
||||
unless (conditions.keys - [:content, :parent, :ancestor]).empty?
|
||||
return false
|
||||
end
|
||||
|
||||
match(conditions[:content])
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def ==(node)
|
||||
return false unless super
|
||||
content == node.content
|
||||
end
|
||||
end
|
||||
|
||||
# A CDATA node is simply a text node with a specialized way of displaying
|
||||
# itself.
|
||||
class CDATA < Text #:nodoc:
|
||||
def to_s
|
||||
"<![CDATA[#{super}]]>"
|
||||
end
|
||||
end
|
||||
|
||||
# A Tag is any node that represents markup. It may be an opening tag, a
|
||||
# closing tag, or a self-closing tag. It has a name, and may have a hash of
|
||||
# attributes.
|
||||
class Tag < Node #:nodoc:
|
||||
|
||||
# Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
|
||||
attr_reader :closing
|
||||
|
||||
# Either +nil+, or a hash of attributes for this node.
|
||||
attr_reader :attributes
|
||||
|
||||
# The name of this tag.
|
||||
attr_reader :name
|
||||
|
||||
# Create a new node as a child of the given parent, using the given content
|
||||
# to describe the node. It will be parsed and the node name, attributes and
|
||||
# closing status extracted.
|
||||
def initialize(parent, line, pos, name, attributes, closing)
|
||||
super(parent, line, pos)
|
||||
@name = name
|
||||
@attributes = attributes
|
||||
@closing = closing
|
||||
end
|
||||
|
||||
# A convenience for obtaining an attribute of the node. Returns +nil+ if
|
||||
# the node has no attributes.
|
||||
def [](attr)
|
||||
@attributes ? @attributes[attr] : nil
|
||||
end
|
||||
|
||||
# Returns non-+nil+ if this tag can contain child nodes.
|
||||
def childless?(xml = false)
|
||||
return false if xml && @closing.nil?
|
||||
!@closing.nil? ||
|
||||
@name =~ /^(img|br|hr|link|meta|area|base|basefont|
|
||||
col|frame|input|isindex|param)$/ox
|
||||
end
|
||||
|
||||
# Returns a textual representation of the node
|
||||
def to_s
|
||||
if @closing == :close
|
||||
"</#{@name}>"
|
||||
else
|
||||
s = "<#{@name}"
|
||||
@attributes.each do |k,v|
|
||||
s << " #{k}"
|
||||
s << "=\"#{v}\"" if String === v
|
||||
end
|
||||
s << " /" if @closing == :self
|
||||
s << ">"
|
||||
@children.each { |child| s << child.to_s }
|
||||
s << "</#{@name}>" if @closing != :self && !@children.empty?
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
# If either the node or any of its children meet the given conditions, the
|
||||
# matching node is returned. Otherwise, +nil+ is returned. (See the
|
||||
# description of the valid conditions in the +match+ method.)
|
||||
def find(conditions)
|
||||
match(conditions) && self || super
|
||||
end
|
||||
|
||||
# Returns +true+, indicating that this node represents an HTML tag.
|
||||
def tag?
|
||||
true
|
||||
end
|
||||
|
||||
# Returns +true+ if the node meets any of the given conditions. The
|
||||
# +conditions+ parameter must be a hash of any of the following keys
|
||||
# (all are optional):
|
||||
#
|
||||
# * <tt>:tag</tt>: the node name must match the corresponding value
|
||||
# * <tt>:attributes</tt>: a hash. The node's values must match the
|
||||
# corresponding values in the hash.
|
||||
# * <tt>:parent</tt>: a hash. The node's parent must match the
|
||||
# corresponding hash.
|
||||
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
|
||||
# keys:
|
||||
# ** <tt>:count</tt>: either a number or a range which must equal (or
|
||||
# include) the number of children that match.
|
||||
# ** <tt>:less_than</tt>: the number of matching children must be less than
|
||||
# this number.
|
||||
# ** <tt>:greater_than</tt>: the number of matching children must be
|
||||
# greater than this number.
|
||||
# ** <tt>:only</tt>: another hash consisting of the keys to use
|
||||
# to match on the children, and only matching children will be
|
||||
# counted.
|
||||
#
|
||||
# Conditions are matched using the following algorithm:
|
||||
#
|
||||
# * if the condition is a string, it must be a substring of the value.
|
||||
# * if the condition is a regexp, it must match the value.
|
||||
# * if the condition is a number, the value must match number.to_s.
|
||||
# * if the condition is +true+, the value must not be +nil+.
|
||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # test if the node is a "span" tag
|
||||
# node.match :tag => "span"
|
||||
#
|
||||
# # test if the node's parent is a "div"
|
||||
# node.match :parent => { :tag => "div" }
|
||||
#
|
||||
# # test if any of the node's ancestors are "table" tags
|
||||
# node.match :ancestor => { :tag => "table" }
|
||||
#
|
||||
# # test if any of the node's immediate children are "em" tags
|
||||
# node.match :child => { :tag => "em" }
|
||||
#
|
||||
# # test if any of the node's descendants are "strong" tags
|
||||
# node.match :descendant => { :tag => "strong" }
|
||||
#
|
||||
# # test if the node has between 2 and 4 span tags as immediate children
|
||||
# node.match :children => { :count => 2..4, :only => { :tag => "span" } }
|
||||
#
|
||||
# # get funky: test to see if the node is a "div", has a "ul" ancestor
|
||||
# # and an "li" parent (with "class" = "enum"), and whether or not it has
|
||||
# # a "span" descendant that contains # text matching /hello world/:
|
||||
# node.match :tag => "div",
|
||||
# :ancestor => { :tag => "ul" },
|
||||
# :parent => { :tag => "li",
|
||||
# :attributes => { :class => "enum" } },
|
||||
# :descendant => { :tag => "span",
|
||||
# :child => /hello world/ }
|
||||
def match(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
# check content of child nodes
|
||||
if conditions[:content]
|
||||
if children.empty?
|
||||
return false unless match_condition("", conditions[:content])
|
||||
else
|
||||
return false unless children.find { |child| child.match(conditions[:content]) }
|
||||
end
|
||||
end
|
||||
|
||||
# test the name
|
||||
return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
|
||||
|
||||
# test attributes
|
||||
(conditions[:attributes] || {}).each do |key, value|
|
||||
return false unless match_condition(self[key], value)
|
||||
end
|
||||
|
||||
# test parent
|
||||
return false unless parent.match(conditions[:parent]) if conditions[:parent]
|
||||
|
||||
# test children
|
||||
return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
|
||||
|
||||
# test ancestors
|
||||
if conditions[:ancestor]
|
||||
return false unless catch :found do
|
||||
p = self
|
||||
throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
|
||||
end
|
||||
end
|
||||
|
||||
# test descendants
|
||||
if conditions[:descendant]
|
||||
return false unless children.find do |child|
|
||||
# test the child
|
||||
child.match(conditions[:descendant]) ||
|
||||
# test the child's descendants
|
||||
child.match(:descendant => conditions[:descendant])
|
||||
end
|
||||
end
|
||||
|
||||
# count children
|
||||
if opts = conditions[:children]
|
||||
matches = children.select do |c|
|
||||
(c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
|
||||
end
|
||||
|
||||
matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
|
||||
opts.each do |key, value|
|
||||
next if key == :only
|
||||
case key
|
||||
when :count
|
||||
if Integer === value
|
||||
return false if matches.length != value
|
||||
else
|
||||
return false unless value.include?(matches.length)
|
||||
end
|
||||
when :less_than
|
||||
return false unless matches.length < value
|
||||
when :greater_than
|
||||
return false unless matches.length > value
|
||||
else raise "unknown count condition #{key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# test siblings
|
||||
if conditions[:sibling] || conditions[:before] || conditions[:after]
|
||||
siblings = parent ? parent.children : []
|
||||
self_index = siblings.index(self)
|
||||
|
||||
if conditions[:sibling]
|
||||
return false unless siblings.detect do |s|
|
||||
s != self && s.match(conditions[:sibling])
|
||||
end
|
||||
end
|
||||
|
||||
if conditions[:before]
|
||||
return false unless siblings[self_index+1..-1].detect do |s|
|
||||
s != self && s.match(conditions[:before])
|
||||
end
|
||||
end
|
||||
|
||||
if conditions[:after]
|
||||
return false unless siblings[0,self_index].detect do |s|
|
||||
s != self && s.match(conditions[:after])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ==(node)
|
||||
return false unless super
|
||||
return false unless closing == node.closing && self.name == node.name
|
||||
attributes == node.attributes
|
||||
end
|
||||
|
||||
private
|
||||
# Match the given value to the given condition.
|
||||
def match_condition(value, condition)
|
||||
case condition
|
||||
when String
|
||||
value && value == condition
|
||||
when Regexp
|
||||
value && value.match(condition)
|
||||
when Numeric
|
||||
value == condition.to_s
|
||||
when true
|
||||
!value.nil?
|
||||
when false, nil
|
||||
value.nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
173
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
vendored
Normal file
173
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
module HTML
|
||||
class Sanitizer
|
||||
def sanitize(text, options = {})
|
||||
return text unless sanitizeable?(text)
|
||||
tokenize(text, options).join
|
||||
end
|
||||
|
||||
def sanitizeable?(text)
|
||||
!(text.nil? || text.empty? || !text.index("<"))
|
||||
end
|
||||
|
||||
protected
|
||||
def tokenize(text, options)
|
||||
tokenizer = HTML::Tokenizer.new(text)
|
||||
result = []
|
||||
while token = tokenizer.next
|
||||
node = Node.parse(nil, 0, 0, token, false)
|
||||
process_node node, result, options
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class FullSanitizer < Sanitizer
|
||||
def sanitize(text, options = {})
|
||||
result = super
|
||||
# strip any comments, and if they have a newline at the end (ie. line with
|
||||
# only a comment) strip that too
|
||||
result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result
|
||||
# Recurse - handle all dirty nested tags
|
||||
result == text ? result : sanitize(result, options)
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s if node.class == HTML::Text
|
||||
end
|
||||
end
|
||||
|
||||
class LinkSanitizer < FullSanitizer
|
||||
cattr_accessor :included_tags, :instance_writer => false
|
||||
self.included_tags = Set.new(%w(a href))
|
||||
|
||||
def sanitizeable?(text)
|
||||
!(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
|
||||
end
|
||||
|
||||
protected
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
|
||||
end
|
||||
end
|
||||
|
||||
class WhiteListSanitizer < Sanitizer
|
||||
[:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
|
||||
:allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
|
||||
class_inheritable_accessor attr, :instance_writer => false
|
||||
end
|
||||
|
||||
# A regular expression of the valid characters used to separate protocols like
|
||||
# the ':' in 'http://foo.com'
|
||||
self.protocol_separator = /:|(�*58)|(p)|(%|%)3A/
|
||||
|
||||
# Specifies a Set of HTML attributes that can have URIs.
|
||||
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
|
||||
|
||||
# Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
|
||||
# to just escaping harmless tags like <font>
|
||||
self.bad_tags = Set.new(%w(script))
|
||||
|
||||
# Specifies the default Set of tags that the #sanitize helper will allow unscathed.
|
||||
self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
|
||||
sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd abbr
|
||||
acronym a img blockquote del ins))
|
||||
|
||||
# Specifies the default Set of html attributes that the #sanitize helper will leave
|
||||
# in the allowed tag.
|
||||
self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
|
||||
|
||||
# Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
|
||||
feed svn urn aim rsync tag ssh sftp rtsp afs))
|
||||
|
||||
# Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
|
||||
border-color border-left-color border-right-color border-top-color clear color cursor direction display
|
||||
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
|
||||
overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
|
||||
speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
|
||||
width))
|
||||
|
||||
# Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
|
||||
collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
|
||||
nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
|
||||
|
||||
# Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
|
||||
self.shorthand_css_properties = Set.new(%w(background border margin padding))
|
||||
|
||||
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
|
||||
def sanitize_css(style)
|
||||
# disallow urls
|
||||
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
|
||||
|
||||
# gauntlet
|
||||
if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
|
||||
style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
|
||||
return ''
|
||||
end
|
||||
|
||||
clean = []
|
||||
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
|
||||
if allowed_css_properties.include?(prop.downcase)
|
||||
clean << prop + ': ' + val + ';'
|
||||
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
|
||||
unless val.split().any? do |keyword|
|
||||
!allowed_css_keywords.include?(keyword) &&
|
||||
keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
|
||||
end
|
||||
clean << prop + ': ' + val + ';'
|
||||
end
|
||||
end
|
||||
end
|
||||
clean.join(' ')
|
||||
end
|
||||
|
||||
protected
|
||||
def tokenize(text, options)
|
||||
options[:parent] = []
|
||||
options[:attributes] ||= allowed_attributes
|
||||
options[:tags] ||= allowed_tags
|
||||
super
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << case node
|
||||
when HTML::Tag
|
||||
if node.closing == :close
|
||||
options[:parent].shift
|
||||
else
|
||||
options[:parent].unshift node.name
|
||||
end
|
||||
|
||||
process_attributes_for node, options
|
||||
|
||||
options[:tags].include?(node.name) ? node : nil
|
||||
else
|
||||
bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "<")
|
||||
end
|
||||
end
|
||||
|
||||
def process_attributes_for(node, options)
|
||||
return unless node.attributes
|
||||
node.attributes.keys.each do |attr_name|
|
||||
value = node.attributes[attr_name].to_s
|
||||
|
||||
if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
|
||||
node.attributes.delete(attr_name)
|
||||
else
|
||||
node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def contains_bad_protocols?(attr_name, value)
|
||||
uri_attributes.include?(attr_name) &&
|
||||
(value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
|
||||
end
|
||||
end
|
||||
end
|
||||
828
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
vendored
Normal file
828
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
vendored
Normal file
|
|
@ -0,0 +1,828 @@
|
|||
#--
|
||||
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
|
||||
# Under MIT and/or CC By license.
|
||||
#++
|
||||
|
||||
module HTML
|
||||
|
||||
# Selects HTML elements using CSS 2 selectors.
|
||||
#
|
||||
# The +Selector+ class uses CSS selector expressions to match and select
|
||||
# HTML elements.
|
||||
#
|
||||
# For example:
|
||||
# selector = HTML::Selector.new "form.login[action=/login]"
|
||||
# creates a new selector that matches any +form+ element with the class
|
||||
# +login+ and an attribute +action+ with the value <tt>/login</tt>.
|
||||
#
|
||||
# === Matching Elements
|
||||
#
|
||||
# Use the #match method to determine if an element matches the selector.
|
||||
#
|
||||
# For simple selectors, the method returns an array with that element,
|
||||
# or +nil+ if the element does not match. For complex selectors (see below)
|
||||
# the method returns an array with all matched elements, of +nil+ if no
|
||||
# match found.
|
||||
#
|
||||
# For example:
|
||||
# if selector.match(element)
|
||||
# puts "Element is a login form"
|
||||
# end
|
||||
#
|
||||
# === Selecting Elements
|
||||
#
|
||||
# Use the #select method to select all matching elements starting with
|
||||
# one element and going through all children in depth-first order.
|
||||
#
|
||||
# This method returns an array of all matching elements, an empty array
|
||||
# if no match is found
|
||||
#
|
||||
# For example:
|
||||
# selector = HTML::Selector.new "input[type=text]"
|
||||
# matches = selector.select(element)
|
||||
# matches.each do |match|
|
||||
# puts "Found text field with name #{match.attributes['name']}"
|
||||
# end
|
||||
#
|
||||
# === Expressions
|
||||
#
|
||||
# Selectors can match elements using any of the following criteria:
|
||||
# * <tt>name</tt> -- Match an element based on its name (tag name).
|
||||
# For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt>
|
||||
# to match any element.
|
||||
# * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the
|
||||
# <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>.
|
||||
# * <tt>.class</tt> -- Match an element based on its class name, all
|
||||
# class names if more than one specified.
|
||||
# * <tt>[attr]</tt> -- Match an element that has the specified attribute.
|
||||
# * <tt>[attr=value]</tt> -- Match an element that has the specified
|
||||
# attribute and value. (More operators are supported see below)
|
||||
# * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class,
|
||||
# such as <tt>:nth-child</tt> and <tt>:empty</tt>.
|
||||
# * <tt>:not(expr)</tt> -- Match an element that does not match the
|
||||
# negation expression.
|
||||
#
|
||||
# When using a combination of the above, the element name comes first
|
||||
# followed by identifier, class names, attributes, pseudo classes and
|
||||
# negation in any order. Do not separate these parts with spaces!
|
||||
# Space separation is used for descendant selectors.
|
||||
#
|
||||
# For example:
|
||||
# selector = HTML::Selector.new "form.login[action=/login]"
|
||||
# The matched element must be of type +form+ and have the class +login+.
|
||||
# It may have other classes, but the class +login+ is required to match.
|
||||
# It must also have an attribute called +action+ with the value
|
||||
# <tt>/login</tt>.
|
||||
#
|
||||
# This selector will match the following element:
|
||||
# <form class="login form" method="post" action="/login">
|
||||
# but will not match the element:
|
||||
# <form method="post" action="/logout">
|
||||
#
|
||||
# === Attribute Values
|
||||
#
|
||||
# Several operators are supported for matching attributes:
|
||||
# * <tt>name</tt> -- The element must have an attribute with that name.
|
||||
# * <tt>name=value</tt> -- The element must have an attribute with that
|
||||
# name and value.
|
||||
# * <tt>name^=value</tt> -- The attribute value must start with the
|
||||
# specified value.
|
||||
# * <tt>name$=value</tt> -- The attribute value must end with the
|
||||
# specified value.
|
||||
# * <tt>name*=value</tt> -- The attribute value must contain the
|
||||
# specified value.
|
||||
# * <tt>name~=word</tt> -- The attribute value must contain the specified
|
||||
# word (space separated).
|
||||
# * <tt>name|=word</tt> -- The attribute value must start with specified
|
||||
# word.
|
||||
#
|
||||
# For example, the following two selectors match the same element:
|
||||
# #my_id
|
||||
# [id=my_id]
|
||||
# and so do the following two selectors:
|
||||
# .my_class
|
||||
# [class~=my_class]
|
||||
#
|
||||
# === Alternatives, siblings, children
|
||||
#
|
||||
# Complex selectors use a combination of expressions to match elements:
|
||||
# * <tt>expr1 expr2</tt> -- Match any element against the second expression
|
||||
# if it has some parent element that matches the first expression.
|
||||
# * <tt>expr1 > expr2</tt> -- Match any element against the second expression
|
||||
# if it is the child of an element that matches the first expression.
|
||||
# * <tt>expr1 + expr2</tt> -- Match any element against the second expression
|
||||
# if it immediately follows an element that matches the first expression.
|
||||
# * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression
|
||||
# that comes after an element that matches the first expression.
|
||||
# * <tt>expr1, expr2</tt> -- Match any element against the first expression,
|
||||
# or against the second expression.
|
||||
#
|
||||
# Since children and sibling selectors may match more than one element given
|
||||
# the first element, the #match method may return more than one match.
|
||||
#
|
||||
# === Pseudo classes
|
||||
#
|
||||
# Pseudo classes were introduced in CSS 3. They are most often used to select
|
||||
# elements in a given position:
|
||||
# * <tt>:root</tt> -- Match the element only if it is the root element
|
||||
# (no parent element).
|
||||
# * <tt>:empty</tt> -- Match the element only if it has no child elements,
|
||||
# and no text content.
|
||||
# * <tt>:only-child</tt> -- Match the element if it is the only child (element)
|
||||
# of its parent element.
|
||||
# * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
|
||||
# of its parent element and its type.
|
||||
# * <tt>:first-child</tt> -- Match the element if it is the first child (element)
|
||||
# of its parent element.
|
||||
# * <tt>:first-of-type</tt> -- Match the element if it is the first child (element)
|
||||
# of its parent element of its type.
|
||||
# * <tt>:last-child</tt> -- Match the element if it is the last child (element)
|
||||
# of its parent element.
|
||||
# * <tt>:last-of-type</tt> -- Match the element if it is the last child (element)
|
||||
# of its parent element of its type.
|
||||
# * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element)
|
||||
# of its parent element. The value <tt>b</tt> specifies its index, starting with 1.
|
||||
# * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element)
|
||||
# in each group of <tt>a</tt> child elements of its parent element.
|
||||
# * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element)
|
||||
# in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child
|
||||
# elements of its parent element.
|
||||
# * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third).
|
||||
# Same as <tt>:nth-child(2n+1)</tt>.
|
||||
# * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second,
|
||||
# fourth). Same as <tt>:nth-child(2n+2)</tt>.
|
||||
# * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type.
|
||||
# * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child.
|
||||
# * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and
|
||||
# only elements of its type.
|
||||
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
|
||||
# match the simple selector.
|
||||
#
|
||||
# As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite
|
||||
# tricky and the CSS specification doesn't do a much better job explaining it.
|
||||
# But after reading the examples and trying a few combinations, it's easy to
|
||||
# figure out.
|
||||
#
|
||||
# For example:
|
||||
# table tr:nth-child(odd)
|
||||
# Selects every second row in the table starting with the first one.
|
||||
#
|
||||
# div p:nth-child(4)
|
||||
# Selects the fourth paragraph in the +div+, but not if the +div+ contains
|
||||
# other elements, since those are also counted.
|
||||
#
|
||||
# div p:nth-of-type(4)
|
||||
# Selects the fourth paragraph in the +div+, counting only paragraphs, and
|
||||
# ignoring all other elements.
|
||||
#
|
||||
# div p:nth-of-type(-n+4)
|
||||
# Selects the first four paragraphs, ignoring all others.
|
||||
#
|
||||
# And you can always select an element that matches one set of rules but
|
||||
# not another using <tt>:not</tt>. For example:
|
||||
# p:not(.post)
|
||||
# Matches all paragraphs that do not have the class <tt>.post</tt>.
|
||||
#
|
||||
# === Substitution Values
|
||||
#
|
||||
# You can use substitution with identifiers, class names and element values.
|
||||
# A substitution takes the form of a question mark (<tt>?</tt>) and uses the
|
||||
# next value in the argument list following the CSS expression.
|
||||
#
|
||||
# The substitution value may be a string or a regular expression. All other
|
||||
# values are converted to strings.
|
||||
#
|
||||
# For example:
|
||||
# selector = HTML::Selector.new "#?", /^\d+$/
|
||||
# matches any element whose identifier consists of one or more digits.
|
||||
#
|
||||
# See http://www.w3.org/TR/css3-selectors/
|
||||
class Selector
|
||||
|
||||
|
||||
# An invalid selector.
|
||||
class InvalidSelectorError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
# :call-seq:
|
||||
# Selector.for_class(cls) => selector
|
||||
#
|
||||
# Creates a new selector for the given class name.
|
||||
def for_class(cls)
|
||||
self.new([".?", cls])
|
||||
end
|
||||
|
||||
|
||||
# :call-seq:
|
||||
# Selector.for_id(id) => selector
|
||||
#
|
||||
# Creates a new selector for the given id.
|
||||
def for_id(id)
|
||||
self.new(["#?", id])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# :call-seq:
|
||||
# Selector.new(string, [values ...]) => selector
|
||||
#
|
||||
# Creates a new selector from a CSS 2 selector expression.
|
||||
#
|
||||
# The first argument is the selector expression. All other arguments
|
||||
# are used for value substitution.
|
||||
#
|
||||
# Throws InvalidSelectorError is the selector expression is invalid.
|
||||
def initialize(selector, *values)
|
||||
raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
|
||||
@source = ""
|
||||
values = values[0] if values.size == 1 && values[0].is_a?(Array)
|
||||
|
||||
# We need a copy to determine if we failed to parse, and also
|
||||
# preserve the original pass by-ref statement.
|
||||
statement = selector.strip.dup
|
||||
|
||||
# Create a simple selector, along with negation.
|
||||
simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
|
||||
|
||||
@alternates = []
|
||||
@depends = nil
|
||||
|
||||
# Alternative selector.
|
||||
if statement.sub!(/^\s*,\s*/, "")
|
||||
second = Selector.new(statement, values)
|
||||
@alternates << second
|
||||
# If there are alternate selectors, we group them in the top selector.
|
||||
if alternates = second.instance_variable_get(:@alternates)
|
||||
second.instance_variable_set(:@alternates, [])
|
||||
@alternates.concat alternates
|
||||
end
|
||||
@source << " , " << second.to_s
|
||||
# Sibling selector: create a dependency into second selector that will
|
||||
# match element immediately following this one.
|
||||
elsif statement.sub!(/^\s*\+\s*/, "")
|
||||
second = next_selector(statement, values)
|
||||
@depends = lambda do |element, first|
|
||||
if element = next_element(element)
|
||||
second.match(element, first)
|
||||
end
|
||||
end
|
||||
@source << " + " << second.to_s
|
||||
# Adjacent selector: create a dependency into second selector that will
|
||||
# match all elements following this one.
|
||||
elsif statement.sub!(/^\s*~\s*/, "")
|
||||
second = next_selector(statement, values)
|
||||
@depends = lambda do |element, first|
|
||||
matches = []
|
||||
while element = next_element(element)
|
||||
if subset = second.match(element, first)
|
||||
if first && !subset.empty?
|
||||
matches << subset.first
|
||||
break
|
||||
else
|
||||
matches.concat subset
|
||||
end
|
||||
end
|
||||
end
|
||||
matches.empty? ? nil : matches
|
||||
end
|
||||
@source << " ~ " << second.to_s
|
||||
# Child selector: create a dependency into second selector that will
|
||||
# match a child element of this one.
|
||||
elsif statement.sub!(/^\s*>\s*/, "")
|
||||
second = next_selector(statement, values)
|
||||
@depends = lambda do |element, first|
|
||||
matches = []
|
||||
element.children.each do |child|
|
||||
if child.tag? && subset = second.match(child, first)
|
||||
if first && !subset.empty?
|
||||
matches << subset.first
|
||||
break
|
||||
else
|
||||
matches.concat subset
|
||||
end
|
||||
end
|
||||
end
|
||||
matches.empty? ? nil : matches
|
||||
end
|
||||
@source << " > " << second.to_s
|
||||
# Descendant selector: create a dependency into second selector that
|
||||
# will match all descendant elements of this one. Note,
|
||||
elsif statement =~ /^\s+\S+/ && statement != selector
|
||||
second = next_selector(statement, values)
|
||||
@depends = lambda do |element, first|
|
||||
matches = []
|
||||
stack = element.children.reverse
|
||||
while node = stack.pop
|
||||
next unless node.tag?
|
||||
if subset = second.match(node, first)
|
||||
if first && !subset.empty?
|
||||
matches << subset.first
|
||||
break
|
||||
else
|
||||
matches.concat subset
|
||||
end
|
||||
elsif children = node.children
|
||||
stack.concat children.reverse
|
||||
end
|
||||
end
|
||||
matches.empty? ? nil : matches
|
||||
end
|
||||
@source << " " << second.to_s
|
||||
else
|
||||
# The last selector is where we check that we parsed
|
||||
# all the parts.
|
||||
unless statement.empty? || statement.strip.empty?
|
||||
raise ArgumentError, "Invalid selector: #{statement}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# :call-seq:
|
||||
# match(element, first?) => array or nil
|
||||
#
|
||||
# Matches an element against the selector.
|
||||
#
|
||||
# For a simple selector this method returns an array with the
|
||||
# element if the element matches, nil otherwise.
|
||||
#
|
||||
# For a complex selector (sibling and descendant) this method
|
||||
# returns an array with all matching elements, nil if no match is
|
||||
# found.
|
||||
#
|
||||
# Use +first_only=true+ if you are only interested in the first element.
|
||||
#
|
||||
# For example:
|
||||
# if selector.match(element)
|
||||
# puts "Element is a login form"
|
||||
# end
|
||||
def match(element, first_only = false)
|
||||
# Match element if no element name or element name same as element name
|
||||
if matched = (!@tag_name || @tag_name == element.name)
|
||||
# No match if one of the attribute matches failed
|
||||
for attr in @attributes
|
||||
if element.attributes[attr[0]] !~ attr[1]
|
||||
matched = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Pseudo class matches (nth-child, empty, etc).
|
||||
if matched
|
||||
for pseudo in @pseudo
|
||||
unless pseudo.call(element)
|
||||
matched = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Negation. Same rules as above, but we fail if a match is made.
|
||||
if matched && @negation
|
||||
for negation in @negation
|
||||
if negation[:tag_name] == element.name
|
||||
matched = false
|
||||
else
|
||||
for attr in negation[:attributes]
|
||||
if element.attributes[attr[0]] =~ attr[1]
|
||||
matched = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if matched
|
||||
for pseudo in negation[:pseudo]
|
||||
if pseudo.call(element)
|
||||
matched = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
break unless matched
|
||||
end
|
||||
end
|
||||
|
||||
# If element matched but depends on another element (child,
|
||||
# sibling, etc), apply the dependent matches instead.
|
||||
if matched && @depends
|
||||
matches = @depends.call(element, first_only)
|
||||
else
|
||||
matches = matched ? [element] : nil
|
||||
end
|
||||
|
||||
# If this selector is part of the group, try all the alternative
|
||||
# selectors (unless first_only).
|
||||
if !first_only || !matches
|
||||
@alternates.each do |alternate|
|
||||
break if matches && first_only
|
||||
if subset = alternate.match(element, first_only)
|
||||
if matches
|
||||
matches.concat subset
|
||||
else
|
||||
matches = subset
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
matches
|
||||
end
|
||||
|
||||
|
||||
# :call-seq:
|
||||
# select(root) => array
|
||||
#
|
||||
# Selects and returns an array with all matching elements, beginning
|
||||
# with one node and traversing through all children depth-first.
|
||||
# Returns an empty array if no match is found.
|
||||
#
|
||||
# The root node may be any element in the document, or the document
|
||||
# itself.
|
||||
#
|
||||
# For example:
|
||||
# selector = HTML::Selector.new "input[type=text]"
|
||||
# matches = selector.select(element)
|
||||
# matches.each do |match|
|
||||
# puts "Found text field with name #{match.attributes['name']}"
|
||||
# end
|
||||
def select(root)
|
||||
matches = []
|
||||
stack = [root]
|
||||
while node = stack.pop
|
||||
if node.tag? && subset = match(node, false)
|
||||
subset.each do |match|
|
||||
matches << match unless matches.any? { |item| item.equal?(match) }
|
||||
end
|
||||
elsif children = node.children
|
||||
stack.concat children.reverse
|
||||
end
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
|
||||
# Similar to #select but returns the first matching element. Returns +nil+
|
||||
# if no element matches the selector.
|
||||
def select_first(root)
|
||||
stack = [root]
|
||||
while node = stack.pop
|
||||
if node.tag? && subset = match(node, true)
|
||||
return subset.first if !subset.empty?
|
||||
elsif children = node.children
|
||||
stack.concat children.reverse
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def to_s #:nodoc:
|
||||
@source
|
||||
end
|
||||
|
||||
|
||||
# Return the next element after this one. Skips sibling text nodes.
|
||||
#
|
||||
# With the +name+ argument, returns the next element with that name,
|
||||
# skipping other sibling elements.
|
||||
def next_element(element, name = nil)
|
||||
if siblings = element.parent.children
|
||||
found = false
|
||||
siblings.each do |node|
|
||||
if node.equal?(element)
|
||||
found = true
|
||||
elsif found && node.tag?
|
||||
return node if (name.nil? || node.name == name)
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
|
||||
# Creates a simple selector given the statement and array of
|
||||
# substitution values.
|
||||
#
|
||||
# Returns a hash with the values +tag_name+, +attributes+,
|
||||
# +pseudo+ (classes) and +negation+.
|
||||
#
|
||||
# Called the first time with +can_negate+ true to allow
|
||||
# negation. Called a second time with false since negation
|
||||
# cannot be negated.
|
||||
def simple_selector(statement, values, can_negate = true)
|
||||
tag_name = nil
|
||||
attributes = []
|
||||
pseudo = []
|
||||
negation = []
|
||||
|
||||
# Element name. (Note that in negation, this can come at
|
||||
# any order, but for simplicity we allow if only first).
|
||||
statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
|
||||
match.strip!
|
||||
tag_name = match.downcase unless match == "*"
|
||||
@source << match
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Get identifier, class, attribute name, pseudo or negation.
|
||||
while true
|
||||
# Element identifier.
|
||||
next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
|
||||
id = $1
|
||||
if id == "?"
|
||||
id = values.shift
|
||||
end
|
||||
@source << "##{id}"
|
||||
id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
|
||||
attributes << ["id", id]
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Class name.
|
||||
next if statement.sub!(/^\.([\w\-]+)/) do |match|
|
||||
class_name = $1
|
||||
@source << ".#{class_name}"
|
||||
class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
|
||||
attributes << ["class", class_name]
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Attribute value.
|
||||
next if statement.sub!(/^\[\s*([[:alpha:]][\w\-]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
|
||||
name, equality, value = $1, $2, $3
|
||||
if value == "?"
|
||||
value = values.shift
|
||||
else
|
||||
# Handle single and double quotes.
|
||||
value.strip!
|
||||
if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
|
||||
value = value[1..-2]
|
||||
end
|
||||
end
|
||||
@source << "[#{name}#{equality}'#{value}']"
|
||||
attributes << [name.downcase.strip, attribute_match(equality, value)]
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Root element only.
|
||||
next if statement.sub!(/^:root/) do |match|
|
||||
pseudo << lambda do |element|
|
||||
element.parent.nil? || !element.parent.tag?
|
||||
end
|
||||
@source << ":root"
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Nth-child including last and of-type.
|
||||
next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
|
||||
reverse = $1 == "last-"
|
||||
of_type = $2 == "of-type"
|
||||
@source << ":nth-#{$1}#{$2}("
|
||||
case $3
|
||||
when "odd"
|
||||
pseudo << nth_child(2, 1, of_type, reverse)
|
||||
@source << "odd)"
|
||||
when "even"
|
||||
pseudo << nth_child(2, 2, of_type, reverse)
|
||||
@source << "even)"
|
||||
when /^(\d+|\?)$/ # b only
|
||||
b = ($1 == "?" ? values.shift : $1).to_i
|
||||
pseudo << nth_child(0, b, of_type, reverse)
|
||||
@source << "#{b})"
|
||||
when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
|
||||
a = ($1 == "?" ? values.shift :
|
||||
$1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
|
||||
b = ($2 == "?" ? values.shift : $2).to_i
|
||||
pseudo << nth_child(a, b, of_type, reverse)
|
||||
@source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
|
||||
else
|
||||
raise ArgumentError, "Invalid nth-child #{match}"
|
||||
end
|
||||
"" # Remove
|
||||
end
|
||||
# First/last child (of type).
|
||||
next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
|
||||
reverse = $1 == "last"
|
||||
of_type = $2 == "of-type"
|
||||
pseudo << nth_child(0, 1, of_type, reverse)
|
||||
@source << ":#{$1}-#{$2}"
|
||||
"" # Remove
|
||||
end
|
||||
# Only child (of type).
|
||||
next if statement.sub!(/^:only-(child|of-type)/) do |match|
|
||||
of_type = $1 == "of-type"
|
||||
pseudo << only_child(of_type)
|
||||
@source << ":only-#{$1}"
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Empty: no child elements or meaningful content (whitespaces
|
||||
# are ignored).
|
||||
next if statement.sub!(/^:empty/) do |match|
|
||||
pseudo << lambda do |element|
|
||||
empty = true
|
||||
for child in element.children
|
||||
if child.tag? || !child.content.strip.empty?
|
||||
empty = false
|
||||
break
|
||||
end
|
||||
end
|
||||
empty
|
||||
end
|
||||
@source << ":empty"
|
||||
"" # Remove
|
||||
end
|
||||
# Content: match the text content of the element, stripping
|
||||
# leading and trailing spaces.
|
||||
next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
|
||||
content = $1
|
||||
if content == "?"
|
||||
content = values.shift
|
||||
elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
|
||||
content = content[1..-2]
|
||||
end
|
||||
@source << ":content('#{content}')"
|
||||
content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
|
||||
pseudo << lambda do |element|
|
||||
text = ""
|
||||
for child in element.children
|
||||
unless child.tag?
|
||||
text << child.content
|
||||
end
|
||||
end
|
||||
text.strip =~ content
|
||||
end
|
||||
"" # Remove
|
||||
end
|
||||
|
||||
# Negation. Create another simple selector to handle it.
|
||||
if statement.sub!(/^:not\(\s*/, "")
|
||||
raise ArgumentError, "Double negatives are not missing feature" unless can_negate
|
||||
@source << ":not("
|
||||
negation << simple_selector(statement, values, false)
|
||||
raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
|
||||
@source << ")"
|
||||
next
|
||||
end
|
||||
|
||||
# No match: moving on.
|
||||
break
|
||||
end
|
||||
|
||||
# Return hash. The keys are mapped to instance variables.
|
||||
{:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
|
||||
end
|
||||
|
||||
|
||||
# Create a regular expression to match an attribute value based
|
||||
# on the equality operator (=, ^=, |=, etc).
|
||||
def attribute_match(equality, value)
|
||||
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
||||
case equality
|
||||
when "=" then
|
||||
# Match the attribute value in full
|
||||
Regexp.new("^#{regexp}$")
|
||||
when "~=" then
|
||||
# Match a space-separated word within the attribute value
|
||||
Regexp.new("(^|\s)#{regexp}($|\s)")
|
||||
when "^="
|
||||
# Match the beginning of the attribute value
|
||||
Regexp.new("^#{regexp}")
|
||||
when "$="
|
||||
# Match the end of the attribute value
|
||||
Regexp.new("#{regexp}$")
|
||||
when "*="
|
||||
# Match substring of the attribute value
|
||||
regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
|
||||
when "|=" then
|
||||
# Match the first space-separated item of the attribute value
|
||||
Regexp.new("^#{regexp}($|\s)")
|
||||
else
|
||||
raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
|
||||
# Match all attributes values (existence check)
|
||||
//
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns a lambda that can match an element against the nth-child
|
||||
# pseudo class, given the following arguments:
|
||||
# * +a+ -- Value of a part.
|
||||
# * +b+ -- Value of b part.
|
||||
# * +of_type+ -- True to test only elements of this type (of-type).
|
||||
# * +reverse+ -- True to count in reverse order (last-).
|
||||
def nth_child(a, b, of_type, reverse)
|
||||
# a = 0 means select at index b, if b = 0 nothing selected
|
||||
return lambda { |element| false } if a == 0 && b == 0
|
||||
# a < 0 and b < 0 will never match against an index
|
||||
return lambda { |element| false } if a < 0 && b < 0
|
||||
b = a + b + 1 if b < 0 # b < 0 just picks last element from each group
|
||||
b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based
|
||||
lambda do |element|
|
||||
# Element must be inside parent element.
|
||||
return false unless element.parent && element.parent.tag?
|
||||
index = 0
|
||||
# Get siblings, reverse if counting from last.
|
||||
siblings = element.parent.children
|
||||
siblings = siblings.reverse if reverse
|
||||
# Match element name if of-type, otherwise ignore name.
|
||||
name = of_type ? element.name : nil
|
||||
found = false
|
||||
for child in siblings
|
||||
# Skip text nodes/comments.
|
||||
if child.tag? && (name == nil || child.name == name)
|
||||
if a == 0
|
||||
# Shortcut when a == 0 no need to go past count
|
||||
if index == b
|
||||
found = child.equal?(element)
|
||||
break
|
||||
end
|
||||
elsif a < 0
|
||||
# Only look for first b elements
|
||||
break if index > b
|
||||
if child.equal?(element)
|
||||
found = (index % a) == 0
|
||||
break
|
||||
end
|
||||
else
|
||||
# Otherwise, break if child found and count == an+b
|
||||
if child.equal?(element)
|
||||
found = (index % a) == b
|
||||
break
|
||||
end
|
||||
end
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
found
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Creates a only child lambda. Pass +of-type+ to only look at
|
||||
# elements of its type.
|
||||
def only_child(of_type)
|
||||
lambda do |element|
|
||||
# Element must be inside parent element.
|
||||
return false unless element.parent && element.parent.tag?
|
||||
name = of_type ? element.name : nil
|
||||
other = false
|
||||
for child in element.parent.children
|
||||
# Skip text nodes/comments.
|
||||
if child.tag? && (name == nil || child.name == name)
|
||||
unless child.equal?(element)
|
||||
other = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
!other
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Called to create a dependent selector (sibling, descendant, etc).
|
||||
# Passes the remainder of the statement that will be reduced to zero
|
||||
# eventually, and array of substitution values.
|
||||
#
|
||||
# This method is called from four places, so it helps to put it here
|
||||
# for reuse. The only logic deals with the need to detect comma
|
||||
# separators (alternate) and apply them to the selector group of the
|
||||
# top selector.
|
||||
def next_selector(statement, values)
|
||||
second = Selector.new(statement, values)
|
||||
# If there are alternate selectors, we group them in the top selector.
|
||||
if alternates = second.instance_variable_get(:@alternates)
|
||||
second.instance_variable_set(:@alternates, [])
|
||||
@alternates.concat alternates
|
||||
end
|
||||
second
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# See HTML::Selector.new
|
||||
def self.selector(statement, *values)
|
||||
Selector.new(statement, *values)
|
||||
end
|
||||
|
||||
|
||||
class Tag
|
||||
|
||||
def select(selector, *values)
|
||||
selector = HTML::Selector.new(selector, values)
|
||||
selector.select(self)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
105
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
vendored
Normal file
105
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
require 'strscan'
|
||||
|
||||
module HTML #:nodoc:
|
||||
|
||||
# A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
|
||||
# token is a string. Each string represents either "text", or an HTML element.
|
||||
#
|
||||
# This currently assumes valid XHTML, which means no free < or > characters.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# tokenizer = HTML::Tokenizer.new(text)
|
||||
# while token = tokenizer.next
|
||||
# p token
|
||||
# end
|
||||
class Tokenizer #:nodoc:
|
||||
|
||||
# The current (byte) position in the text
|
||||
attr_reader :position
|
||||
|
||||
# The current line number
|
||||
attr_reader :line
|
||||
|
||||
# Create a new Tokenizer for the given text.
|
||||
def initialize(text)
|
||||
@scanner = StringScanner.new(text)
|
||||
@position = 0
|
||||
@line = 0
|
||||
@current_line = 1
|
||||
end
|
||||
|
||||
# Return the next token in the sequence, or +nil+ if there are no more tokens in
|
||||
# the stream.
|
||||
def next
|
||||
return nil if @scanner.eos?
|
||||
@position = @scanner.pos
|
||||
@line = @current_line
|
||||
if @scanner.check(/<\S/)
|
||||
update_current_line(scan_tag)
|
||||
else
|
||||
update_current_line(scan_text)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Treat the text at the current position as a tag, and scan it. Supports
|
||||
# comments, doctype tags, and regular tags, and ignores less-than and
|
||||
# greater-than characters within quoted strings.
|
||||
def scan_tag
|
||||
tag = @scanner.getch
|
||||
if @scanner.scan(/!--/) # comment
|
||||
tag << @scanner.matched
|
||||
tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
|
||||
elsif @scanner.scan(/!\[CDATA\[/)
|
||||
tag << @scanner.matched
|
||||
tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/))
|
||||
elsif @scanner.scan(/!/) # doctype
|
||||
tag << @scanner.matched
|
||||
tag << consume_quoted_regions
|
||||
else
|
||||
tag << consume_quoted_regions
|
||||
end
|
||||
tag
|
||||
end
|
||||
|
||||
# Scan all text up to the next < character and return it.
|
||||
def scan_text
|
||||
"#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
|
||||
end
|
||||
|
||||
# Counts the number of newlines in the text and updates the current line
|
||||
# accordingly.
|
||||
def update_current_line(text)
|
||||
text.scan(/\r?\n/) { @current_line += 1 }
|
||||
end
|
||||
|
||||
# Skips over quoted strings, so that less-than and greater-than characters
|
||||
# within the strings are ignored.
|
||||
def consume_quoted_regions
|
||||
text = ""
|
||||
loop do
|
||||
match = @scanner.scan_until(/['"<>]/) or break
|
||||
|
||||
delim = @scanner.matched
|
||||
if delim == "<"
|
||||
match = match.chop
|
||||
@scanner.pos -= 1
|
||||
end
|
||||
|
||||
text << match
|
||||
break if delim == "<" || delim == ">"
|
||||
|
||||
# consume the quoted region
|
||||
while match = @scanner.scan_until(/[\\#{delim}]/)
|
||||
text << match
|
||||
break if @scanner.matched == delim
|
||||
text << @scanner.getch # skip the escaped character
|
||||
end
|
||||
end
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
11
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb
vendored
Normal file
11
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module HTML #:nodoc:
|
||||
module Version #:nodoc:
|
||||
|
||||
MAJOR = 0
|
||||
MINOR = 5
|
||||
TINY = 3
|
||||
|
||||
STRING = [ MAJOR, MINOR, TINY ].join(".")
|
||||
|
||||
end
|
||||
end
|
||||
130
vendor/rails/actionpack/lib/action_controller/verification.rb
vendored
Normal file
130
vendor/rails/actionpack/lib/action_controller/verification.rb
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
module ActionController #:nodoc:
|
||||
module Verification #:nodoc:
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# 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:
|
||||
#
|
||||
# <tt>:params</tt>::
|
||||
# a single key or an array of keys that must be in the <tt>params</tt>
|
||||
# hash in order for the action(s) to be safely called.
|
||||
# <tt>:session</tt>::
|
||||
# a single key or an array of keys that must be in the <tt>session</tt>
|
||||
# in order for the action(s) to be safely called.
|
||||
# <tt>:flash</tt>::
|
||||
# a single key or an array of keys that must be in the flash in order
|
||||
# for the action(s) to be safely called.
|
||||
# <tt>:method</tt>::
|
||||
# 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: <tt>:get</tt> or <tt>:post</tt>, for
|
||||
# example.)
|
||||
# <tt>:xhr</tt>::
|
||||
# true/false option to ensure that the request is coming from an Ajax
|
||||
# call or not.
|
||||
# <tt>:add_flash</tt>::
|
||||
# a hash of name/value pairs that should be merged into the session's
|
||||
# flash if the prerequisites cannot be satisfied.
|
||||
# <tt>:add_headers</tt>::
|
||||
# a hash of name/value pairs that should be merged into the response's
|
||||
# headers hash if the prerequisites cannot be satisfied.
|
||||
# <tt>:redirect_to</tt>::
|
||||
# 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.
|
||||
# <tt>:render</tt>::
|
||||
# the render parameters to be used when the prerequisites cannot be satisfied.
|
||||
# <tt>:only</tt>::
|
||||
# only apply this verification to the actions specified in the associated
|
||||
# array (may also be a single value).
|
||||
# <tt>:except</tt>::
|
||||
# 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.update(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| params[v].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
|
||||
Loading…
Add table
Add a link
Reference in a new issue