diff options
author | francis <francis> | 2008-01-23 01:48:14 +0000 |
---|---|---|
committer | francis <francis> | 2008-01-23 01:48:14 +0000 |
commit | 60eaae4f7df1f1dae91defb87d3707451c359cf4 (patch) | |
tree | e74835c37779a2f094e810960cda07b99a75330e /vendor/rails-2.0.2/actionpack/lib/action_controller | |
parent | 71d22c740302e1f83bbbd89b229734ea9c67493c (diff) |
Freeze in rails 2.0.2 (Am I going to regret having this beast in CVS?)
Diffstat (limited to 'vendor/rails-2.0.2/actionpack/lib/action_controller')
64 files changed, 14084 insertions, 0 deletions
diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb new file mode 100644 index 000000000..5b9a2b71f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb new file mode 100644 index 000000000..5ffe5f188 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/dom_assertions.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb new file mode 100644 index 000000000..0b4313055 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/model_assertions.rb @@ -0,0 +1,19 @@ +module ActionController + module Assertions + module ModelAssertions + # Ensures that the passed record is valid by ActiveRecord 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb new file mode 100644 index 000000000..42bd7fb3d --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -0,0 +1,166 @@ +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 + assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false } + 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 + # + def assert_redirected_to(options = {}, message=nil) + clean_backtrace do + assert_response(:redirect, message) + return true if options == @response.redirected_to + ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? + + begin + url = {} + original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup } + original.each do |key, value| + if value.is_a?(Symbol) + value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url") + end + + unless value.is_a?(Hash) + request = case value + when NilClass then nil + when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil) + else recognized_request_for(value) + end + value = request.path_parameters if request + end + + if value.is_a?(Hash) # stringify 2 levels of hash keys + if name = value.delete(:use_route) + route = ActionController::Routing::Routes.named_routes[name] + value.update(route.parameter_shell) + end + + value.stringify_keys! + value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! } + if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash) + original[:actual].stringify_keys! + value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller'] + end + end + + if value.respond_to?(:[]) && value['controller'] + value['controller'] = value['controller'].to_s + if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/') + new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path) + value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) + end + value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash + end + url[key] = value + end + + @response_diff = url[:actual].diff(url[:expected]) if url[:actual] + msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff) + + assert_block(msg) do + url[:expected].keys.all? do |k| + if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path) + else parameterize(url[:expected][k]) == parameterize(url[:actual][k]) + end + end + end + rescue ActionController::RoutingError # routing failed us, so match the strings only. + msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url) + url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$} + eurl, epath, url, path = [options, @response.redirect_url].collect do |url| + u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url] + [u, (p.first == '/') ? p : '/' + p] + end.flatten + + assert_equal(eurl, url, msg) if eurl && url + assert_equal(epath, path, msg) if epath && path + 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 = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file + msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered) + assert_block(msg) do + if expected.nil? + !@response.rendered_with_file? + else + expected == rendered + end + end + end + 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 + + # Proxy to to_param if the object will respond to it. + def parameterize(value) + value.respond_to?(:to_param) ? value.to_param : value + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb new file mode 100644 index 000000000..9bff28324 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/routing_assertions.rb @@ -0,0 +1,143 @@ +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"} + 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, 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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb new file mode 100644 index 000000000..573405c0f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -0,0 +1,640 @@ +#-- +# 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 = "" + stack = match.children.reverse + while node = stack.pop + if node.tag? + stack.concat node.children.reverse + else + text << node.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 = nil + arg = args.shift + + # 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 arg.is_a?(Symbol) + rjs_type = arg + + if rjs_type == :insert + arg = args.shift + insertion = "insert_#{arg}".to_sym + raise ArgumentError, "Unknown RJS insertion type #{arg}" 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 + arg = args.shift + else + statement = "#{RJS_STATEMENTS[:any]}" + end + + # Next argument we're looking for is the element identifier. If missing, we pick + # any element. + if arg.is_a?(String) + id = Regexp.quote(arg) + arg = args.shift + else + id = "[^\"]*" + end + + pattern = + case rjs_type + when :chained_replace, :chained_replace_html + Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) + when :remove, :show, :hide, :toggle + Regexp.new("#{statement}\\(\"#{id}\"\\)") + else + Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) + end + + # 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($2) + 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. + flunk args.shift || "No RJS statement that replaces or inserts HTML content." + 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_STATEMENTS = { + :replace => /Element\.replace/, + :replace_html => /Element\.update/, + :chained_replace => /\.replace/, + :chained_replace_html => /\.update/, + :remove => /Element\.remove/, + :show => /Element\.show/, + :hide => /Element\.hide/, + :toggle => /Element\.toggle/ + } + RJS_INSERTIONS = [:top, :bottom, :before, :after] + RJS_INSERTIONS.each do |insertion| + RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}")) + end + RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") + RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion| + Regexp.quote("new Insertion.#{insertion.to_s.camelize}") + end.join('|')) + RJS_PATTERN_HTML = /"((\\"|[^"])*)"/ + RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", + Regexp::MULTILINE) + 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_PATTERN_EVERYTHING) do |match| + html = unescape_rjs($3) + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb new file mode 100644 index 000000000..4ac489520 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/assertions/tag_assertions.rb @@ -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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb new file mode 100755 index 000000000..ff77e036f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/base.rb @@ -0,0 +1,1295 @@ +require 'action_controller/mime_type' +require 'action_controller/request' +require 'action_controller/response' +require 'action_controller/routing' +require 'action_controller/resources' +require 'action_controller/url_rewriter' +require 'action_controller/status_codes' +require 'drb' +require 'set' + +module ActionController #:nodoc: + class ActionControllerError < StandardError #:nodoc: + end + + class SessionRestoreError < ActionControllerError #:nodoc: + end + + class MissingTemplate < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class RoutingError < ActionControllerError #:nodoc: + attr_reader :failures + def initialize(message, failures=[]) + super(message) + @failures = failures + end + end + + class MethodNotAllowed < ActionControllerError #:nodoc: + attr_reader :allowed_methods + + def initialize(*allowed_methods) + super("Only #{allowed_methods.to_sentence} requests are allowed.") + @allowed_methods = allowed_methods + end + + def allowed_methods_header + allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' + end + + def handle_response!(response) + response.headers['Allow'] ||= allowed_methods_header + end + end + + class NotImplemented < MethodNotAllowed #:nodoc: + end + + class UnknownController < ActionControllerError #:nodoc: + end + + class UnknownAction < ActionControllerError #:nodoc: + end + + class MissingFile < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class SessionOverflowError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class DoubleRenderError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class RedirectBackError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class UnknownHttpMethod < ActionControllerError #:nodoc: + end + + # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed + # on request and then either render a template or redirect to another action. An action is defined as a public method + # on the controller, which will automatically be made accessible to the web-server through Rails Routes. + # + # A sample controller could look like this: + # + # class GuestBookController < ActionController::Base + # def index + # @entries = Entry.find(:all) + # end + # + # def sign + # Entry.create(params[:entry]) + # redirect_to :action => "index" + # end + # end + # + # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action + # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the + # template <tt>app/views/guestbook/index.erb</tt> by default after populating the <tt>@entries</tt> instance variable. + # + # Unlike index, the sign action will not render a template. After performing its main purpose (creating a + # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external + # "302 Moved" HTTP response that takes the user to the index action. + # + # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. + # Most actions are variations of these themes. + # + # == Requests + # + # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters. + # This value should hold the name of the action to be performed. Once the action has been identified, the remaining + # request parameters, the session (if one is available), and the full request with all the http headers are made available to + # the action through instance variables. Then the action is performed. + # + # The full request object is available with the request accessor and is primarily used to query for http headers. These queries + # are made by accessing the environment hash, like this: + # + # def server_ip + # location = request.env["SERVER_ADDR"] + # render :text => "This server hosted at #{location}" + # end + # + # == Parameters + # + # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method + # which returns a hash. For example, an action that was performed through <tt>/weblog/list?category=All&limit=5</tt> will include + # <tt>{ "category" => "All", "limit" => 5 }</tt> in params. + # + # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: + # + # <input type="text" name="post[name]" value="david"> + # <input type="text" name="post[address]" value="hyacintvej"> + # + # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. + # If the address input had been named "post[address][street]", the params would have included + # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. + # + # == Sessions + # + # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, + # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such + # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely + # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. + # + # You can place objects in the session by using the <tt>session</tt> method, which accesses a hash: + # + # session[:person] = Person.authenticate(user_name, password) + # + # And retrieved again through the same hash: + # + # Hello #{session[:person]} + # + # For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can + # remove the entire session with reset_session. + # + # Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents + # the user from tampering with the session but also allows him to see its contents. + # + # Do not put secret information in session! + # + # Other options for session storage are: + # + # ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and, + # unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set + # + # config.action_controller.session_store = :active_record_store + # + # in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>. + # + # MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>: + # + # config.action_controller.session_store = :mem_cache_store + # + # This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information. + # + # == Responses + # + # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response + # object is generated automatically through the use of renders and redirects and requires no user intervention. + # + # == Renders + # + # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering + # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured. + # The controller passes objects to the view by assigning instance variables: + # + # def show + # @post = Post.find(params[:id]) + # end + # + # Which are then automatically available to the view: + # + # Title: <%= @post.title %> + # + # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use + # the manual rendering methods: + # + # def search + # @results = Search.find(params[:query]) + # case @results + # when 0 then render :action => "no_results" + # when 1 then render :action => "show" + # when 2..10 then render :action => "show_many" + # end + # end + # + # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html. + # + # == Redirects + # + # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database, + # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) + # a <tt>show</tt> action that we'll assume has already been created. The code might look like this: + # + # def create + # @entry = Entry.new(params[:entry]) + # if @entry.save + # # The entry was saved correctly, redirect to show + # redirect_to :action => 'show', :id => @entry.id + # else + # # things didn't go so well, do something else + # end + # end + # + # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed. + # + # == Calling multiple redirects or renders + # + # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: + # + # def do_something + # redirect_to :action => "elsewhere" + # render :action => "overthere" # raises DoubleRenderError + # end + # + # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. + # + # def do_something + # redirect_to(:action => "elsewhere") and return if monkeys.nil? + # render :action => "overthere" # won't be called unless monkeys is nil + # end + # + class Base + DEFAULT_RENDER_STATUS_CODE = "200 OK" + + include StatusCodes + + # Determines whether the view has access to controller internals @request, @response, @session, and @template. + # By default, it does. + @@view_controller_internals = true + cattr_accessor :view_controller_internals + + # Protected instance variable cache + @@protected_variables_cache = nil + cattr_accessor :protected_variables_cache + + # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, + # and images to a dedicated asset server away from the main web server. Example: + # ActionController::Base.asset_host = "http://assets.example.com" + @@asset_host = "" + cattr_accessor :asset_host + + # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors. + # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt> + # should instead be implemented in the controller to determine when debugging screens should be shown. + @@consider_all_requests_local = true + cattr_accessor :consider_all_requests_local + + # Enable or disable the collection of failure information for RoutingErrors. + # This information can be extremely useful when tweaking custom routes, but is + # pointless once routes have been tested and verified. + @@debug_routes = true + cattr_accessor :debug_routes + + # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex + # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications + # may not be. Turned off by default. + @@allow_concurrency = false + cattr_accessor :allow_concurrency + + # Modern REST web services often need to submit complex data to the web application. + # The param_parsers hash lets you register handlers which will process the http body and add parameters to the + # <tt>params</tt> hash. These handlers are invoked for post and put requests. + # + # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated + # in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one + # action serve both regular forms and web service requests. + # + # Example of doing your own parser for a custom content type: + # + # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data| + # node = REXML::Document.new(post) + # { node.root.name => node.root } + # end + # + # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the + # root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results + # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can + # re-register XmlSimple as application/xml handler ike this: + # + # ActionController::Base.param_parsers[Mime::XML] = + # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } + # + # A YAML parser is also available and can be turned on with: + # + # ActionController::Base.param_parsers[Mime::YAML] = :yaml + @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, + Mime::URL_ENCODED_FORM => :url_encoded_form, + Mime::XML => :xml_simple } + cattr_accessor :param_parsers + + # Controls the default charset for all renders. + @@default_charset = "utf-8" + cattr_accessor :default_charset + + # The logger is used for generating information on the action run-time (including benchmarking) if available. + # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. + cattr_accessor :logger + + # Determines which template class should be used by ActionController. + cattr_accessor :template_class + + # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates. + cattr_accessor :ignore_missing_templates + + # Controls the resource action separator + @@resource_action_separator = "/" + cattr_accessor :resource_action_separator + + # Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default + cattr_accessor :request_forgery_protection_token + + # Indicates whether or not optimise the generated named + # route helper methods + cattr_accessor :optimise_named_routes + self.optimise_named_routes = true + + # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. + class_inheritable_accessor :allow_forgery_protection + self.allow_forgery_protection = true + + # Holds the request object that's primarily used to get environment variables through access like + # <tt>request.env["REQUEST_URI"]</tt>. + attr_internal :request + + # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>params["post_id"]</tt> + # to get the post_id. No type casts are made, so all values are returned as strings. + attr_internal :params + + # Holds the response object that's primarily used to set additional HTTP headers through access like + # <tt>response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template + # has been rendered through response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output, + # such as a OutputCompressionFilter. + attr_internal :response + + # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person" + # key. The session will hold any type of object as values, but the key should be a string or symbol. + attr_internal :session + + # Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control + # directive. Values should always be specified as strings. + attr_internal :headers + + # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash + # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered. + attr_accessor :assigns + + # Returns the name of the action this controller is processing. + attr_accessor :action_name + + # Templates that are exempt from layouts + @@exempt_from_layout = Set.new([/\.rjs$/]) + + class << self + # Factory for the standard create, process loop where the controller is discarded after processing. + def process(request, response) #:nodoc: + new.process(request, response) + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". + def controller_class_name + @controller_class_name ||= name.demodulize + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". + def controller_name + @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". + def controller_path + @controller_path ||= name.gsub(/Controller$/, '').underscore + end + + # Return an array containing the names of public methods that have been marked hidden from the action processor. + # By default, all methods defined in ActionController::Base and included modules are hidden. + # More methods can be hidden using <tt>hide_actions</tt>. + def hidden_actions + unless read_inheritable_attribute(:hidden_actions) + write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s)) + end + + read_inheritable_attribute(:hidden_actions) + end + + # Hide each of the given methods from being callable as actions. + def hide_action(*names) + write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s)) + end + + ## View load paths determine the bases from which template references can be made. So a call to + ## render("test/template") will be looked up in the view load paths array and the closest match will be + ## returned. + def view_paths + @view_paths || superclass.view_paths + end + + def view_paths=(value) + @view_paths = value + end + + # Adds a view_path to the front of the view_paths array. + # If the current class has no view paths, copy them from + # the superclass. This change will be visible for all future requests. + # + # ArticleController.prepend_view_path("views/default") + # ArticleController.prepend_view_path(["views/default", "views/custom"]) + # + def prepend_view_path(path) + @view_paths = superclass.view_paths.dup if @view_paths.nil? + view_paths.unshift(*path) + end + + # Adds a view_path to the end of the view_paths array. + # If the current class has no view paths, copy them from + # the superclass. This change will be visible for all future requests. + # + # ArticleController.append_view_path("views/default") + # ArticleController.append_view_path(["views/default", "views/custom"]) + # + def append_view_path(path) + @view_paths = superclass.view_paths.dup if @view_paths.nil? + view_paths.push(*path) + end + + # Replace sensitive parameter data from the request log. + # Filters parameters that have any of the arguments as a substring. + # Looks in all subhashes of the param hash for keys to filter. + # If a block is given, each key and value of the parameter hash and all + # subhashes is passed to it, the value or key + # can be replaced using String#replace or similar method. + # + # Examples: + # filter_parameter_logging + # => Does nothing, just slows the logging process down + # + # filter_parameter_logging :password + # => replaces the value to all keys matching /password/i with "[FILTERED]" + # + # filter_parameter_logging :foo, "bar" + # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + # + # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i } + # => reverses the value to all keys matching /secret/i + # + # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i } + # => reverses the value to all keys matching /secret/i, and + # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + def filter_parameter_logging(*filter_words, &block) + parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 + + define_method(:filter_parameters) do |unfiltered_parameters| + filtered_parameters = {} + + unfiltered_parameters.each do |key, value| + if key =~ parameter_filter + filtered_parameters[key] = '[FILTERED]' + elsif value.is_a?(Hash) + filtered_parameters[key] = filter_parameters(value) + elsif block_given? + key = key.dup + value = value.dup if value + yield key, value + filtered_parameters[key] = value + else + filtered_parameters[key] = value + end + end + + filtered_parameters + end + end + + # Don't render layouts for templates with the given extensions. + def exempt_from_layout(*extensions) + regexps = extensions.collect do |extension| + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ + end + @@exempt_from_layout.merge regexps + end + end + + public + # Extracts the action_name from the request parameters and performs that action. + def process(request, response, method = :perform_action, *arguments) #:nodoc: + initialize_template_class(response) + assign_shortcuts(request, response) + initialize_current_url + assign_names + forget_variables_added_to_assigns + + log_processing + send(method, *arguments) + + assign_default_content_type_and_charset + + response.request = request + response.prepare! unless component_request? + response + ensure + process_cleanup + end + + # Returns a URL that has been rewritten according to the options hash and the defined Routes. + # (For doing a complete redirect, use redirect_to). + # Â + # <tt>url_for</tt> is used to: + # Â + # All keys given to url_for are forwarded to the Route module, save for the following: + # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example, + # <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt> + # will produce "/posts/show/10#comments". + # * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default) + # * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this + # is currently not recommended since it breaks caching. + # * <tt>:host</tt> -- overrides the default (current) host if provided. + # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided. + # * <tt>:port</tt> -- optionally specify the port to connect to. + # * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present). + # * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present). + # * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path + # will include the web server relative installation directory. + # + # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string. + # Routes composes a query string as the key/value pairs not included in the <base>. + # + # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with + # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs: + # + # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent' + # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts' + # url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts' + # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10' + # url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts' + # + # When generating a new URL, missing values may be filled in from the current request's parameters. For example, + # <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to + # other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's + # path. + # Â + # The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for + # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be + # taken from the defaults. There are a few simple rules on how this is performed: + # + # * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt> + # * If the controller changes, the action will default to index unless provided + # + # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the + # route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>. + # + # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated + # from this page. + # + # * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and + # last components, and the action shall change. The generated URL will be, "people/hh/david/bio". + # * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note + # that this URL leaves out the assumed action of 'bio'. + # + # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The + # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the + # value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using + # defaults. On its own, this rule can account for much of the typical Rails URL behavior. + # Â + # Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired. + # The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options. + # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the + # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is + # displayed on: + # + # url_for :controller => 'posts', :action => nil + # + # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the + # :overwrite_params options. Say for your posts you have different views for showing and printing them. + # Then, in the show view, you get the URL for the print view like this + # + # url_for :overwrite_params => { :action => 'print' } + # + # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt> + # would have slashed-off the path components after the changed action. + def url_for(options = nil) #:doc: + case options || {} + when String + options + when Hash + @url.rewrite(rewrite_options(options)) + else + polymorphic_url(options) + end + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". + def controller_class_name + self.class.controller_class_name + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". + def controller_name + self.class.controller_name + end + + # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". + def controller_path + self.class.controller_path + end + + def session_enabled? + request.session_options && request.session_options[:disabled] != false + end + + self.view_paths = [] + + # View load paths for controller. + def view_paths + (@template || self.class).view_paths + end + + def view_paths=(value) + (@template || self.class).view_paths = value + end + + # Adds a view_path to the front of the view_paths array. + # This change affects the current request only. + # + # self.prepend_view_path("views/default") + # self.prepend_view_path(["views/default", "views/custom"]) + # + def prepend_view_path(path) + (@template || self.class).prepend_view_path(path) + end + + # Adds a view_path to the end of the view_paths array. + # This change affects the current request only. + # + # self.append_view_path("views/default") + # self.append_view_path(["views/default", "views/custom"]) + # + def append_view_path(path) + (@template || self.class).append_view_path(path) + end + + protected + # Renders the content that will be returned to the browser as the response body. + # + # === Rendering an action + # + # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is + # specified. By default, actions are rendered within the current layout (if one exists). + # + # # Renders the template for the action "goal" within the current controller + # render :action => "goal" + # + # # Renders the template for the action "short_goal" within the current controller, + # # but without the current active layout + # render :action => "short_goal", :layout => false + # + # # Renders the template for the action "long_goal" within the current controller, + # # but with a custom layout + # render :action => "long_goal", :layout => "spectacular" + # + # === Rendering partials + # + # Partial rendering in a controller is most commonly used together with Ajax calls that only update one or a few elements on a page + # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in + # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the + # controller action responding to Ajax calls). By default, the current layout is not used. + # + # # Renders the same partial with a local variable. + # render :partial => "person", :locals => { :name => "david" } + # + # # Renders the partial, making @new_person available through + # # the local variable 'person' + # render :partial => "person", :object => @new_person + # + # # Renders a collection of the same partial by making each element + # # of @winners available through the local variable "person" as it + # # builds the complete response. + # render :partial => "person", :collection => @winners + # + # # Renders the same collection of partials, but also renders the + # # person_divider partial between each person partial. + # render :partial => "person", :collection => @winners, :spacer_template => "person_divider" + # + # # Renders a collection of partials located in a view subfolder + # # outside of our current controller. In this example we will be + # # rendering app/views/shared/_note.r(html|xml) Inside the partial + # # each element of @new_notes is available as the local var "note". + # render :partial => "shared/note", :collection => @new_notes + # + # # Renders the partial with a status code of 500 (internal error). + # render :partial => "broken", :status => 500 + # + # Note that the partial filename must also be a valid Ruby variable name, + # so e.g. 2005 and register-user are invalid. + # + # + # == Automatic etagging + # + # Rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the + # response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified + # and the response body will be set to an empty string. No etag header will be inserted if it's already set. + # + # === Rendering a template + # + # Template rendering works just like action rendering except that it takes a path relative to the template root. + # The current layout is automatically applied. + # + # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb) + # render :template => "weblog/show" + # + # === Rendering a file + # + # File rendering works just like action rendering except that it takes a filesystem path. By default, the path + # is assumed to be absolute, and the current layout is not applied. + # + # # Renders the template located at the absolute filesystem path + # render :file => "/path/to/some/template.erb" + # render :file => "c:/path/to/some/template.erb" + # + # # Renders a template within the current layout, and with a 404 status code + # render :file => "/path/to/some/template.erb", :layout => true, :status => 404 + # render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404 + # + # # Renders a template relative to the template root and chooses the proper file extension + # render :file => "some/template", :use_full_path => true + # + # === Rendering text + # + # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text + # rendering is not done within the active layout. + # + # # Renders the clear text "hello world" with status code 200 + # render :text => "hello world!" + # + # # Renders the clear text "Explosion!" with status code 500 + # render :text => "Explosion!", :status => 500 + # + # # Renders the clear text "Hi there!" within the current active layout (if one exists) + # render :text => "Hi there!", :layout => true + # + # # Renders the clear text "Hi there!" within the layout + # # placed in "app/views/layouts/special.r(html|xml)" + # render :text => "Hi there!", :layout => "special" + # + # The :text option can also accept a Proc object, which can be used to manually control the page generation. This should + # generally be avoided, as it violates the separation between code and content, and because almost everything that can be + # done with this method can also be done more cleanly using one of the other rendering methods, most notably templates. + # + # # Renders "Hello from code!" + # render :text => proc { |response, output| output.write("Hello from code!") } + # + # === Rendering JSON + # + # Rendering JSON sets the content type to application/json and optionally wraps the JSON in a callback. It is expected + # that the response will be parsed (or eval'd) for use as a data structure. + # + # # Renders '{"name": "David"}' + # render :json => {:name => "David"}.to_json + # + # It's not necessary to call <tt>to_json</tt> on the object you want to render, since <tt>render</tt> will + # automatically do that for you: + # + # # Also renders '{"name": "David"}' + # render :json => {:name => "David"} + # + # Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag), + # so the <tt>:callback</tt> option is provided for these cases. + # + # # Renders 'show({"name": "David"})' + # render :json => {:name => "David"}.to_json, :callback => 'show' + # + # === Rendering an inline template + # + # Rendering of an inline template works as a cross between text and action rendering where the source for the template + # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering + # and the current layout is not used. + # + # # Renders "hello, hello, hello, again" + # render :inline => "<%= 'hello, ' * 3 + 'again' %>" + # + # # Renders "<p>Good seeing you!</p>" using Builder + # render :inline => "xml.p { 'Good seeing you!' }", :type => :builder + # + # # Renders "hello david" + # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" } + # + # === Rendering inline JavaScriptGenerator page updates + # + # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details), + # you can also pass the <tt>:update</tt> parameter to +render+, along with a block, to render page updates inline. + # + # render :update do |page| + # page.replace_html 'user_list', :partial => 'user', :collection => @users + # page.visual_effect :highlight, 'user_list' + # end + # + # === Rendering with status and location headers + # + # All renders take the :status and :location options and turn them into headers. They can even be used together: + # + # render :xml => post.to_xml, :status => :created, :location => post_url(post) + def render(options = nil, &block) #:doc: + raise DoubleRenderError, "Can only render or redirect once per action" if performed? + + if options.nil? + return render_for_file(default_template_name, nil, true) + else + if options == :update + options = { :update => true } + elsif !options.is_a?(Hash) + raise RenderError, "You called render with invalid options : #{options}" + end + end + + if content_type = options[:content_type] + response.content_type = content_type.to_s + end + + if location = options[:location] + response.headers["Location"] = url_for(location) + end + + if text = options[:text] + render_for_text(text, options[:status]) + + else + if file = options[:file] + render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {}) + + elsif template = options[:template] + render_for_file(template, options[:status], true) + + elsif inline = options[:inline] + add_variables_to_assigns + render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status]) + + elsif action_name = options[:action] + template = default_template_name(action_name.to_s) + if options[:layout] && !template_exempt_from_layout?(template) + render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) + else + render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true) + end + + elsif xml = options[:xml] + response.content_type ||= Mime::XML + render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml, options[:status]) + + elsif json = options[:json] + json = json.to_json unless json.is_a?(String) + json = "#{options[:callback]}(#{json})" unless options[:callback].blank? + response.content_type ||= Mime::JSON + render_for_text(json, options[:status]) + + elsif partial = options[:partial] + partial = default_template_name if partial == true + add_variables_to_assigns + + if collection = options[:collection] + render_for_text( + @template.send!(:render_partial_collection, partial, collection, + options[:spacer_template], options[:locals]), options[:status] + ) + else + render_for_text( + @template.send!(:render_partial, partial, + ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status] + ) + end + + elsif options[:update] + add_variables_to_assigns + @template.send! :evaluate_assigns + + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block) + response.content_type = Mime::JS + render_for_text(generator.to_s) + + elsif options[:nothing] + # Safari doesn't pass the headers of the return if the response is zero length + render_for_text(" ", options[:status]) + + else + render_for_file(default_template_name, options[:status], true) + end + end + end + + # Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead + # of sending it as the response body to the browser. + def render_to_string(options = nil, &block) #:doc: + render(options, &block) + ensure + erase_render_results + forget_variables_added_to_assigns + reset_variables_added_to_assigns + end + + # Return a response that has no content (merely headers). The options + # argument is interpreted to be a hash of header names and values. + # This allows you to easily return a response that consists only of + # significant headers: + # + # head :created, :location => person_path(@person) + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + def head(*args) + if args.length > 2 + raise ArgumentError, "too many arguments to head" + elsif args.empty? + raise ArgumentError, "too few arguments to head" + end + options = args.extract_options! + status = interpret_status(args.shift || options.delete(:status) || :ok) + + options.each do |key, value| + headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s + end + + render :nothing => true, :status => status + end + + + # Clears the rendered results, allowing for another render to be performed. + def erase_render_results #:nodoc: + response.body = nil + @performed_render = false + end + + # Clears the redirected results from the headers, resets the status to 200 and returns + # the URL that was used to redirect or nil if there was no redirected URL + # Note that +redirect_to+ will change the body of the response to indicate a redirection. + # The response body is not reset here, see +erase_render_results+ + def erase_redirect_results #:nodoc: + @performed_redirect = false + response.redirected_to = nil + response.redirected_to_method_params = nil + response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE + response.headers.delete('Location') + end + + # Erase both render and redirect results + def erase_results #:nodoc: + erase_render_results + erase_redirect_results + end + + def rewrite_options(options) #:nodoc: + if defaults = default_url_options(options) + defaults.merge(options) + else + options + end + end + + # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in + # the form of a hash, just like the one you would use for url_for directly. Example: + # + # def default_url_options(options) + # { :project => @project.active? ? @project.url_name : "unknown" } + # end + # + # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the + # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set + # by this method. + def default_url_options(options) #:doc: + end + + # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: + # + # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+. + # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * <tt>String starting with protocol:// (like http://)</tt> - Is passed straight through as the target for redirection. + # * <tt>String not containing a protocol</tt> - The current protocol and host is prepended to the string. + # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places. + # Short-hand for redirect_to(request.env["HTTP_REFERER"]) + # + # Examples: + # redirect_to :action => "show", :id => 5 + # redirect_to post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to articles_url + # redirect_to :back + # + # The redirection happens as a "302 Moved" header unless otherwise specified. + # + # Examples: + # redirect_to post_url(@post), :status=>:found + # redirect_to :action=>'atom', :status=>:moved_permanently + # redirect_to post_url(@post), :status=>301 + # redirect_to :action=>'atom', :status=>302 + # + # When using <tt>redirect_to :back</tt>, if there is no referrer, + # RedirectBackError will be raised. You may specify some fallback + # behavior for this case by rescuing RedirectBackError. + def redirect_to(options = {}, response_status = {}) #:doc: + + if options.is_a?(Hash) && options[:status] + status = options.delete(:status) + elsif response_status[:status] + status = response_status[:status] + else + status = 302 + end + + case options + when %r{^\w+://.*} + raise DoubleRenderError if performed? + logger.info("Redirected to #{options}") if logger && logger.info? + response.redirect(options, interpret_status(status)) + response.redirected_to = options + @performed_redirect = true + + when String + redirect_to(request.protocol + request.host_with_port + options, :status=>status) + + when :back + request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError) + + when Hash + redirect_to(url_for(options), :status=>status) + response.redirected_to = options + + else + redirect_to(url_for(options), :status=>status) + end + end + + # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that + # intermediate caches shouldn't cache the response. + # + # Examples: + # expires_in 20.minutes + # expires_in 3.hours, :private => false + # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true + # + # This method will overwrite an existing Cache-Control header. + # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + def expires_in(seconds, options = {}) #:doc: + cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys) + cache_options.delete_if { |k,v| v.nil? or v == false } + cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} + response.headers["Cache-Control"] = cache_control.join(', ') + end + + # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or + # intermediate caches (like caching proxy servers). + def expires_now #:doc: + response.headers["Cache-Control"] = "no-cache" + end + + # Resets the session by clearing out all the objects stored within and initializing a new session object. + def reset_session #:doc: + request.reset_session + @_session = request.session + response.session = @_session + end + + + private + def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc: + add_variables_to_assigns + assert_existence_of_template_file(template_path) if use_full_path + logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger + render_for_text(@template.render_file(template_path, use_full_path, locals), status) + end + + def render_for_text(text = nil, status = nil, append_response = false) #:nodoc: + @performed_render = true + + response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE) + + if append_response + response.body ||= '' + response.body << text.to_s + else + response.body = text.is_a?(Proc) ? text : text.to_s + end + end + + def initialize_template_class(response) + unless @@template_class + raise "You must assign a template class through ActionController.template_class= before processing a request" + end + + response.template = ActionView::Base.new(view_paths, {}, self) + response.template.extend self.class.master_helper_module + response.redirected_to = nil + @performed_render = @performed_redirect = false + end + + def assign_shortcuts(request, response) + @_request, @_params, @_cookies = request, request.parameters, request.cookies + + @_response = response + @_response.session = request.session + + @_session = @_response.session + @template = @_response.template + @assigns = @_response.template.assigns + + @_headers = @_response.headers + end + + def initialize_current_url + @url = UrlRewriter.new(request, params.clone) + end + + def log_processing + if logger && logger.info? + logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" + logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id) + logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}" + end + end + + def default_render #:nodoc: + render + end + + def perform_action + if self.class.action_methods.include?(action_name) + send(action_name) + default_render unless performed? + elsif respond_to? :method_missing + method_missing action_name + default_render unless performed? + elsif template_exists? && template_public? + default_render + else + raise UnknownAction, "No action responded to #{action_name}", caller + end + end + + def performed? + @performed_render || @performed_redirect + end + + def assign_names + @action_name = (params['action'] || 'index') + end + + def assign_default_content_type_and_charset + response.content_type ||= Mime::HTML + response.charset ||= self.class.default_charset unless sending_file? + end + + def sending_file? + response.headers["Content-Transfer-Encoding"] == "binary" + end + + def action_methods + self.class.action_methods + end + + def self.action_methods + @action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions + end + + def add_variables_to_assigns + unless @variables_added + add_instance_variables_to_assigns + add_class_variables_to_assigns if view_controller_internals + @variables_added = true + end + end + + def forget_variables_added_to_assigns + @variables_added = nil + end + + def reset_variables_added_to_assigns + @template.instance_variable_set("@assigns_added", nil) + end + + def add_instance_variables_to_assigns + @@protected_variables_cache ||= Set.new(protected_instance_variables) + instance_variables.each do |var| + next if @@protected_variables_cache.include?(var) + @assigns[var[1..-1]] = instance_variable_get(var) + end + end + + def add_class_variables_to_assigns + %w(view_paths logger template_class ignore_missing_templates).each do |cvar| + @assigns[cvar] = self.send(cvar) + end + end + + def protected_instance_variables + if view_controller_internals + %w(@assigns @performed_redirect @performed_render) + else + %w(@assigns @performed_redirect @performed_render + @_request @request @_response @response @_params @params + @_session @session @_cookies @cookies + @template @request_origin @parent_controller) + end + end + + def request_origin + # this *needs* to be cached! + # otherwise you'd get different results if calling it more than once + @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}" + end + + def complete_request_uri + "#{request.protocol}#{request.host}#{request.request_uri}" + end + + def close_session + @_session.close if @_session && @_session.respond_to?(:close) + end + + def template_exists?(template_name = default_template_name) + @template.file_exists?(template_name) + end + + def template_public?(template_name = default_template_name) + @template.file_public?(template_name) + end + + def template_exempt_from_layout?(template_name = default_template_name) + extension = @template && @template.pick_template_extension(template_name) + name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name + @@exempt_from_layout.any? { |ext| name_with_extension =~ ext } + end + + def assert_existence_of_template_file(template_name) + unless template_exists?(template_name) || ignore_missing_templates + full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb" + display_paths = view_paths.join(':') + template_type = (template_name =~ /layouts/i) ? 'layout' : 'template' + raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}") + end + end + + def default_template_name(action_name = self.action_name) + if action_name + action_name = action_name.to_s + if action_name.include?('/') && template_path_includes_controller?(action_name) + action_name = strip_out_controller(action_name) + end + end + "#{self.class.controller_path}/#{action_name}" + end + + def strip_out_controller(path) + path.split('/', 2).last + end + + def template_path_includes_controller?(path) + self.class.controller_path.split('/')[-1] == path.split('/')[0] + end + + def process_cleanup + close_session + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb new file mode 100644 index 000000000..5201d31b8 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/benchmarking.rb @@ -0,0 +1,94 @@ +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} (#{'%.5f' % seconds})") + 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, deprecated_status = nil, &block) + unless logger + render_without_benchmark(options, &block) + else + db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + + render_output = nil + @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real + + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + @db_rt_before_render = db_runtime + @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + @rendering_runtime -= @db_rt_after_render + end + + render_output + end + end + + private + def perform_action_with_benchmark + unless logger + perform_action_without_benchmark + else + runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max + + log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)" + log_message << rendering_runtime(runtime) if defined?(@rendering_runtime) + log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + log_message << " | #{headers["Status"]}" + log_message << " [#{complete_request_uri rescue "unknown"}]" + + logger.info(log_message) + response.headers["X-Runtime"] = sprintf("%.5f", runtime) + end + end + + def rendering_runtime(runtime) + percentage = @rendering_runtime * 100 / runtime + " | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i] + end + + def active_record_runtime(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_percentage = db_runtime * 100 / runtime + " | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i] + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb new file mode 100644 index 000000000..e3e48f660 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/caching.rb @@ -0,0 +1,683 @@ +require 'fileutils' +require 'uri' +require 'set' + +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. + module Caching + def self.included(base) #:nodoc: + base.class_eval do + include Pages, Actions, Fragments + + if defined? ActiveRecord + include Sweeping, SqlCache + end + + @@perform_caching = true + cattr_accessor :perform_caching + end + end + + # 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 the Action Pack. This can be as much as 100 times faster than going through the process of dynamically + # 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</tt> class method: + # + # class WeblogController < ActionController::Base + # caches_page :show, :new + # end + # + # This will generate cache files such as weblog/show/5 and weblog/new, 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 + # the 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 Base.page_cache_directory = "/document/root". + # For Rails, this directory has already been set to RAILS_ROOT + "/public". + # + # == Setting the cache extension + # + # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want + # something else, like .php or .shtml, just set Base.page_cache_extension. + module Pages + def self.included(base) #:nodoc: + base.extend(ClassMethods) + base.class_eval do + @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "" + 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. + def caches_page(*actions) + return unless perform_caching + actions = actions.map(&:to_s) + after_filter { |c| c.cache_page if actions.include?(c.action_name) } + 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 + + # 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 :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. + # + # class ListsController < ApplicationController + # before_filter :authenticate, :except => :public + # caches_page :public + # caches_action :show, :cache_path => { :project => 1 } + # caches_action :show, :cache_path => Proc.new { |controller| + # controller.params[:user_id] ? + # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : + # controller.send(:list_url, c.params[:id]) } + # end + module Actions + def self.included(base) #:nodoc: + base.extend(ClassMethods) + base.class_eval do + attr_accessor :rendered_action_cache, :action_cache_path + alias_method_chain :protected_instance_variables, :action_caching + end + end + + module ClassMethods + # Declares that +actions+ should be cached. + # See ActionController::Caching::Actions for details. + def caches_action(*actions) + return unless perform_caching + around_filter(ActionCacheFilter.new(*actions)) + end + end + + def protected_instance_variables_with_action_caching + protected_instance_variables_without_action_caching + %w(@action_cache_path) + end + + def expire_action(options = {}) + return unless perform_caching + if options[:action].is_a?(Array) + options[:action].dup.each do |action| + expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) + end + else + expire_fragment(ActionCachePath.path_for(self, options)) + end + end + + class ActionCacheFilter #:nodoc: + def initialize(*actions, &block) + @options = actions.extract_options! + @actions = Set.new actions + end + + def before(controller) + return unless @actions.include?(controller.action_name.intern) + cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) + if cache = controller.read_fragment(cache_path.path) + controller.rendered_action_cache = true + set_content_type!(controller, cache_path.extension) + controller.send!(:render_for_text, cache) + false + else + controller.action_cache_path = cache_path + end + end + + def after(controller) + return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller) + controller.write_fragment(controller.action_cache_path.path, controller.response.body) + 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 + end + + class ActionCachePath + attr_reader :path, :extension + + class << self + def path_for(controller, options) + new(controller, options).path + end + end + + def initialize(controller, options = {}) + @extension = extract_extension(controller.request.path) + path = controller.url_for(options).split('://').last + normalize!(path) + add_extension!(path, @extension) + @path = URI.unescape(path) + end + + private + def normalize!(path) + path << 'index' if path[-1] == ?/ + end + + def add_extension!(path, extension) + path << ".#{extension}" if extension + end + + def extract_extension(file_path) + # Don't want just what comes after the last '.' to accommodate multi part extensions + # such as tar.gz. + file_path[/^[^.]+\.(.+)$/, 1] + end + end + end + + # 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 doing 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") + # + # == Fragment stores + # + # By default, cached fragments are stored in memory. The available store options are: + # + # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all + # processes running from the same application directory to access the cached content. + # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its + # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take + # up a lot of memory since each process keeps all the caches in memory. + # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache + # around for all processes, but requires that you run and manage a separate DRb process. + # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead. + # Requires the ruby-memcache library: gem install ruby-memcache. + # + # Configuration examples (MemoryStore is the default): + # + # ActionController::Base.fragment_cache_store = :memory_store + # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" + # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" + # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" + # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter") + module Fragments + def self.included(base) #:nodoc: + base.class_eval do + @@fragment_cache_store = MemoryStore.new + cattr_reader :fragment_cache_store + + # Defines the storage option for cached fragments + def self.fragment_cache_store=(store_option) + store, *parameters = *([ store_option ].flatten) + @@fragment_cache_store = if store.is_a?(Symbol) + store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize) + store_class = ActionController::Caching::Fragments.const_get(store_class_name) + store_class.new(*parameters) + else + store + end + end + end + end + + # Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading, + # writing, or expiring a cached fragment. If the name is a hash, the generated name is the return + # value of url_for on that hash (without the protocol). + def fragment_cache_key(name) + name.is_a?(Hash) ? url_for(name).split("://").last : name + end + + # Called by CacheHelper#cache + def cache_erb_fragment(block, name = {}, options = nil) + unless perform_caching then block.call; return end + + buffer = eval(ActionView::Base.erb_variable, block.binding) + + if cache = read_fragment(name, options) + buffer.concat(cache) + else + pos = buffer.length + block.call + write_fragment(name, buffer[pos..-1], options) + end + end + + # Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) + def write_fragment(name, content, options = nil) + return unless perform_caching + + key = fragment_cache_key(name) + self.class.benchmark "Cached fragment: #{key}" do + fragment_cache_store.write(key, content, options) + end + content + end + + # Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) + def read_fragment(name, options = nil) + return unless perform_caching + + key = fragment_cache_key(name) + self.class.benchmark "Fragment read: #{key}" do + fragment_cache_store.read(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(name, options = nil) + return unless perform_caching + + key = fragment_cache_key(name) + + if key.is_a?(Regexp) + self.class.benchmark "Expired fragments matching: #{key.source}" do + fragment_cache_store.delete_matched(key, options) + end + else + self.class.benchmark "Expired fragment: #{key}" do + fragment_cache_store.delete(key, options) + end + end + end + + + class UnthreadedMemoryStore #:nodoc: + def initialize #:nodoc: + @data = {} + end + + def read(name, options=nil) #:nodoc: + @data[name] + end + + def write(name, value, options=nil) #:nodoc: + @data[name] = value + end + + def delete(name, options=nil) #:nodoc: + @data.delete(name) + end + + def delete_matched(matcher, options=nil) #:nodoc: + @data.delete_if { |k,v| k =~ matcher } + end + end + + module ThreadSafety #:nodoc: + def read(name, options=nil) #:nodoc: + @mutex.synchronize { super } + end + + def write(name, value, options=nil) #:nodoc: + @mutex.synchronize { super } + end + + def delete(name, options=nil) #:nodoc: + @mutex.synchronize { super } + end + + def delete_matched(matcher, options=nil) #:nodoc: + @mutex.synchronize { super } + end + end + + class MemoryStore < UnthreadedMemoryStore #:nodoc: + def initialize #:nodoc: + super + if ActionController::Base.allow_concurrency + @mutex = Mutex.new + MemoryStore.module_eval { include ThreadSafety } + end + end + end + + class DRbStore < MemoryStore #:nodoc: + attr_reader :address + + def initialize(address = 'druby://localhost:9192') + super() + @address = address + @data = DRbObject.new(nil, address) + end + end + + begin + require_library_or_gem 'memcache' + class MemCacheStore < MemoryStore #:nodoc: + attr_reader :addresses + + def initialize(*addresses) + super() + addresses = addresses.flatten + addresses = ["localhost"] if addresses.empty? + @addresses = addresses + @data = MemCache.new(*addresses) + end + end + rescue LoadError + # MemCache wasn't available so neither can the store be + end + + class UnthreadedFileStore #:nodoc: + attr_reader :cache_path + + def initialize(cache_path) + @cache_path = cache_path + end + + def write(name, value, options = nil) #:nodoc: + ensure_cache_path(File.dirname(real_file_path(name))) + File.open(real_file_path(name), "wb+") { |f| f.write(value) } + rescue => e + Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger + end + + def read(name, options = nil) #:nodoc: + File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil + end + + def delete(name, options) #:nodoc: + File.delete(real_file_path(name)) + rescue SystemCallError => e + # If there's no cache, then there's nothing to complain about + end + + def delete_matched(matcher, options) #:nodoc: + search_dir(@cache_path) do |f| + if f =~ matcher + begin + File.delete(f) + rescue SystemCallError => e + # If there's no cache, then there's nothing to complain about + end + end + end + end + + private + def real_file_path(name) + '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')] + end + + def ensure_cache_path(path) + FileUtils.makedirs(path) unless File.exist?(path) + end + + def search_dir(dir, &callback) + Dir.foreach(dir) do |d| + next if d == "." || d == ".." + name = File.join(dir, d) + if File.directory?(name) + search_dir(name, &callback) + else + callback.call name + end + end + end + end + + class FileStore < UnthreadedFileStore #:nodoc: + def initialize(cache_path) + super(cache_path) + if ActionController::Base.allow_concurrency + @mutex = Mutex.new + FileStore.module_eval { include ThreadSafety } + end + end + end + end + + # 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. + module Sweeping + def self.included(base) #:nodoc: + base.extend(ClassMethods) + end + + module ClassMethods #:nodoc: + def cache_sweeper(*sweepers) + return unless perform_caching + configuration = sweepers.extract_options! + sweepers.each do |sweeper| + ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) + sweeper_instance = Object.const_get(Inflector.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) + end + + def after(controller) + callback(:after) + # 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 + + module SqlCache + def self.included(base) #:nodoc: + if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) + base.alias_method_chain :perform_action, :caching + end + end + + def perform_action_with_caching + ActiveRecord::Base.cache do + perform_action_without_caching + end + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb new file mode 100644 index 000000000..f3b8c08d8 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb new file mode 100644 index 000000000..07d2f08d5 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/cookie.rb @@ -0,0 +1,106 @@ +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 + + # Create 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: + # + # name:: the name of the cookie. Required. + # value:: the cookie's value or list of values. + # path:: the path for which this cookie applies. Defaults to the + # base directory of the CGI script. + # domain:: the domain for which this cookie applies. + # expires:: the time at which this cookie expires, as a +Time+ object. + # secure:: whether this cookie is a secure cookie or not (default to + # false). Secure cookies are only transmitted to HTTPS + # servers. + # http_only:: whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP + # More details: 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 = Array(name['value']) + @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 + + # Set whether the Cookie is a secure cookie or not. + def secure=(val) + @secure = val == true + end + + # Set whether the Cookie is an HTTP only cookie or not. + def http_only=(val) + @http_only = val == true + end + + # Convert 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 + + # Parse a raw cookie string into a hash of cookie-name=>Cookie + # 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, values = pairs.split('=',2) + next unless name and values + name = CGI::unescape(name) + values = values.split('&').collect!{|v| CGI::unescape(v) } + unless cookies.has_key?(name) + cookies[name] = new(name, *values) + end + end + end + + cookies + end + end # class Cookie +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb new file mode 100644 index 000000000..9620fd287 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/query_extension.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb new file mode 100644 index 000000000..a01f17f9c --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/session.rb @@ -0,0 +1,73 @@ +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: + begin + require 'securerandom' + + # Generate a 32-character unique id using SecureRandom. + # This is used to generate session ids but may be reused elsewhere. + def self.generate_unique_id(constant = nil) + SecureRandom.hex(16) + end + rescue LoadError + # Generate an 32-character unique id based on a hash of the current time, + # a random number, the process id, and a constant string. This is used + # to generate session ids but may be reused elsewhere. + def self.generate_unique_id(constant = 'foobar') + md5 = Digest::MD5.new + now = Time.now + md5 << now.to_s + md5 << String(now.usec) + md5 << String(rand(0)) + md5 << String($$) + md5 << constant + md5.hexdigest + end + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb new file mode 100644 index 000000000..b0ca63ef2 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_ext/stdinput.rb @@ -0,0 +1,23 @@ +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 + initialize_without_stdinput(type || 'query') + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb new file mode 100644 index 000000000..6a802aa8f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cgi_process.rb @@ -0,0 +1,221 @@ +require 'action_controller/cgi_ext' +require 'action_controller/session/cookie_store' + +module ActionController #:nodoc: + class Base + # Process a request extracted from an 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; end #:nodoc: + + 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 + } unless const_defined?(:DEFAULT_SESSION_OPTIONS) + + 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 + + # 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'] + StringIO.new(raw_post) + else + @cgi.stdinput + end + end + + def query_parameters + @query_parameters ||= self.class.parse_query_parameters(query_string) + end + + def request_parameters + @request_parameters ||= parse_formatted_request_parameters + end + + def cookies + @cgi.cookies.freeze + end + + def host_with_port_without_standard_port_handling + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + elsif http_host = env['HTTP_HOST'] + http_host + elsif server_name = env['SERVER_NAME'] + server_name + else + "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + + def host + host_with_port_without_standard_port_handling.sub(/:\d+$/, '') + end + + def port + if host_with_port_without_standard_port_handling =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb new file mode 100644 index 000000000..7f7ecfff7 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/components.rb @@ -0,0 +1,165 @@ +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 + extend ClassMethods + + helper do + def render_component(options) + @controller.send!(:render_component_as_string, options) + end + end + + # 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 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 + + # 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 + + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb new file mode 100644 index 000000000..19847c695 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/cookies.rb @@ -0,0 +1,84 @@ +module ActionController #:nodoc: + # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request, + # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object + # itself back -- just the value it holds). Examples for writing: + # + # cookies[:user_name] = "david" # => Will set a simple session cookie + # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } + # # => Will set a cookie that expires in 1 hour + # + # Examples for reading: + # + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # + # Example for deleting: + # + # cookies.delete :user_name + # + # All 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 secure cookie or not (default to false). + # Secure cookies are only transmitted to HTTPS servers. + # * <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. You set new cookies using cookies[]= + # (for simple name/value cookies without options). + def [](name) + cookie = @cookies[name.to_s] + if cookie && cookie.respond_to?(:value) + cookie.size > 1 ? cookie.value : cookie.value[0] + end + end + + 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 []=, you can pass in an options + # hash to delete cookies with extra data such as a +path+. + def delete(name, options = {}) + options.stringify_keys! + set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0))) + end + + private + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb new file mode 100644 index 000000000..c8656e4ba --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/dispatcher.rb @@ -0,0 +1,195 @@ +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 + class << self + # 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 + + # Declare a block to be called before each dispatch. + # Run in the order declared. + def before_dispatch(*method_names, &block) + callbacks[:before].concat method_names + callbacks[:before] << block if block_given? + end + + # Declare a block to be called after each dispatch. + # Run in reverse of the order declared. + def after_dispatch(*method_names, &block) + callbacks[:after].concat method_names + callbacks[:after] << block if block_given? + 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) + # Already registered: update the existing callback + if identifier + if callback = callbacks[:prepare].assoc(identifier) + callback[1] = block + else + callbacks[:prepare] << [identifier, block] + end + else + callbacks[:prepare] << block + end + 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_ROOT}/public" if defined? ::RAILS_ROOT + + cattr_accessor :callbacks + self.callbacks = Hash.new { |h, k| h[k] = [] } + + cattr_accessor :unprepared + self.unprepared = true + + + before_dispatch :reload_application + before_dispatch :prepare_application + after_dispatch :flush_logger + after_dispatch :cleanup_application + + if defined? ActiveRecord + to_prepare :activerecord_instantiate_observers do + ActiveRecord::Base.instantiate_observers + end + end + + def initialize(output, request = nil, response = nil) + @output, @request, @response = output, request, response + end + + def dispatch + run_callbacks :before + handle_request + rescue Exception => exception + failsafe_rescue exception + ensure + run_callbacks :after, :reverse_each + 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 reload_application + if Dependencies.load? + Routing::Routes.reload + self.unprepared = true + end + end + + def prepare_application(force = false) + begin + require_dependency 'application' unless defined?(::ApplicationController) + rescue LoadError => error + raise unless error.message =~ /application\.rb/ + end + + ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) + + if unprepared || force + run_callbacks :prepare + self.unprepared = false + end + 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(force = false) + if Dependencies.load? || force + ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) + Dependencies.clear + ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) + end + end + + def flush_logger + RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush) + end + + protected + def handle_request + @controller = Routing::Routes.recognize(@request) + @controller.process(@request, @response).out(@output) + end + + def run_callbacks(kind, enumerator = :each) + callbacks[kind].send!(enumerator) do |callback| + case callback + when Proc; callback.call(self) + when String, Symbol; send!(callback) + when Array; callback[1].call(self) + else raise ArgumentError, "Unrecognized callback #{callback.inspect}" + end + end + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb new file mode 100644 index 000000000..d7fb27617 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/filters.rb @@ -0,0 +1,767 @@ +module ActionController #:nodoc: + module Filters #:nodoc: + def self.included(base) + base.class_eval do + extend ClassMethods + include ActionController::Filters::InstanceMethods + 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 block.call) 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 :filter or both :before and :after. 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 (:only => :index) + # or arrays of actions (:except => [:foo, :bar]). + # + # 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) + 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) + 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) + 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) + 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 append_around_filter A.new, B.new, 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) + filters, conditions = extract_conditions(filters, &block) + filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter| + append_filter_to_chain([filter, conditions]) + end + end + + # If you prepend_around_filter A.new, B.new, 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) + filters, conditions = extract_conditions(filters, &block) + filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter| + prepend_filter_to_chain([filter, conditions]) + end + 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) + 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) + 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) + skip_filter_in_chain(*filters) + end + + # Returns an array of Filter objects for this controller. + def filter_chain + read_inheritable_attribute("filter_chain") || [] + 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(&:filter) + 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(&:filter) + end + + # Returns a mapping between filters and the actions that may run them. + def included_actions #:nodoc: + @included_actions ||= read_inheritable_attribute("included_actions") || {} + end + + # Returns a mapping between filters and actions that may not run them. + def excluded_actions #:nodoc: + @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {} + end + + # Find a filter in the filter_chain where the filter method matches the _filter_ param + # and (optionally) the passed block evaluates to true (mostly used for testing before? + # and after? on the filter). Useful for symbol filters. + # + # The object of type Filter is passed to the block when yielded, not the filter itself. + def find_filter(filter, &block) #:nodoc: + filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first + end + + # Returns true if the filter is excluded from the given action + def filter_excluded_from_action?(filter,action) #:nodoc: + case + when ia = included_actions[filter] + !ia.include?(action) + when ea = excluded_actions[filter] + ea.include?(action) + end + end + + # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but + # contains no logic for calling the actual filters. + class Filter #:nodoc: + attr_reader :filter, :included_actions, :excluded_actions + + def initialize(filter) + @filter = filter + end + + def type + :around + end + + def before? + type == :before + end + + def after? + type == :after + end + + def around? + type == :around + end + + def run(controller) + raise ActionControllerError, 'No filter type: Nothing to do here.' + end + + def call(controller, &block) + run(controller) + end + end + + # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old + # before_filter and after_filter by moving the logic into the filter itself. + class FilterProxy < Filter #:nodoc: + def filter + @filter.filter + end + end + + class BeforeFilterProxy < FilterProxy #:nodoc: + def type + :before + end + + def run(controller) + # only filters returning false are halted. + @filter.call(controller) + if controller.send!(:performed?) + controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected) + end + end + + def call(controller) + yield unless run(controller) + end + end + + class AfterFilterProxy < FilterProxy #:nodoc: + def type + :after + end + + def run(controller) + @filter.call(controller) + end + + def call(controller) + yield + run(controller) + end + end + + class SymbolFilter < Filter #:nodoc: + def call(controller, &block) + controller.send!(@filter, &block) + end + end + + class ProcFilter < Filter #:nodoc: + def call(controller) + @filter.call(controller) + rescue LocalJumpError # a yield from a proc... no no bad dog. + raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') + end + end + + class ProcWithCallFilter < Filter #:nodoc: + def call(controller, &block) + @filter.call(controller, block) + rescue LocalJumpError # a yield from a proc... no no bad dog. + raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') + end + end + + class MethodFilter < Filter #:nodoc: + def call(controller, &block) + @filter.call(controller, &block) + end + end + + class ClassFilter < Filter #:nodoc: + def call(controller, &block) + @filter.filter(controller, &block) + end + end + + class ClassBeforeFilter < Filter #:nodoc: + def call(controller, &block) + @filter.before(controller) + end + end + + class ClassAfterFilter < Filter #:nodoc: + def call(controller, &block) + @filter.after(controller) + end + end + + protected + def append_filter_to_chain(filters, filter_type = :around, &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 = :around, &block) + pos = find_filter_prepend_position(filters, filter_type) + update_filter_chain(filters, filter_type, pos, &block) + end + + def update_filter_chain(filters, filter_type, pos, &block) + new_filters = create_filters(filters, filter_type, &block) + new_chain = filter_chain.insert(pos, new_filters).flatten + write_inheritable_attribute('filter_chain', new_chain) + 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 + filter_chain.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 + filter_chain.each_with_index do |f,i| + return i if f.after? + end + return -1 + end + return 0 + end + + def create_filters(filters, filter_type, &block) #:nodoc: + filters, conditions = extract_conditions(filters, &block) + filters.map! { |filter| find_or_create_filter(filter, filter_type) } + update_conditions(filters, conditions) + filters + end + + def find_or_create_filter(filter, filter_type) + if found_filter = find_filter(filter) { |f| f.type == filter_type } + found_filter + else + f = class_for_filter(filter, filter_type).new(filter) + # apply proxy to filter if necessary + case filter_type + when :before + BeforeFilterProxy.new(f) + when :after + AfterFilterProxy.new(f) + else + f + end + end + end + + # The determination of the filter type was once done at run time. + # This method is here to extract as much logic from the filter run time as possible + def class_for_filter(filter, filter_type) #:nodoc: + case + when filter.is_a?(Symbol) + SymbolFilter + when filter.respond_to?(:call) + if filter.is_a?(Method) + MethodFilter + elsif filter.arity == 1 + ProcFilter + else + ProcWithCallFilter + end + when filter.respond_to?(:filter) + ClassFilter + when filter.respond_to?(:before) && filter_type == :before + ClassBeforeFilter + when filter.respond_to?(:after) && filter_type == :after + ClassAfterFilter + else + raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.') + end + end + + def extract_conditions(*filters, &block) #:nodoc: + filters.flatten! + conditions = filters.extract_options! + filters << block if block_given? + return filters, conditions + end + + def update_conditions(filters, conditions) + return if conditions.empty? + if conditions[:only] + write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only])) + elsif conditions[:except] + write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) + end + end + + def condition_hash(filters, *actions) + actions = actions.flatten.map(&:to_s) + filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) } + end + + def skip_filter_in_chain(*filters, &test) #:nodoc: + filters, conditions = extract_conditions(filters) + filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) } + filters.compact! + + if conditions.empty? + delete_filters_in_chain(filters) + else + remove_actions_from_included_actions!(filters,conditions[:only] || []) + conditions[:only], conditions[:except] = conditions[:except], conditions[:only] + update_conditions(filters,conditions) + end + end + + def remove_actions_from_included_actions!(filters,*actions) + actions = actions.flatten.map(&:to_s) + updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter| + ia = (hash[filter] || []) - actions + ia.empty? ? hash.delete(filter) : hash[filter] = ia + hash + end + write_inheritable_attribute('included_actions', updated_hash) + end + + def delete_filters_in_chain(filters) #:nodoc: + write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) }) + end + + def filter_responds_to_before_and_after(filter) #:nodoc: + filter.respond_to?(:before) && filter.respond_to?(:after) + end + + def proxy_before_and_after_filter(filter) #:nodoc: + return filter unless filter_responds_to_before_and_after(filter) + Proc.new do |controller, action| + filter.before(controller) + + if controller.send!(:performed?) + controller.send!(:halt_filter_chain, filter, :rendered_or_redirected) + else + begin + action.call + ensure + filter.after(controller) + end + end + end + 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 skip_excluded_filters(chain, index) + while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name) + index = index.next + end + [filter, index] + end + + def run_before_filters(chain, index, nesting) + while chain[index] + filter, index = skip_excluded_filters(chain, index) + break unless filter # end of call chain reached + + case filter.type + when :before + filter.run(self) # invoke before filter + index = index.next + break if @before_filter_chain_aborted + when :around + 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 = skip_excluded_filters(chain, index) + break unless filter # end of call chain reached + + case filter.type + when :after + seen_after_filter = true + filter.run(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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb new file mode 100644 index 000000000..692168f23 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/flash.rb @@ -0,0 +1,177 @@ +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 :process_cleanup, :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) + end + + def process_cleanup_with_flash + flash.sweep if @_session + process_cleanup_without_flash + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb new file mode 100644 index 000000000..81b8ff975 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/helpers.rb @@ -0,0 +1,204 @@ +# 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 +ActiveRecord+ 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 + # def current_user + # @current_user ||= User.find(session[:user]) + # end + # 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 + + + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb new file mode 100644 index 000000000..18a503c3a --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/http_authentication.rb @@ -0,0 +1,126 @@ +require 'base64' + +module ActionController + module HttpAuthentication + # Makes it dead easy to do HTTP Basic authentication. + # + # Simple Basic example: + # + # class PostsController < ApplicationController + # USER_NAME, PASSWORD = "dhh", "secret" + # + # before_filter :authenticate, :except => [ :index ] + # + # def index + # render :text => "Everyone can see me!" + # end + # + # def edit + # render :text => "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_basic do |user_name, password| + # user_name == USER_NAME && password == PASSWORD + # end + # end + # end + # + # + # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, + # the regular HTML interface is protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_filter :set_account, :authenticate + # + # protected + # def set_account + # @account = Account.find_by_url_name(request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime::XML, Mime::ATOM + # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } + # @current_user = user + # else + # request_http_basic_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # get( + # "/notes/1.xml", nil, + # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # ) + # + # assert_equal 200, status + # end + # + # + # 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 public/.htaccess (replace the plain one): + # + # 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) + Base64.decode64(authorization(request).split.last || '') + end + + def encode_credentials(user_name, password) + "Basic #{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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb new file mode 100644 index 000000000..25fb1b93a --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/integration.rb @@ -0,0 +1,581 @@ +require 'dispatcher' +require 'stringio' +require 'uri' +require 'action_controller/test_process' + +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 + + # 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 mimicing 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. The parameters may + # be +nil+, a Hash, or a string that is appropriately encoded + # (application/x-www-form-urlencoded or multipart/form-data). The headers + # should be a hash. The keys will automatically be upcased, with the + # prefix 'HTTP_' added if needed. + # + # 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 + class StubCGI < CGI #:nodoc: + attr_accessor :stdinput, :stdoutput, :env_table + + def initialize(env, stdinput = nil) + self.env_table = env + self.stdoutput = StringIO.new + + super + + @stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '') + end + end + + # 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! + + cgi = StubCGI.new(env, data) + Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) + @result = cgi.stdoutput.string + @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 + + parse_result + return status + end + + # Parses the result of the response and extracts the various values, + # like cookies, status, headers, etc. + def parse_result + headers, result_body = @result.split(/\r\n\r\n/, 2) + + @headers = Hash.new { |h,k| h[k] = [] } + headers.each_line do |line| + key, value = line.strip.split(/:\s*/, 2) + @headers[key.downcase] << value + end + + (@headers['set-cookie'] || [] ).each do |string| + name, value = string.match(/^([^=]*)=([^;]*);/)[1,2] + @cookies[name] = value + end + + @status, @status_message = @headers["status"].first.split(/ /) + @status = @status.to_i + 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 + cgi = StubCGI.new('REQUEST_METHOD' => "GET", + 'QUERY_STRING' => "", + "REQUEST_URI" => "/", + "HTTP_HOST" => host, + "SERVER_PORT" => https? ? "443" : "80", + "HTTPS" => https? ? "on" : "off") + ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {}) + 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 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 + 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 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 < Test::Unit::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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb new file mode 100644 index 000000000..63a68c108 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/layout.rb @@ -0,0 +1,326 @@ +module ActionController #:nodoc: + module Layout #:nodoc: + def self.included(base) + base.extend(ClassMethods) + base.class_eval do + # NOTE: Can't use alias_method_chain here because +render_without_layout+ is already + # defined as a publicly exposed method + alias_method :render_with_no_layout, :render + alias_method :render, :render_with_a_layout + + 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 + # + # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout, + # like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout --> + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + # <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 +app/views/layouts/+. + # Otherwise, it will be looked up relative to the template root. + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # <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. + # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller. + # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully + # qualified template and layout names as this example shows: + # + # class WeblogController < ActionController::Base + # def help + # render :action => "help/index", :layout => "help" + # end + # end + # + # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout + # as the third. + # + # 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. + 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: + view_paths.collect do |path| + Dir["#{path}/layouts/**/*"] + end.flatten + 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 layout_directory_exists_cache + @@layout_directory_exists_cache ||= Hash.new do |h, dirname| + h[dirname] = File.directory? dirname + end + 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(response.template.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 + + protected + def render_with_a_layout(options = nil, &block) #:nodoc: + template_with_options = options.is_a?(Hash) + + if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options)) + assert_existence_of_template_file(layout) + + options = options.merge :layout => false if template_with_options + logger.info("Rendering template within #{layout}") if logger + + content_for_layout = render_with_no_layout(options, &block) + erase_render_results + add_variables_to_assigns + @template.instance_variable_set("@content_for_layout", content_for_layout) + response.layout = layout + status = template_with_options ? options[:status] : nil + render_for_text(@template.render_file(layout, true), status) + else + render_with_no_layout(options, &block) + end + end + + + private + def apply_layout?(template_with_options, options) + return false if options == :update + template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout? + end + + def candidate_for_layout?(options) + (options.has_key?(:layout) && options[:layout] != false) || + options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? && + !template_exempt_from_layout?(options[:template] || default_template_name(options[:action])) + end + + def pick_layout(template_with_options, options) + if template_with_options + case layout = options[:layout] + when FalseClass + nil + when NilClass, TrueClass + active_layout if action_has_layout? + else + active_layout(layout) + end + else + active_layout if action_has_layout? + 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 + + # Does a layout directory for this class exist? + # we cache this info in a class level hash + def layout_directory?(layout_name) + view_paths.find do |path| + next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first + self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)] + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb new file mode 100644 index 000000000..4ba4e626e --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_responds.rb @@ -0,0 +1,170 @@ +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 note), 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 + + @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts) + + @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) + args.each { |type| send(type, &block) } + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb new file mode 100644 index 000000000..ec9d2eea6 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_type.rb @@ -0,0 +1,163 @@ +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 + # 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) + # keep track of creation order to keep the subsequent sort stable + list = [] + accept_header.split(/,/).each_with_index do |header, index| + params = header.split(/;\s*q=/) + list << AcceptItem.new(index, *params) unless params.empty? + 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 + + 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) + (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type + end + + private + def method_missing(method, *args) + if method.to_s =~ /(\w+)\?$/ + mime_type = $1.downcase.to_sym + mime_type == @symbol || (mime_type == :html && @symbol == :all) + else + super + end + end + end +end + +require 'action_controller/mime_types' diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb new file mode 100644 index 000000000..71706b4c4 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/mime_types.rb @@ -0,0 +1,20 @@ +# 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 +Mime::Type.register "application/json", :json, %w( text/x-json ) diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb new file mode 100644 index 000000000..94aefc9aa --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/polymorphic_routes.rb @@ -0,0 +1,88 @@ +module ActionController + module PolymorphicRoutes + def polymorphic_url(record_or_hash_or_array, options = {}) + record = extract_record(record_or_hash_or_array) + + 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] == "new" + args.pop + :singular + when record.respond_to?(:new_record?) && record.new_record? + args.pop + :plural + else + :singular + end + + named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options) + send!(named_route, *args) + end + + def polymorphic_path(record_or_hash_or_array) + polymorphic_url(record_or_hash_or_array, :routing_type => :path) + end + + %w(edit new formatted).each do |action| + module_eval <<-EOT, __FILE__, __LINE__ + def #{action}_polymorphic_url(record_or_hash) + polymorphic_url(record_or_hash, :action => "#{action}") + end + + def #{action}_polymorphic_path(record_or_hash) + polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path) + end + EOT + end + + + private + def action_prefix(options) + options[:action] ? "#{options[:action]}_" : "" + end + + def routing_type(options) + "#{options[:routing_type] || "url"}" + end + + def build_named_route_call(records, namespace, inflection, options = {}) + records = Array.new([extract_record(records)]) unless records.is_a?(Array) + base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_" + + method_root = records.reverse.inject(base_segment) do |string, name| + segment = "#{RecordIdentifier.send!("singular_class_name", name)}_" + segment << string + end + + action_prefix(options) + namespace + method_root + routing_type(options) + 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_namespace(record_or_hash_or_array) + returning "" do |namespace| + if record_or_hash_or_array.is_a?(Array) + record_or_hash_or_array.delete_if do |record_or_namespace| + if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol) + namespace << "#{record_or_namespace.to_s}_" + end + end + end + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb new file mode 100644 index 000000000..bdf2753a7 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/record_identifier.rb @@ -0,0 +1,91 @@ +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 + + # Returns plural/singular for a record or class. Example: + # + # partial_path(post) # => "posts/post" + # partial_path(Person) # => "people/person" + def partial_path(record_or_class) + klass = class_from_record_or_class(record_or_class) + "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + 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) + [ prefix, singular_class_name(record_or_class) ].compact * '_' + end + + # The DOM class 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_class(Post.new(:id => 45)) # => "post_45" + # dom_class(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_class(Post.new(:id => 45), :edit) # => "edit_post_45" + def dom_id(record, prefix = nil) + prefix ||= 'new' unless record.id + [ prefix, singular_class_name(record), record.id ].compact * '_' + 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) + singular_class_name(record_or_class).pluralize + 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) + class_from_record_or_class(record_or_class).name.underscore.tr('/', '_') + end + + private + def class_from_record_or_class(record_or_class) + record_or_class.is_a?(Class) ? record_or_class : record_or_class.class + end + end +end
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb new file mode 100755 index 000000000..19948da9c --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request.rb @@ -0,0 +1,730 @@ +require 'tempfile' +require 'stringio' +require 'strscan' + +module ActionController + # HTTP methods which are accepted by default. + ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options )) + + # CgiRequest and TestRequest provide concrete implementations. + class AbstractRequest + cattr_accessor :relative_url_root + remove_method :relative_url_root + + # 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 :get. + # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. + def request_method + @request_method ||= begin + method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase + if ACCEPTED_HTTP_METHODS.include?(method) + method.to_sym + else + raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}" + end + end + end + + # The HTTP request method as a lowercase symbol, such as :get. + # Note, HEAD is returned as :get 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 request.method == :get + def get? + method == :get + end + + # Is this a POST request? Equivalent to request.method == :post + def post? + request_method == :post + end + + # Is this a PUT request? Equivalent to request.method == :put + def put? + request_method == :put + end + + # Is this a DELETE request? Equivalent to request.method == :delete + def delete? + request_method == :delete + end + + # Is this a HEAD request? request.method sees HEAD as :get, so check the + # HTTP method directly. + def head? + request_method == :head + end + + def headers + @env + end + + def content_length + @content_length ||= env['CONTENT_LENGTH'].to_i + end + + # 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 + @content_type ||= Mime::Type.lookup(content_type_without_parameters) + end + + # Returns the accepted MIME type for the request + def accepts + @accepts ||= + if @env['HTTP_ACCEPT'].to_s.strip.empty? + [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included + else + Mime::Type.parse(@env['HTTP_ACCEPT']) + end + end + + # Returns the Mime type for the format used in the request. If there is no format available, the first of the + # accept types will be used. Examples: + # + # GET /posts/5.xml | request.format => Mime::XML + # GET /posts/5.xhtml | request.format => Mime::HTML + # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers) + def format + @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first + end + + + # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension. + # Example: + # + # 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 + 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? + + # Determine 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 before + # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma- + # delimited list in the case of multiple chained proxies; the first is + # the originating IP. + # + # Security note: do not use if IP spoofing is a concern for your + # application. Since remote_ip checks HTTP headers for addresses forwarded + # by proxies, the client may send any IP. remote_addr can't be spoofed but + # also doesn't work behind a proxy, since it's always the proxy's IP. + def remote_ip + return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP' + + if @env.include? 'HTTP_X_FORWARDED_FOR' then + remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip| + ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i + end + + return remote_ips.first.strip unless remote_ips.empty? + end + + @env['REMOTE_ADDR'] + end + + # 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 + + + # Returns the complete URL used for this request + def url + protocol + host_with_port + request_uri + end + + # Return 'https://' if this is an SSL request and 'http://' otherwise. + def protocol + ssl? ? 'https://' : 'http://' + end + + # 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 host + end + + # Returns a host:port string for this request, such as example.com or + # example.com:8080. + def host_with_port + @host_with_port ||= host + port_string + end + + # Returns the port number of this request as an integer. + def port + @port_as_int ||= @env['SERVER_PORT'].to_i + end + + # 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 ["dev", "www"] would be returned for "dev.www.rubyonrails.org". + # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"] + # in "www.rubyonrails.co.uk". + def subdomains(tld_length = 1) + return [] unless named_host?(host) + parts = host.split('.') + parts[0..-(tld_length+2)] + end + + # Return the query string, accounting for server idiosyncracies. + def query_string + if uri = @env['REQUEST_URI'] + uri.split('?', 2)[1] || '' + else + @env['QUERY_STRING'] || '' + end + end + + # Return the request URI, accounting for server idiosyncracies. + # 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. + script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) + uri = @env['PATH_INFO'] + uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil? + unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty? + uri << '?' << env_qs + end + + if uri.nil? + @env.delete('REQUEST_URI') + uri + else + @env['REQUEST_URI'] = uri + end + end + end + + # 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/^#{relative_url_root}/, '') + path || '' + end + + # Returns the path minus the web server relative installation directory. + # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT. + # It can be automatically extracted for Apache setups. If the server is not + # Apache, this method returns an empty string. + def relative_url_root + @@relative_url_root ||= case + when @env["RAILS_RELATIVE_URL_ROOT"] + @env["RAILS_RELATIVE_URL_ROOT"] + when server_software == 'apache' + @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '') + else + '' + end + end + + + # 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. See <tt>symbolized_path_parameters</tt> for symbolized keys. + # + # Example: + # + # {'action' => 'my_action', 'controller' => 'my_controller'} + def path_parameters + @path_parameters ||= {} + end + + + #-- + # Must be implemented in the concrete request + #++ + + # The request body is an IO input stream. + def body + end + + def query_parameters #:nodoc: + end + + def request_parameters #: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 + @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters) + end + + 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) + 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, content_length, env) + parse_request_parameters(read_multipart(body, boundary, content_length, 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.is_a?(UploadedFile) + # 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, content_length, env) + params = Hash.new([]) + boundary = "--" + boundary + quoted_boundary = Regexp.quote(boundary, "n") + buf = "" + bufsize = 10 * 1024 + boundary_end="" + + # start multipart/form-data + body.binmode if defined? body.binmode + boundary_size = boundary.size + EOL.size + content_length -= 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 < content_length + 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 < content_length + body.read(bufsize) + else + body.read(content_length) + end + if c.nil? || c.empty? + raise EOFError, "bad content body" + end + buf.concat(c) + content_length -= 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 + content_length = -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 buf.size == 0 + break if content_length == -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 + end + else + top << value + end + elsif top.is_a? Hash + key = CGI.unescape(key) + parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) + return top[key] ||= value + 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." + end + end + + module UploadedFile + def self.included(base) + base.class_eval do + attr_accessor :original_path, :content_type + alias_method :local_path, :path + end + end + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + class UploadedStringIO < StringIO + include UploadedFile + end + + class UploadedTempfile < Tempfile + include UploadedFile + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb new file mode 100644 index 000000000..75f9c0b28 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_forgery_protection.rb @@ -0,0 +1,132 @@ +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 + + module ClassMethods + # Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not + # a forged link from another site. This 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 indempotent anyway. + # + # You turn this on with the #protect_from_forgery method, which will perform the check and raise + # an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add + # a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message + # given through public/422.html. + # + # Learn more about CSRF (Cross-Site Request Forgery) attacks: + # + # * 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" + # + # If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the + # authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself. + # + # 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 + # + # If you are upgrading from Rails 1.x, disable forgery protection to + # simplify your tests. Add this to config/environments/test.rb: + # + # # Disable request forgery protection in test environment + # config.action_controller.allow_forgery_protection = false + # + # Valid Options: + # + # * <tt>:only/:except</tt> - passed to the before_filter call. Set which actions are verified. + # * <tt>:secret</tt> - Custom salt used to generate the form_authenticity_token. + # 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.format.html? || request.format.js? + end + + # Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash. + def form_authenticity_token + @form_authenticity_token ||= if 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 + elsif session.nil? + raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb new file mode 100755 index 000000000..62f6e665f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/request_profiler.rb @@ -0,0 +1,138 @@ +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(File.read(script_path)) + reset! + end + + def benchmark(n) + @quiet = true + print ' ' + result = Benchmark.realtime do + n.times do |i| + run + print i % 10 == 0 ? 'x' : '.' + $stdout.flush + end + end + puts + result + ensure + @quiet = false + end + + def say(message) + puts " #{message}" unless @quiet + end + + private + def define_run_method(script) + instance_eval "def run; #{script}; end", __FILE__, __LINE__ + 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 + + results = RubyProf.profile { benchmark(sandbox) } + + show_profile_results results + results + end + + def benchmark(sandbox) + sandbox.request_count = 0 + elapsed = sandbox.benchmark(options[:n]).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 } + 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 [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i } + opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = 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 + require 'ruby-prof' + #RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS + rescue LoadError + abort '`gem install ruby-prof` to use the profiler' + end + end + + def show_profile_results(results) + File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file| + RubyProf::GraphHtmlPrinter.new(results).print(file) + `#{options[:open] % file.path}` if options[:open] + end + + File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file| + RubyProf::FlatPrinter.new(results).print(file) + `#{options[:open] % file.path}` if options[:open] + end + end + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb new file mode 100644 index 000000000..b91115a93 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/rescue.rb @@ -0,0 +1,258 @@ +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 = { + 'ActionController::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.class_inheritable_array :rescue_handlers + base.rescue_handlers = [] + + base.extend(ClassMethods) + 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 + + # Rescue exceptions raised in controller actions. + # + # <tt>rescue_from</tt> receives a series of exception classes or class + # names, and a trailing :with option with the name of a method or a Proc + # object to be called to handle them. Alternatively a block can be given. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception + # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render :xml => exception, :status => 500 + # end + # + # protected + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # end + # end + def rescue_from(*klasses, &block) + options = klasses.extract_options! + unless options.has_key?(:with) + block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.") + end + + klasses.each do |klass| + key = if klass.is_a?(Class) && klass <= Exception + klass.name + elsif klass.is_a?(String) + klass + else + raise(ArgumentError, "#{klass} is neither an Exception nor a String") + end + + # Order is important, we put the pair at the end. When dealing with an + # exception we will follow the documented order going from right to left. + rescue_handlers << [key, options[:with]] + end + end + end + + protected + # Exception handler called when the performance of an action raises an exception. + def rescue_action(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 + + # 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.s + 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_ROOT}/public/#{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 and request.remote_ip == LOCALHOST + end + + # Render detailed diagnostics for unhandled exceptions rescued from + # a controller action. + def rescue_action_locally(exception) + add_variables_to_assigns + @template.instance_variable_set("@exception", exception) + @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub"))) + @template.send!(:assign_variables_from_controller) + + @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false)) + + response.content_type = Mime::HTML + render_for_file(rescues_path("layout"), response_code_for_rescue(exception)) + end + + # Tries to rescue the exception by looking up and calling a registered handler. + def rescue_action_with_handler(exception) + if handler = handler_for_rescue(exception) + if handler.arity != 0 + handler.call(exception) + else + handler.call + end + true # don't rely on the return value of the handler + end + end + + private + def perform_action_with_rescue #:nodoc: + perform_action_without_rescue + rescue Exception => exception # errors from action performed + return if rescue_action_with_handler(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 handler_for_rescue(exception) + # We go from right to left because pairs are pushed onto rescue_handlers + # as rescue_from declarations are found. + _, handler = *rescue_handlers.reverse.detect do |klass_name, handler| + # The purpose of allowing strings in rescue_from is to support the + # declaration of handler associations for exception classes whose + # definition is yet unknown. + # + # Since this loop needs the constants it would be inconsistent to + # assume they should exist at this point. An early raised exception + # could trigger some other handler and the array could include + # precisely a string whose corresponding constant has not yet been + # seen. This is why we are tolerant to unknown constants. + # + # Note that this tolerance only matters if the exception was given as + # a string, otherwise a NameError will be raised by the interpreter + # itself when rescue_from CONSTANT is executed. + klass = self.class.const_get(klass_name) rescue nil + klass ||= klass_name.constantize rescue nil + exception.is_a?(klass) if klass + end + + case handler + when Symbol + method(handler) + when Proc + handler.bind(self) + end + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb new file mode 100644 index 000000000..8ec6b84a5 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/resources.rb @@ -0,0 +1,529 @@ +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 + class Resource #:nodoc: + attr_reader :collection_methods, :member_methods, :new_methods + attr_reader :path_prefix, :name_prefix + attr_reader :plural, :singular + attr_reader :options + + def initialize(entities, options) + @plural ||= entities + @singular ||= options[:singular] || plural.to_s.singularize + + @options = options + + arrange_actions + add_default_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}/#{plural}" + end + + def new_path + @new_path ||= "#{path}/new" + end + + def member_path + @member_path ||= "#{path}/:id" + end + + def nesting_path_prefix + @nesting_path_prefix ||= "#{path}/:#{singular}_id" + end + + def nesting_name_prefix + "#{name_prefix}#{singular}_" + end + + def action_separator + @action_separator ||= Base.resource_action_separator + end + + def uncountable? + @singular.to_s == @plural.to_s + 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_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 :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| %> + # + # 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> + # 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 :collection, but for actions that operate on a specific member. + # * <tt>:new</tt> - same as :collection, 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 :method value for the method-specific routes. + # * <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 :article_id. + # + # article_comments_url(@article) + # article_comment_url(@article, @comment) + # + # article_comments_url(:article_id => @article) + # article_comment_url(:article_id => @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 :name_prefix 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) + # + # 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 + # /account 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 map.resources for general conventions. These are the main differences: + # * A singular name is given to map.resource. The default controller name is still taken from the plural name. + # * To specify a custom plural name, use the :plural option. There is no :singular 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(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &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(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block) + end + end + end + + def map_associations(resource, options) + path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" + name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" + + Array(options[:has_many]).each do |association| + resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace]) + end + + Array(options[:has_one]).each do |association| + resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace]) + end + end + + def map_collection_actions(map, resource) + resource.collection_methods.each do |method, actions| + actions.each do |action| + action_options = action_options_for(action, resource, method) + map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options) + map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options) + end + end + end + + def map_default_collection_actions(map, resource) + index_action_options = action_options_for("index", resource) + index_route_name = "#{resource.name_prefix}#{resource.plural}" + + if resource.uncountable? + index_route_name << "_index" + end + + map.named_route(index_route_name, resource.path, index_action_options) + map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options) + + create_action_options = action_options_for("create", resource) + map.connect(resource.path, create_action_options) + map.connect("#{resource.path}.:format", create_action_options) + end + + def map_default_singleton_actions(map, resource) + create_action_options = action_options_for("create", resource) + map.connect(resource.path, create_action_options) + map.connect("#{resource.path}.:format", create_action_options) + end + + def map_new_actions(map, resource) + resource.new_methods.each do |method, actions| + actions.each do |action| + action_options = action_options_for(action, resource, method) + if action == :new + map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options) + map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options) + else + map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options) + map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options) + end + end + end + end + + def map_member_actions(map, resource) + resource.member_methods.each do |method, actions| + actions.each do |action| + action_options = action_options_for(action, resource, method) + map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options) + map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options) + end + end + + show_action_options = action_options_for("show", resource) + map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options) + map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options) + + update_action_options = action_options_for("update", resource) + map.connect(resource.member_path, update_action_options) + map.connect("#{resource.member_path}.:format", update_action_options) + + destroy_action_options = action_options_for("destroy", resource) + map.connect(resource.member_path, destroy_action_options) + map.connect("#{resource.member_path}.:format", destroy_action_options) + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb new file mode 100755 index 000000000..1d9f6676b --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/response.rb @@ -0,0 +1,76 @@ +require 'digest/md5' + +module ActionController + class AbstractResponse #:nodoc: + DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } + attr_accessor :request + attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout + + def initialize + @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] + end + + def content_type=(mime_type) + self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type + end + + def content_type + content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0] + content_type.blank? ? nil : content_type + end + + def charset=(encoding) + self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}" + end + + def charset + charset = String(headers["Content-Type"] || headers["type"]).split(";")[1] + charset.blank? ? nil : charset.strip.split("=")[1] + end + + def redirect(to_url, response_status) + self.headers["Status"] = response_status + self.headers["Location"] = to_url + + self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>" + end + + def prepare! + handle_conditional_get! + convert_content_type! + set_content_length! + end + + + private + def handle_conditional_get! + if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty? + self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}") + self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] + + if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag'] + self.headers['Status'] = '304 Not Modified' + self.body = '' + end + 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! + self.headers["Content-Length"] = body.size unless body.respond_to?(:call) + end + end +end
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb new file mode 100644 index 000000000..edd4683f6 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing.rb @@ -0,0 +1,1499 @@ +require 'cgi' +require 'uri' +require 'action_controller/polymorphic_routes' +require 'action_controller/routing_optimisation' + +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 + + 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 + +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 routes.rb in your RAILS_ROOT/config directory. + # + # 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 + # :controller followed by an :action that in turn is fed some :id. + # + # 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 +:id+. + # + # == Route priority + # + # Not all routes are created equally. Routes have priority defined by the + # order of appearance of the routes in the routes.rb 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 +:defaults+ key. + # + # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' } + # + # == 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 # => '' + # + # 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 below maps to: + # # params = {:year => '2005', :month => '11', :day => '06'} + # # http://localhost:3000/articles/2005/11/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})?/ } + # + # == 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 +reload!+. + # + # == 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 + def with_controllers(names) + prior_controllers = @possible_controllers + use_controllers! names + yield + ensure + use_controllers! prior_controllers + end + + 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{\w+[/\\]\.\.[/\\]} + path.gsub!(%r{\w+[/\\]\.\.[/\\]}, "") while path.match(re) + path + end + + # start with longest path, first + paths = paths.uniq.sort_by { |path| - path.length } + end + + 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 + + def use_controllers!(controller_names) + @possible_controllers = controller_names + end + + 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 + + class Route #:nodoc: + attr_accessor :segments, :requirements, :conditions, :optimise + + def initialize + @segments = [] + @requirements = {} + @conditions = {} + @optimise = true + 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 + + # 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.source}\\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] === 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 += Regexp.new(segment.regexp_chunk).number_of_captures + x + end + extraction.compact + end + + # Write the real generation implementation and then resend the message. + def generate(options, hash, expire_on = {}) + write_generation + generate options, hash, expire_on + end + + def generate_extras(options, hash, expire_on = {}) + write_generation + generate_extras options, hash, expire_on + 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 + # :controller 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 + + # 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 + + # Write the real recognition implementation and then resend the message. + def recognize(path, environment={}) + write_recognition + recognize path, environment + 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) + unless defined? @matching_prepared + @controller_requirement = requirement_for(:controller) + @action_requirement = requirement_for(:action) + @matching_prepared = true + end + + (@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 + + protected + 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 + + end + + class Segment #:nodoc: + RESERVED_PCHAR = ':@&=+$,;' + UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze + + attr_accessor :is_optional + alias_method :optional?, :is_optional + + def initialize + self.is_optional = false + 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_accessor :value, :raw + alias_method :raw?, :raw + + def initialize(value = nil) + super() + self.value = value + end + + def interpolation_chunk + raw? ? value : super + end + + def regexp_chunk + chunk = Regexp.escape(value) + optional? ? Regexp.optionalize(chunk) : chunk + 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) + super(value) + self.raw = true + self.is_optional = true + end + + def optionality_implied? + true + end + end + + class DynamicSegment < Segment #:nodoc: + attr_accessor :key, :default, :regexp + + def initialize(key = nil, options = {}) + super() + self.key = key + self.default = options[:default] if options.key? :default + self.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.source}\\Z" if regexp + end + def regexp_chunk + regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)" + end + + def build_pattern(pattern) + chunk = regexp_chunk + chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 + pattern = "#{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 + + 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 + + # 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: + RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/" + UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze + + def interpolation_chunk(value_code = "#{local_name}") + "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}" + 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 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 + + class RouteBuilder #:nodoc: + attr_accessor :separators, :optional_separators + + def initialize + self.separators = Routing::SEPARATORS + self.optional_separators = %w( / ) + end + + def separator_pattern(inverted = false) + "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" + end + + def interval_regexp + Regexp.new "(.*?)(#{separators.source}|$)" + 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 + # #optional? 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 + case key + when :controller then ControllerSegment.new(key) + else DynamicSegment.new key + end + when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) + when /\A\?(.*?)\?/ + returning segment = StaticSegment.new($1) do + segment.is_optional = true + end + when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) + when Regexp.new(separator_pattern) then + returning segment = DividerSegment.new($&) do + segment.is_optional = (optional_separators.include? $&) + end + 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.dup + + if options[:namespace] + options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}" + options.delete(:path_prefix) + options.delete(:name_prefix) + options.delete(:namespace) + end + + requirements = (options.delete(:requirements) || {}).dup + defaults = (options.delete(:defaults) || {}).dup + conditions = (options.delete(:conditions) || {}).dup + + 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 + 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 :action. 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) + + route = Route.new + + route.segments = segments + route.requirements = requirements + route.conditions = conditions + + if !route.significant_keys.include?(:action) && !route.requirements[:action] + route.requirements[:action] = "index" + route.significant_keys << :action + end + + # Routes cannot use the current string interpolation method + # if there are user-supplied :requirements as the interpolation + # code won't raise RoutingErrors when generating + if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION + route.optimise = false + end + + if !route.significant_keys.include?(:controller) + raise ArgumentError, "Illegal route: the :controller must be specified!" + end + + route + end + end + + 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 = {}) + 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.last.is_a?(Hash) ? args.pop : {} + 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 + + def initialize + self.routes = [] + self.named_routes = NamedRouteCollection.new + 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 + 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! + install_helpers + end + + # reload! will always force a reload whereas load checks the timestamp first + alias reload! load! + + def reload + if @routes_last_modified && defined?(RAILS_ROOT) + mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").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 defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes + load File.join("#{RAILS_ROOT}/config/routes.rb") + @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").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={}) + routes.each do |route| + result = route.recognize(path, environment) and return result + end + + 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 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 + + Routes = RouteSet.new + + ::Inflector.module_eval do + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb new file mode 100644 index 000000000..ba4aeb4e8 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/routing_optimisation.rb @@ -0,0 +1,119 @@ +module ActionController + module Routing + # Much of the slow performance from routes comes from the + # complexity of expiry, :requirements 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 + # :requirements, 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 + def initialize(route, kind) + @route = route + @kind = kind + end + + def guard_condition + 'false' + end + + def generation_code + 'nil' + end + + def source_code + if applicable? + "return #{generation_code} if #{guard_condition}\n" + else + "\n" + end + end + + # Temporarily disabled :url 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 person_url(@person), 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_condition + 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 + "defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)" + else + "defined?(request) && request && 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 << '#{request.relative_url_root if request.relative_url_root}' + + # The last entry in route.segments 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_condition + "defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)" + end + + # This case uses almost the same code as positional arguments, + # but add an args.last.to_query on the end + def generation_code + super.insert(-2, '?#{args.last.to_query}') + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb new file mode 100644 index 000000000..14747c505 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/active_record_store.rb @@ -0,0 +1,336 @@ +require 'cgi' +require 'cgi/session' +require 'digest/md5' +require 'base64' + +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 config/environment.rb: + # 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 session.model.id = session.session_id 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) Base64.encode64(Marshal.dump(data)) if data end + def unmarshal(data) Marshal.load(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 +Base64.encode64(Marshal.dump(data))+ and + # unmarshaling data is +Marshal.load(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) Base64.encode64(Marshal.dump(data)) if data end + def unmarshal(data) Marshal.load(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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb new file mode 100644 index 000000000..086f5a87a --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/cookie_store.rb @@ -0,0 +1,164 @@ +require 'cgi' +require 'cgi/session' +require 'base64' # to convert Marshal.dump to ASCII +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: +# :secret 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. +# +# Example: :secret => '449fe2e7daee471bffae2fd8dc02313d' +# :secret => Proc.new { User.current_user.secret_key } +# +# :digest 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'] + } + + # 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' => '', '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 = Base64.encode64(Marshal.dump(session)).chop + CGI.escape "#{data}--#{generate_digest(data)}" + end + + # Unmarshal cookie data to a hash and verify its integrity. + def unmarshal(cookie) + if cookie + data, digest = CGI.unescape(cookie).split('--') + unless digest == generate_digest(data) + delete + raise TamperedWithCookie + end + Marshal.load(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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb new file mode 100644 index 000000000..6f90db674 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_server.rb @@ -0,0 +1,32 @@ +#!/usr/local/bin/ruby -w + +# 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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb new file mode 100644 index 000000000..4feb2636e --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/drb_store.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb new file mode 100644 index 000000000..2f08af663 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session/mem_cache_store.rb @@ -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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb new file mode 100644 index 000000000..fabb6e7f6 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/session_management.rb @@ -0,0 +1,151 @@ +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 (:cookie_store), but you can also specify one of the other included stores + # (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) 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 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?) } + # + # All session options described for ActionController::Base.process_cgi + # are valid arguments. + def session(*args) + options = args.extract_options! + + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb new file mode 100644 index 000000000..4977c7949 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/status_codes.rb @@ -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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb new file mode 100644 index 000000000..42fe42983 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/streaming.rb @@ -0,0 +1,141 @@ +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 + }.freeze + + protected + # Sends the file by 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. + # + # Be careful to sanitize the path parameter if it coming from a web + # page. send_file(params[:path]) 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 File.basename(path). + # * <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>: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 :filename overrides this option). + # + # 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). + # 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[: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 + + # 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]}" unless logger.nil? + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb new file mode 100644 index 000000000..64b34650b --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb @@ -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> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb new file mode 100644 index 000000000..b322b0aaa --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/_trace.erb @@ -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>
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb new file mode 100644 index 000000000..032f945ed --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/diagnostics.erb @@ -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", false) %> + +<%= render_file(@rescues_path + "/_request_and_response.erb", false) %> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb new file mode 100644 index 000000000..d38f3e67f --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/layout.erb @@ -0,0 +1,29 @@ +<html> +<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>
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb new file mode 100644 index 000000000..dbfdf7694 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/missing_template.erb @@ -0,0 +1,2 @@ +<h1>Template is missing</h1> +<p><%=h @exception.message %></p> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb new file mode 100644 index 000000000..ccfa858cc --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/routing_error.erb @@ -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 %> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb new file mode 100644 index 000000000..eda64db3e --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/template_error.erb @@ -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", false) %> +<% @exception = @real_exception %> + +<%= render_file(@rescues_path + "/_request_and_response.erb", false) %> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb new file mode 100644 index 000000000..683379da1 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/templates/rescues/unknown_action.erb @@ -0,0 +1,2 @@ +<h1>Unknown action</h1> +<p><%=h @exception.message %></p> diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb new file mode 100644 index 000000000..d81cb5dae --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_case.rb @@ -0,0 +1,53 @@ +require 'active_support/test_case' + +module ActionController + class NonInferrableControllerError < ActionControllerError + def initialize(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" + end + end + + class TestCase < ActiveSupport::TestCase + @@controller_class = nil + class << self + 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.class_eval do + def rescue_action(e) + raise e + end + end + end + end + + def setup + @controller = self.class.controller_class.new + @request = TestRequest.new + @response = TestResponse.new + end + end +end
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb new file mode 100644 index 000000000..885331159 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/test_process.rb @@ -0,0 +1,520 @@ +require 'action_controller/assertions' + +module ActionController #:nodoc: + class Base + # 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 + add_variables_to_assigns + 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, :env + 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 + 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'] ||= url_encoded_request_parameters + end + + def port=(number) + @env["SERVER_PORT"] = number.to_i + @port_as_int = nil + 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 + 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(",") + end + + def remote_addr=(addr) + @env['REMOTE_ADDR'] = addr + end + + def remote_addr + @env['REMOTE_ADDR'] + end + + def request_uri + @request_uri || super + end + + def path + @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 = {} + @request_method, @accepts, @content_type = nil, nil, nil + end + + def referer + @env["HTTP_REFERER"] + 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 + headers['Status'][0,3].to_i rescue 0 + end + + # returns a String to ensure compatibility with Net::HTTPResponse + def code + headers['Status'].to_s.split(' ')[0] + end + + def message + headers['Status'].to_s.split(' ',2)[1] + end + + # was the response successful? + def success? + response_code == 200 + 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 path of the file which was used to + # render this response (or nil) + def rendered_file(with_controller=false) + unless template.first_render.nil? + unless with_controller + template.first_render + else + template.first_render.split('/').last || template.first_render + end + end + end + + # was this template rendered by a file? + def rendered_with_file? + !rendered_file.nil? + end + + # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!) + 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 + # Example: + # + # 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 + + class TestResponse < AbstractResponse #:nodoc: + include TestResponseBehavior + end + + class TestSession #:nodoc: + attr_accessor :session_id + + def initialize(attributes = nil) + @session_id = '' + @attributes = attributes + @saved_attributes = nil + end + + def data + @attributes ||= @saved_attributes || {} + end + + def [](key) + data[key] + end + + def []=(key, value) + data[key] = 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_reader :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.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 + %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_variables.include?(iv_name) || instance_variables.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! + + @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 follow_redirect + redirected_controller = @response.redirected_to[:controller] + if redirected_controller && redirected_controller != @controller.controller_name + raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})" + end + + get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys) + end + + 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) + return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + return super + end + + # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example: + # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') + # + # To upload binary files on Windows, pass :binary 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 map.draw { map.connect ... }: + # + # 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb new file mode 100644 index 000000000..c650763fd --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/url_rewriter.rb @@ -0,0 +1,135 @@ +module ActionController + # Write URLs from arbitrary places in your codebase, such as your mailers. + # + # Example: + # + # class MyMailer + # include ActionController::UrlWriter + # default_url_options[:host] = 'www.basecamphq.com' + # + # def signup_url(token) + # url_for(:controller => 'signup', action => 'index', :token => token) + # end + # end + # + # In addition to providing +url_for+, named routes are also accessible after + # including UrlWriter. + module UrlWriter + # The default options for urls written by this writer. Typically a :host 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. + # + # Any other key(:controller, :action, etc...) given to <tt>url_for</tt> 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', :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("://") #dont add separator if its already been specified in :protocol + + 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].each { |k| options.delete k } + end + + anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options.key?(:anchor) + url << Routing::Routes.generate(options, {}) + url << anchor if anchor + + return 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 << @request.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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb new file mode 100644 index 000000000..607fd186b --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb @@ -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) + + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb new file mode 100644 index 000000000..472c5b2ba --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb @@ -0,0 +1,530 @@ +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\[/) + scanner.scan_until(/\]\]>/) + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb new file mode 100644 index 000000000..1eb426aea --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -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*[^:;]*(;|$))*$/ + 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(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
\ No newline at end of file diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb new file mode 100644 index 000000000..1a3c77025 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -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 seprate 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 varient 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb new file mode 100644 index 000000000..b950e8462 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb @@ -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(/\]\]>/) + 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 diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb new file mode 100644 index 000000000..6d645c3e1 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb @@ -0,0 +1,11 @@ +module HTML #:nodoc: + module Version #:nodoc: + + MAJOR = 0 + MINOR = 5 + TINY = 3 + + STRING = [ MAJOR, MINOR, TINY ].join(".") + + end +end diff --git a/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb b/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb new file mode 100644 index 000000000..e5045fba7 --- /dev/null +++ b/vendor/rails-2.0.2/actionpack/lib/action_controller/verification.rb @@ -0,0 +1,114 @@ +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={}) + filter_opts = { :only => options[:only], :except => options[:except] } + before_filter(filter_opts) do |c| + c.send! :verify_action, options + end + end + end + + def verify_action(options) #:nodoc: + prereqs_invalid = + [*options[:params] ].find { |v| params[v].nil? } || + [*options[:session]].find { |v| session[v].nil? } || + [*options[:flash] ].find { |v| flash[v].nil? } + + if !prereqs_invalid && options[:method] + prereqs_invalid ||= + [*options[:method]].all? { |v| request.method != v.to_sym } + end + + prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil? + + if prereqs_invalid + flash.update(options[:add_flash]) if options[:add_flash] + response.headers.update(options[:add_headers]) if options[:add_headers] + + unless performed? + case + when options[:render] + render(options[:render]) + when options[:redirect_to] + options[:redirect_to] = self.send!(options[:redirect_to]) if options[:redirect_to].is_a?(Symbol) + redirect_to(options[:redirect_to]) + else + head(:bad_request) + end + end + end + end + + private :verify_action + end +end
\ No newline at end of file |