diff options
39 files changed, 933 insertions, 246 deletions
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index d4806623c3..c328db8beb 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -87,8 +87,9 @@ module ActionController #:nodoc: def delete(key, options = {}) options.symbolize_keys! options[:path] = "/" unless options.has_key?(:path) - super(key.to_s) + value = super(key.to_s) @controller.response.delete_cookie(key, options) + value end end end diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb index f79fd54acd..b55f5e7bfc 100644 --- a/actionpack/lib/action_controller/metal/redirector.rb +++ b/actionpack/lib/action_controller/metal/redirector.rb @@ -16,7 +16,7 @@ module ActionController logger.info("Redirected to #{url}") if logger && logger.info? self.status = status self.location = url.gsub(/[\r\n]/, '') - self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>" + self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(url)}\">redirected</a>.</body></html>" end end end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index bbc7f3c8f9..323cce6a2f 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -35,7 +35,7 @@ module ActionController #:nodoc: end def cookies - @response.cookies + @request.cookies.merge(@response.cookies) end def redirect_to_url diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2c4a3a356d..58ebe94a5b 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -396,8 +396,12 @@ module ActionDispatch # 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! + if @integration_session.respond_to?(sym) + returning @integration_session.__send__(sym, *args, &block) do + copy_session_variables! + end + else + super end end end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 31e9c5ef9d..5f28ba6ccb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -167,7 +167,7 @@ module ActionView #:nodoc: module Subclasses end - include Helpers, Rendering, Partials, ::ERB::Util + include Helpers, Rendering, Partials, ::ERB::Util, ActiveSupport::Configurable extend ActiveSupport::Memoizable diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index faa7f2e2e9..15b70ecff5 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -133,9 +133,13 @@ module ActionView # change. You can use something like Live HTTP Headers for Firefox to verify # that the cache is indeed working. module AssetTagHelper - ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" - JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" - STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" + assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public" + ActionView::DEFAULT_CONFIG = { + :assets_dir => assets_dir, + :javascripts_dir => "#{assets_dir}/javascripts", + :stylesheets_dir => "#{assets_dir}/stylesheets", + } + JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) # Returns a link tag that browsers and news readers can use to auto-detect @@ -280,7 +284,7 @@ module ActionView if concat || (ActionController::Base.perform_caching && cache) joined_javascript_name = (cache == true ? "all" : cache) + ".js" - joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name) + joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name) unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path) write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) @@ -431,7 +435,7 @@ module ActionView if concat || (ActionController::Base.perform_caching && cache) joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" - joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name) + joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name) unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path) write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) @@ -630,11 +634,11 @@ module ActionView # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) + def compute_public_path(source, dir, ext = nil, include_host = true) has_request = @controller.respond_to?(:request) source_ext = File.extname(source)[1..-1] - if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))) + if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) source += ".#{ext}" end @@ -700,7 +704,7 @@ module ActionView if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source]) asset_id else - path = File.join(ASSETS_DIR, source) + path = File.join(config.assets_dir, source) asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' if @@cache_asset_timestamps @@ -743,20 +747,20 @@ module ActionView def expand_javascript_sources(sources, recursive = false) if sources.include?(:all) - all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js') + all_javascript_files = collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js') ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq else expanded_sources = sources.collect do |source| determine_source(source, @@javascript_expansions) end.flatten - expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(config.javascripts_dir, "application.js")) expanded_sources end end def expand_stylesheet_sources(sources, recursive) if sources.first == :all - collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css') + collect_asset_files(config.stylesheets_dir, ('**' if recursive), '*.css') else sources.collect do |source| determine_source(source, @@stylesheet_expansions) @@ -803,7 +807,7 @@ module ActionView end def asset_file_path(path) - File.join(ASSETS_DIR, path.split('?').first) + File.join(config.assets_dir, path.split('?').first) end def asset_file_path!(path) diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index e651bc17a9..5b136d4f54 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -83,7 +83,7 @@ module ActionView options when Hash options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) - escape = options.key?(:escape) ? options.delete(:escape) : true + escape = options.key?(:escape) ? options.delete(:escape) : false @controller.send(:url_for, options) when :back escape = false @@ -93,7 +93,7 @@ module ActionView polymorphic_path(options) end - (escape ? escape_once(url) : url).html_safe! + escape ? escape_once(url).html_safe! : url end # Creates a link tag of the given +name+ using a URL created by the set diff --git a/actionpack/lib/action_view/safe_buffer.rb b/actionpack/lib/action_view/safe_buffer.rb index 8ba9cd80d6..09f44ab26f 100644 --- a/actionpack/lib/action_view/safe_buffer.rb +++ b/actionpack/lib/action_view/safe_buffer.rb @@ -5,7 +5,7 @@ module ActionView #:nodoc: if value.html_safe? super(value) else - super(CGI.escapeHTML(value)) + super(ERB::Util.h(value)) end end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 7199da3441..b429cbf0e6 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -118,6 +118,13 @@ class CookieTest < ActionController::TestCase assert_equal %w{1 2 3}, jar["pages"] end + def test_cookiejar_delete_removes_item_and_returns_its_value + @request.cookies["user_name"] = "david" + @controller.response = @response + jar = ActionController::CookieJar.new(@controller) + assert_equal "david", jar.delete("user_name") + end + def test_delete_cookie_with_path get :delete_cookie_with_path assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 508364d0b5..fe95fb5750 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -199,6 +199,24 @@ class IntegrationTestTest < Test::Unit::TestCase assert_equal ::ActionController::Integration::Session, session2.class assert_not_equal session1, session2 end + + # RSpec mixes Matchers (which has a #method_missing) into + # IntegrationTest's superclass. Make sure IntegrationTest does not + # try to delegate these methods to the session object. + def test_does_not_prevent_method_missing_passing_up_to_ancestors + mixin = Module.new do + def method_missing(name, *args) + name.to_s == 'foo' ? 'pass' : super + end + end + @test.class.superclass.__send__(:include, mixin) + begin + assert_equal 'pass', @test.foo + ensure + # leave other tests as unaffected as possible + mixin.__send__(:remove_method, :method_missing) + end + end end # Tests that integration tests don't call Controller test methods for processing. diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 73870a56bb..375878b755 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -108,6 +108,11 @@ XML head :created, :location => 'created resource' end + def delete_cookie + cookies.delete("foo") + render :nothing => true + end + private def rescue_action(e) raise e @@ -512,6 +517,18 @@ XML assert @request.params[:foo].blank? end + def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set + @request.cookies['foo'] = 'bar' + get :no_op + assert_equal 'bar', cookies['foo'] + end + + def test_should_detect_if_cookie_is_deleted + @request.cookies['foo'] = 'bar' + get :delete_cookie + assert_nil cookies['foo'] + end + %w(controller response request).each do |variable| %w(get post put delete head process).each do |method| define_method("test_#{variable}_missing_for_#{method}_raises_error") do diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 0ff93f1c5d..c8dc4ab461 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -26,11 +26,11 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase assert_equal('value', session[:key]) end - def test_calling_delete_removes_item + def test_calling_delete_removes_item_and_returns_its_value session = ActionController::TestSession.new session[:key] = 'value' assert_equal('value', session[:key]) - session.delete(:key) + assert_equal('value', session.delete(:key)) assert_nil(session[:key]) end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index d94135b04b..57802ebf42 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -3,6 +3,13 @@ require 'abstract_unit' class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG.merge( + :assets_dir => File.dirname(__FILE__) + "/../fixtures/public", + :javascripts_dir => File.dirname(__FILE__) + "/../fixtures/public/javascripts", + :stylesheets_dir => File.dirname(__FILE__) + "/../fixtures/public/stylesheets") + + include ActiveSupport::Configurable + def setup super silence_warnings do @@ -872,6 +879,9 @@ end class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + include ActiveSupport::Configurable + def setup super ActionController::Base.relative_url_root = "/collaboration/hieraki" diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index d64b9492e2..47462b1237 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -3,6 +3,9 @@ require 'abstract_unit' class FormTagHelperTest < ActionView::TestCase tests ActionView::Helpers::FormTagHelper + include ActiveSupport::Configurable + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + def setup super @controller = Class.new do diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 7f6ebc56b7..cec53e479c 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -5,6 +5,9 @@ require 'controller/fake_controllers' RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env) class UrlHelperTest < ActionView::TestCase + include ActiveSupport::Configurable + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + def setup super @controller = Class.new do @@ -19,10 +22,15 @@ class UrlHelperTest < ActionView::TestCase def test_url_for_escapes_urls @controller.url = "http://www.example.com?a=b&c=d" - assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd') + assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd') assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => true) assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false) end + + def test_url_for_escaping_is_safety_aware + assert url_for(:a => 'b', :c => 'd', :escape => true).html_safe?, "escaped urls should be html_safe?" + assert !url_for(:a => 'b', :c => 'd', :escape => false).html_safe?, "non-escaped urls shouldn't be safe" + end def test_url_for_escapes_url_once @controller.url = "http://www.example.com?a=b&c=d" @@ -39,6 +47,16 @@ class UrlHelperTest < ActionView::TestCase assert_equal 'javascript:history.back()', url_for(:back) end + def test_url_for_from_hash_doesnt_escape_ampersand + @controller = TestController.new + @view = ActionView::Base.new + @view.controller = @controller + + path = @view.url_for(:controller => :cheeses, :foo => :bar, :baz => :quux) + + assert_equal '/cheeses?baz=quux&foo=bar', path + end + # todo: missing test cases def test_button_to_with_straight_url assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") @@ -295,7 +313,7 @@ class UrlHelperTest < ActionView::TestCase @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog", :order=>'desc', :page=>'1' }) - assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") + assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") @@ -305,7 +323,7 @@ class UrlHelperTest < ActionView::TestCase @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=2" - assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) + assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=2") diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index b21d8db613..ae627c365d 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -326,6 +326,17 @@ module ActiveResource @password = password end + def auth_type + if defined?(@auth_type) + @auth_type + end + end + + def auth_type=(auth_type) + @connection = nil + @auth_type = auth_type + end + # Sets the format that attributes are sent and received in from a mime type reference: # # Person.format = :json @@ -397,6 +408,7 @@ module ActiveResource @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password + @connection.auth_type = auth_type if auth_type @connection.timeout = timeout if timeout @connection.ssl_options = ssl_options if ssl_options @connection diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 9d551f04e7..98cb1a932b 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -17,7 +17,7 @@ module ActiveResource :head => 'Accept' } - attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options + attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -57,6 +57,11 @@ module ActiveResource @password = password end + # Sets the auth type for remote service. + def auth_type=(auth_type) + @auth_type = legitimize_auth_type(auth_type) + end + # Sets the number of seconds after which HTTP requests to the remote service should time out. def timeout=(timeout) @timeout = timeout @@ -70,31 +75,31 @@ module ActiveResource # Executes a GET request. # Used to get (find) resources. def get(path, headers = {}) - format.decode(request(:get, path, build_request_headers(headers, :get)).body) + with_auth { format.decode(request(:get, path, build_request_headers(headers, :get, self.site.merge(path))).body) } end # Executes a DELETE request (see HTTP protocol documentation if unfamiliar). # Used to delete resources. def delete(path, headers = {}) - request(:delete, path, build_request_headers(headers, :delete)) + with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } end # Executes a PUT request (see HTTP protocol documentation if unfamiliar). # Used to update resources. def put(path, body = '', headers = {}) - request(:put, path, body.to_s, build_request_headers(headers, :put)) + with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) } end # Executes a POST request. # Used to create new resources. def post(path, body = '', headers = {}) - request(:post, path, body.to_s, build_request_headers(headers, :post)) + with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) } end # Executes a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers, :head)) + with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) } end @@ -198,13 +203,70 @@ module ActiveResource end # Builds headers for request to remote service. - def build_request_headers(headers, http_method=nil) - authorization_header.update(default_header).update(http_format_header(http_method)).update(headers) + def build_request_headers(headers, http_method, uri) + authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers) + end + + def response_auth_header + @response_auth_header ||= "" + end + + def with_auth + retried ||= false + yield + rescue UnauthorizedAccess => e + raise if retried || auth_type != :digest + @response_auth_header = e.response['WWW-Authenticate'] + retried = true + retry + end + + def authorization_header(http_method, uri) + if @user || @password + if auth_type == :digest + { 'Authorization' => digest_auth_header(http_method, uri) } + else + { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") } + end + else + {} + end end - # Sets authorization header - def authorization_header - (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) + def digest_auth_header(http_method, uri) + params = extract_params_from_response + + ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") + ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{uri.path}") + + params.merge!('cnonce' => client_nonce) + request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) + "Digest #{auth_attributes_for(uri, request_digest, params)}" + end + + def client_nonce + Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))) + end + + def extract_params_from_response + params = {} + if response_auth_header =~ /^(\w+) (.*)/ + $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } + end + params + end + + def auth_attributes_for(uri, request_digest, params) + [ + %Q(username="#{@user}"), + %Q(realm="#{params['realm']}"), + %Q(qop="#{params['qop']}"), + %Q(uri="#{uri.path}"), + %Q(nonce="#{params['nonce']}"), + %Q(nc="0"), + %Q(cnonce="#{params['cnonce']}"), + %Q(opaque="#{params['opaque']}"), + %Q(response="#{request_digest}")].join(", ") end def http_format_header(http_method) @@ -214,5 +276,11 @@ module ActiveResource def logger #:nodoc: Base.logger end + + def legitimize_auth_type(auth_type) + return :basic if auth_type.nil? + auth_type = auth_type.to_sym + [:basic, :digest].include?(auth_type) ? auth_type : :basic + end end end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 05efac79cf..6a33040243 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,5 +1,6 @@ require 'rubygems' require 'test/unit' +require 'active_support' require 'active_support/test_case' $:.unshift "#{File.dirname(__FILE__)}/../lib" diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb index ca25f437e3..1a7c9ec8a4 100644 --- a/activeresource/test/cases/authorization_test.rb +++ b/activeresource/test/cases/authorization_test.rb @@ -8,46 +8,75 @@ class AuthorizationTest < Test::Unit::TestCase @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") - @authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + + @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA==" ActiveResource::HttpMock.respond_to do |mock| - mock.get "/people/2.xml", @authorization_request_header, @david - mock.put "/people/2.xml", @authorization_request_header, nil, 204 - mock.delete "/people/2.xml", @authorization_request_header, nil, 200 - mock.post "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.get "/people/2.xml", @basic_authorization_request_header, @david + mock.get "/people/1.xml", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' } + mock.put "/people/2.xml", @basic_authorization_request_header, nil, 204 + mock.delete "/people/2.xml", @basic_authorization_request_header, nil, 200 + mock.post "/people/2/addresses.xml", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.head "/people/2.xml", @basic_authorization_request_header, nil, 200 + + mock.get "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.get "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "912c7a643f18cda562b8d9662c47b6f5") }, @david, 200 + mock.get "/people/1.xml", { 'Authorization' => request_digest_auth_header("/people/1.xml", "d76e675c0ecfa2bb1abe01491b068a06") }, @matz, 200 + + mock.put "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "7de8a265a5be3c4c2d3a246562ecd6bd") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.put "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "3fb3b33d9d0b869cc75815aa11faacd9") }, nil, 204 + + mock.delete "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "07dfc32769a34ea3510d3a77d64ca495") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.delete "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "5d438610de7ec163b29096c9afcbb254") }, nil, 200 + + mock.post "/people/2/addresses.xml", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.xml", "966dab13620421f928d051f2b9d7b9af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.post "/people/2/addresses.xml", { 'Authorization' => request_digest_auth_header("/people/2/addresses.xml", "ed540d032c63f8ee34959116c090ec45") }, nil, 201, 'Location' => '/people/1/addresses/5' + + mock.head "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "2854eeb92cce2aed29350ea0ce7ba1e2") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.head "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "07cd4d247e9c130f92ba2501a080b328") }, nil, 200 + end + + # Make client nonce deterministic + class << @authenticated_conn + private + + def client_nonce + 'i-am-a-client-nonce' + end end end def test_authorization_header - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_username_but_no_password @conn = ActiveResource::Connection.new("http://david:@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_password_but_no_username @conn = ActiveResource::Connection.new("http://:test123@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_decoded_credentials_from_url @conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -58,8 +87,8 @@ class AuthorizationTest < Test::Unit::TestCase @authenticated_conn = ActiveResource::Connection.new("http://@localhost") @authenticated_conn.user = 'david' @authenticated_conn.password = 'test123' - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -69,7 +98,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_username_but_no_password @conn = ActiveResource::Connection.new("http://@localhost") @conn.user = "david" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -79,38 +108,119 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_password_but_no_username @conn = ActiveResource::Connection.new("http://@localhost") @conn.password = "test123" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end + def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic + @authenticated_conn.auth_type = :basic + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization = authorization_header["Authorization"].to_s.split + + assert_equal "Basic", authorization[0] + assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] + end + + def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest + @authenticated_conn.auth_type = :digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af"), authorization_header['Authorization'] + end + def test_get david = @authenticated_conn.get("/people/2.xml") assert_equal "David", david["name"] end - + def test_post response = @authenticated_conn.post("/people/2/addresses.xml") assert_equal "/people/1/addresses/5", response["Location"] end - + def test_put response = @authenticated_conn.put("/people/2.xml") assert_equal 204, response.code end - + def test_delete response = @authenticated_conn.delete("/people/2.xml") assert_equal 200, response.code end + def test_head + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + end + + def test_post_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.post("/people/2/addresses.xml") + assert_equal "/people/1/addresses/5", response["Location"] + assert_equal 201, response.code + end + + def test_put_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.put("/people/2.xml") + assert_equal 204, response.code + end + + def test_delete_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.delete("/people/2.xml") + assert_equal 200, response.code + end + + def test_head_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_caches_nonce + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + + # There is no mock for this request with a non-cached nonce. + response = @authenticated_conn.get("/people/1.xml") + assert_equal "Matz", response["name"] + end + + def test_retry_on_401_only_happens_with_digest_auth + assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.xml") } + assert_equal "", @authenticated_conn.send(:response_auth_header) + end + def test_raises_invalid_request_on_unauthorized_requests - assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth + @conn.auth_type = :digest + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_client_nonce_is_not_nil + assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) end protected @@ -119,4 +229,16 @@ class AuthorizationTest < Test::Unit::TestCase @conn.__send__(:handle_response, Response.new(code)) end end + + def blank_digest_auth_header(uri, response) + %Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}") + end + + def request_digest_auth_header(uri, response) + %Q(Digest username="david", realm="RailsTestApp", qop="auth", uri="#{uri}", nonce="#{@nonce}", nc="0", cnonce="i-am-a-client-nonce", opaque="ef6dfb078ba22298d366f99567814ffb", response="#{response}") + end + + def response_digest_auth_header + %Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb") + end end diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 1593e25595..1d3f7891ec 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -163,6 +163,12 @@ class BaseTest < Test::Unit::TestCase assert_equal('test123', Forum.connection.password) end + def test_should_accept_setting_auth_type + Forum.auth_type = :digest + assert_equal(:digest, Forum.auth_type) + assert_equal(:digest, Forum.connection.auth_type) + end + def test_should_accept_setting_timeout Forum.timeout = 5 assert_equal(5, Forum.timeout) diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 2a3e04272a..a2744d7531 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -225,6 +225,21 @@ class ConnectionTest < Test::Unit::TestCase assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') } end + def test_auth_type_can_be_string + @conn.auth_type = 'digest' + assert_equal(:digest, @conn.auth_type) + end + + def test_auth_type_defaults_to_basic + @conn.auth_type = nil + assert_equal(:basic, @conn.auth_type) + end + + def test_auth_type_ignores_nonsensical_values + @conn.auth_type = :wibble + assert_equal(:basic, @conn.auth_type) + end + protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb index 47a17687bf..f3a68b482f 100644 --- a/activesupport/lib/active_support/autoload.rb +++ b/activesupport/lib/active_support/autoload.rb @@ -7,6 +7,8 @@ module ActiveSupport autoload :Callbacks, 'active_support/callbacks' autoload :Concern, 'active_support/concern' autoload :ConcurrentHash, 'active_support/concurrent_hash' + autoload :Configurable, 'active_support/configurable' + autoload :DependencyModule, 'active_support/dependency_module' autoload :DeprecatedCallbacks, 'active_support/deprecated_callbacks' autoload :Deprecation, 'active_support/deprecation' autoload :Gzip, 'active_support/gzip' diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb new file mode 100644 index 0000000000..890f465ce1 --- /dev/null +++ b/activesupport/lib/active_support/configurable.rb @@ -0,0 +1,35 @@ +require "active_support/concern" + +module ActiveSupport + module Configurable + extend ActiveSupport::Concern + + module ClassMethods + def get_config + module_parts = name.split("::") + modules = [Object] + module_parts.each {|name| modules.push modules.last.const_get(name) } + modules.reverse_each do |mod| + return mod.const_get(:DEFAULT_CONFIG) if const_defined?(:DEFAULT_CONFIG) + end + {} + end + + def config + self.config = get_config unless @config + @config + end + + def config=(hash) + @config = ActiveSupport::OrderedOptions.new + hash.each do |key, value| + @config[key] = value + end + end + end + + def config + self.class.config + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index bec303f6ab..c75b59c284 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/load_error' - module ActiveSupport module Testing class ProxyTestResult diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index b738ef334c..7952eb50c3 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -3,10 +3,8 @@ module ActiveSupport module SetupAndTeardown def self.included(base) base.class_eval do - extend ClassMethods - - include ActiveSupport::Callbacks - define_callbacks :test + include ActiveSupport::DeprecatedCallbacks + define_callbacks :setup, :teardown if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions include ForMiniTest @@ -16,33 +14,20 @@ module ActiveSupport end end - module ClassMethods - def setup(*args, &block) - set_callback(:test, :before, *args, &block) - end - - def teardown(*args, &block) - set_callback(:test, :after, *args, &block) - end - - def wrap(*args, &block) - set_callback(:test, :around, *args, &block) - end - end - module ForMiniTest def run(runner) result = '.' begin - run_callbacks :test do - begin - result = super - rescue Exception => e - result = runner.puke(self.class, self.name, e) - end - end + run_callbacks :setup + result = super rescue Exception => e result = runner.puke(self.class, self.name, e) + ensure + begin + run_callbacks :teardown, :enumerator => :reverse_each + rescue Exception => e + result = runner.puke(self.class, self.name, e) + end end result end @@ -70,27 +55,27 @@ module ActiveSupport @_result = result begin begin - run_callbacks :test do - begin - setup - __send__(@method_name) - mocha_verify(assertion_counter) if using_mocha - rescue Mocha::ExpectationError => e - add_failure(e.message, e.backtrace) - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue Exception => e - raise if PASSTHROUGH_EXCEPTIONS.include?(e.class) - add_error(e) - ensure - teardown - end - end + run_callbacks :setup + setup + __send__(@method_name) + mocha_verify(assertion_counter) if using_mocha + rescue Mocha::ExpectationError => e + add_failure(e.message, e.backtrace) rescue Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) rescue Exception => e raise if PASSTHROUGH_EXCEPTIONS.include?(e.class) add_error(e) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue Exception => e + raise if PASSTHROUGH_EXCEPTIONS.include?(e.class) + add_error(e) + end end ensure mocha_teardown if using_mocha diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 2ae22c35fb..0f7ba1918b 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -13,8 +13,6 @@ module ActiveSupport data = StringIO.new(data || '') end - LibXML::XML.default_keep_blanks = false - char = data.getc if char.nil? {} @@ -44,9 +42,9 @@ module LibXML #:nodoc: # hash:: # Hash to merge the converted element into. def to_hash(hash={}) - if text? - raise LibXML::XML::Error if content.length >= LIB_XML_LIMIT - hash[CONTENT_ROOT] = content + if text? || cdata? + raise LibXML::XML::Error if hash[CONTENT_ROOT].to_s.length + content.length >= LIB_XML_LIMIT + hash[CONTENT_ROOT] = hash[CONTENT_ROOT].to_s + content else sub_hash = insert_name_into_hash(hash, name) attributes_to_hash(sub_hash) @@ -88,6 +86,11 @@ module LibXML #:nodoc: # Hash to merge the children into. def children_to_hash(hash={}) each { |child| child.to_hash(hash) } + + if hash.length > 1 && hash[CONTENT_ROOT].blank? + hash.delete(CONTENT_ROOT) + end + attributes_to_hash(hash) hash end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 7a45dab60b..5cbffb81fc 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -95,3 +95,55 @@ end class AlsoDoingNothingTest < ActiveSupport::TestCase end + +# Setup and teardown callbacks. +class SetupAndTeardownTest < ActiveSupport::TestCase + setup :reset_callback_record, :foo + teardown :foo, :sentinel, :foo + + def test_inherited_setup_callbacks + assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain.map(&:method) + assert_equal [:foo], @called_back + assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain.map(&:method) + end + + def setup + end + + def teardown + end + + protected + def reset_callback_record + @called_back = [] + end + + def foo + @called_back << :foo + end + + def sentinel + assert_equal [:foo, :foo], @called_back + end +end + + +class SubclassSetupAndTeardownTest < SetupAndTeardownTest + setup :bar + teardown :bar + + def test_inherited_setup_callbacks + assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain.map(&:method) + assert_equal [:foo, :bar], @called_back + assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain.map(&:method) + end + + protected + def bar + @called_back << :bar + end + + def sentinel + assert_equal [:foo, :bar, :bar, :foo], @called_back + end +end diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb new file mode 100644 index 0000000000..900c8052d6 --- /dev/null +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -0,0 +1,194 @@ +require 'abstract_unit' +require 'active_support/xml_mini' +require 'active_support/core_ext/hash/conversions' + +begin + require 'libxml' +rescue LoadError + # Skip libxml tests +else + +class LibxmlEngineTest < Test::Unit::TestCase + include ActiveSupport + + def setup + @default_backend = XmlMini.backend + XmlMini.backend = 'LibXML' + + LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do + end + + def teardown + XmlMini.backend = @default_backend + end + + def test_exception_thrown_on_expansion_attack + assert_raise LibXML::XML::Error do + attack_xml = %{<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + } + Hash.from_xml(attack_xml) + end + end + + def test_setting_libxml_as_backend + XmlMini.backend = 'LibXML' + assert_equal XmlMini_LibXML, XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse('')) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + <blog> + <posts type="array"> + <post>a post</post> + <post>another post</post> + </posts> + </blog> + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + </products> + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + <book name="america" id="67890" /> + </products> + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + <products> + hello world + </products> + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + <root> + <products> + <book name="america" id="67890" /> + </products> + </root> + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello everyone + </products> + </root> + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_parse_from_io + io = StringIO.new(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + XmlMini.parse(io) + end + + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> + morning + </products> + </root> + eoxml + end + + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + private + def assert_equal_rexml(xml) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + assert_equal(hash, XmlMini.parse(xml)) + end +end + +end diff --git a/railties/Rakefile b/railties/Rakefile index 23f9603b65..0ba5ca10c2 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -28,6 +28,7 @@ task :default => :test task :test do dir = ENV["TEST_DIR"] || "**" Dir["test/#{dir}/*_test.rb"].all? do |file| + next true if file.include?("fixtures") ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file) end or raise "Failures" diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d54120f850..a0e5d6a5a5 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -18,6 +18,10 @@ module Rails @plugin_loader ||= config.plugin_loader.new(self) end + def root + config.root + end + def routes ActionController::Routing::Routes end @@ -102,7 +106,7 @@ module Rails # Create tmp directories initializer :ensure_tmp_directories_exist do %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(config.root_path, 'tmp', dir_to_make)) + FileUtils.mkdir_p(File.join(config.root, 'tmp', dir_to_make)) end end @@ -346,7 +350,7 @@ module Rails # Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt> # defaults to <tt>vendor/plugins</tt> but may also be set to a list of # paths, such as - # config.plugin_paths = ["#{RAILS_ROOT}/lib/plugins", "#{RAILS_ROOT}/vendor/plugins"] + # config.plugin_paths = ["#{config.root}/lib/plugins", "#{config.root}/vendor/plugins"] # # In the default implementation, as each plugin discovered in <tt>plugin_paths</tt> is initialized: # * its +lib+ directory, if present, is added to the load path (immediately after the applications lib directory) @@ -394,7 +398,7 @@ module Rails initializer :load_application_initializers do if config.gems_dependencies_loaded - Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer| + Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer| load(initializer) end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 4a70a4800e..322590f108 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,8 +1,11 @@ +require 'rails/plugin/loader' +require 'rails/plugin/locator' + module Rails class Configuration - attr_accessor :cache_classes, :load_paths, :eager_load_paths, :framework_paths, + attr_accessor :cache_classes, :load_paths, :load_once_paths, :gems_dependencies_loaded, :after_initialize_blocks, - :frameworks, :framework_root_path, :root_path, :plugin_paths, :plugins, + :frameworks, :framework_root_path, :root, :plugin_paths, :plugins, :plugin_loader, :plugin_locators, :gems, :loaded_plugins, :reload_plugins, :i18n, :gems, :whiny_nils, :consider_all_requests_local, :action_controller, :active_record, :action_view, :active_support, @@ -13,31 +16,13 @@ module Rails :eager_load_paths, :dependency_loading, :paths, :serve_static_assets def initialize - set_root_path! - - @framework_paths = [] @load_once_paths = [] @after_initialize_blocks = [] @loaded_plugins = [] @dependency_loading = true - @eager_load_paths = default_eager_load_paths - @load_paths = default_load_paths - @plugin_paths = default_plugin_paths - @frameworks = default_frameworks - @plugin_loader = default_plugin_loader - @plugin_locators = default_plugin_locators - @gems = default_gems - @i18n = default_i18n - @log_path = default_log_path - @log_level = default_log_level - @cache_store = default_cache_store - @view_path = default_view_path - @controller_paths = default_controller_paths - @routes_configuration_file = default_routes_configuration_file - @database_configuration_file = default_database_configuration_file - @serve_static_assets = default_serve_static_assets - - for framework in default_frameworks + @serve_static_assets = true + + for framework in frameworks self.send("#{framework}=", Rails::OrderedOptions.new) end self.active_support = Rails::OrderedOptions.new @@ -47,38 +32,60 @@ module Rails @after_initialize_blocks << blk if blk end - def set_root_path! - raise 'RAILS_ROOT is not set' unless defined?(RAILS_ROOT) - raise 'RAILS_ROOT is not a directory' unless File.directory?(RAILS_ROOT) + def root + @root ||= begin + if defined?(RAILS_ROOT) + root = RAILS_ROOT + else + call_stack = caller.map { |p| p.split(':').first } + root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] } + root_path = File.dirname(root_path) - self.root_path = - # Pathname is incompatible with Windows, but Windows doesn't have - # real symlinks so File.expand_path is safe. - if RUBY_PLATFORM =~ /(:?mswin|mingw)/ - File.expand_path(RAILS_ROOT) + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru") + parent = File.dirname(root_path) + root_path = parent != root_path && parent + end - # Otherwise use Pathname#realpath which respects symlinks. - else - Pathname.new(RAILS_ROOT).realpath.to_s + Object.class_eval("RAILS_ROOT = ''") + + root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd end - @paths = Rails::Application::Root.new(root_path) - @paths.app "app", :load_path => true - @paths.app.metals "app/metal", :eager_load => true - @paths.app.models "app/models", :eager_load => true - @paths.app.controllers "app/controllers", builtin_directories, :eager_load => true - @paths.app.helpers "app/helpers", :eager_load => true - @paths.app.services "app/services", :load_path => true - @paths.lib "lib", :load_path => true - @paths.vendor "vendor", :load_path => true - @paths.vendor.plugins "vendor/plugins" - @paths.tmp "tmp" - @paths.tmp.cache "tmp/cache" - @paths.config "config" - @paths.config.locales "config/locales" - @paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb" - - RAILS_ROOT.replace root_path + root = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? + Pathname.new(root).expand_path.to_s : + Pathname.new(root).realpath.to_s + + # TODO: Remove RAILS_ROOT + RAILS_ROOT.replace(root) + root + end + end + + def root=(root) + Object.class_eval("RAILS_ROOT = ''") unless defined?(RAILS_ROOT) + RAILS_ROOT.replace(root) + @root = root + end + + def paths + @paths ||= begin + paths = Rails::Application::Root.new(root) + paths.app "app", :load_path => true + paths.app.metals "app/metal", :eager_load => true + paths.app.models "app/models", :eager_load => true + paths.app.controllers "app/controllers", builtin_directories, :eager_load => true + paths.app.helpers "app/helpers", :eager_load => true + paths.app.services "app/services", :load_path => true + paths.lib "lib", :load_path => true + paths.vendor "vendor", :load_path => true + paths.vendor.plugins "vendor/plugins" + paths.tmp "tmp" + paths.tmp.cache "tmp/cache" + paths.config "config" + paths.config.locales "config/locales" + paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb" + paths + end end # Enable threaded mode. Allows concurrent requests to controller actions and @@ -105,7 +112,7 @@ module Rails end def framework_root_path - defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root_path}/vendor/rails" + defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root}/vendor/rails" end def middleware @@ -121,63 +128,69 @@ module Rails YAML::load(ERB.new(IO.read(database_configuration_file)).result) end - def default_routes_configuration_file - File.join(root_path, 'config', 'routes.rb') + def routes_configuration_file + @routes_configuration_file ||= File.join(root, 'config', 'routes.rb') end - def default_controller_paths - paths = [File.join(root_path, 'app', 'controllers')] - paths.concat builtin_directories - paths + def controller_paths + @controller_paths ||= begin + paths = [File.join(root, 'app', 'controllers')] + paths.concat builtin_directories + paths + end end - def default_cache_store - if File.exist?("#{root_path}/tmp/cache/") - [ :file_store, "#{root_path}/tmp/cache/" ] - else - :memory_store + def cache_store + @cache_store ||= begin + if File.exist?("#{root}/tmp/cache/") + [ :file_store, "#{root}/tmp/cache/" ] + else + :memory_store + end end end - def default_database_configuration_file - File.join(root_path, 'config', 'database.yml') + def database_configuration_file + @database_configuration_file ||= File.join(root, 'config', 'database.yml') end - def default_view_path - File.join(root_path, 'app', 'views') + def view_path + @view_path ||= File.join(root, 'app', 'views') end - def default_eager_load_paths - %w( + def eager_load_paths + @eager_load_paths ||= %w( app/metal app/models app/controllers app/helpers - ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } + ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } end - def default_load_paths - paths = [] - - # Add the old mock paths only if the directories exists - paths.concat(Dir["#{root_path}/test/mocks/#{RAILS_ENV}"]) if File.exists?("#{root_path}/test/mocks/#{RAILS_ENV}") - - # Add the app's controller directory - paths.concat(Dir["#{root_path}/app/controllers/"]) - - # Followed by the standard includes. - paths.concat %w( - app - app/metal - app/models - app/controllers - app/helpers - app/services - lib - vendor - ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } - - paths.concat builtin_directories + def load_paths + @load_paths ||= begin + paths = [] + + # Add the old mock paths only if the directories exists + paths.concat(Dir["#{root}/test/mocks/#{RAILS_ENV}"]) if File.exists?("#{root}/test/mocks/#{RAILS_ENV}") + + # Add the app's controller directory + paths.concat(Dir["#{root}/app/controllers/"]) + + # Followed by the standard includes. + paths.concat %w( + app + app/metal + app/models + app/controllers + app/helpers + app/services + lib + vendor + ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } + + paths.concat builtin_directories + end end def builtin_directories @@ -185,48 +198,48 @@ module Rails (RAILS_ENV == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : [] end - def default_log_path - File.join(root_path, 'log', "#{RAILS_ENV}.log") + def log_path + @log_path ||= File.join(root, 'log', "#{RAILS_ENV}.log") end - def default_log_level - RAILS_ENV == 'production' ? :info : :debug + def log_level + @log_level ||= RAILS_ENV == 'production' ? :info : :debug end - def default_frameworks - [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] + def frameworks + @frameworks ||= [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] end - def default_plugin_paths - ["#{root_path}/vendor/plugins"] + def plugin_paths + @plugin_paths ||= ["#{root}/vendor/plugins"] end - def default_plugin_loader - require 'rails/plugin/loader' - Plugin::Loader + def plugin_loader + @plugin_loader ||= begin + Plugin::Loader + end end - def default_plugin_locators - require 'rails/plugin/locator' - locators = [] - locators << Plugin::GemLocator if defined? Gem - locators << Plugin::FileSystemLocator + def plugin_locators + @plugin_locators ||= begin + locators = [] + locators << Plugin::GemLocator if defined? Gem + locators << Plugin::FileSystemLocator + end end - def default_i18n - i18n = Rails::OrderedOptions.new - i18n.load_path = [] - - if File.exist?(File.join(RAILS_ROOT, 'config', 'locales')) - i18n.load_path << Dir[File.join(RAILS_ROOT, 'config', 'locales', '*.{rb,yml}')] - i18n.load_path.flatten! - end + def i18n + @i18n ||= begin + i18n = Rails::OrderedOptions.new + i18n.load_path = [] - i18n - end + if File.exist?(File.join(root, 'config', 'locales')) + i18n.load_path << Dir[File.join(root, 'config', 'locales', '*.{rb,yml}')] + i18n.load_path.flatten! + end - def default_serve_static_assets - true + i18n + end end # Adds a single Gem dependency to the rails application. By default, it will require @@ -241,15 +254,15 @@ module Rails # # config.gem 'qrp', :version => '0.4.1', :lib => false def gem(name, options = {}) - @gems << Rails::GemDependency.new(name, options) + gems << Rails::GemDependency.new(name, options) end - def default_gems - [] + def gems + @gems ||= [] end def environment_path - "#{root_path}/config/environments/#{RAILS_ENV}.rb" + "#{root}/config/environments/#{RAILS_ENV}.rb" end def reload_plugins? diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 78b4b057ae..bd7af25bd5 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -54,6 +54,7 @@ module Rails::Generators copy_file "Rakefile" copy_file "README" copy_file "config.ru" + template "Gemfile" end def create_app_files diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile new file mode 100644 index 0000000000..bcbaa432b7 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -0,0 +1,10 @@ +# Gemfile is where you list all of your application's dependencies +# +gem "rails", "<%= Rails::VERSION::STRING %>" +# +# Bundling edge rails: +# gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git" + +# You can list more dependencies here +# gem "nokogiri" +# gem "merb" # ;)
\ No newline at end of file diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 3899b744b0..0f24106353 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -19,14 +19,14 @@ module Rails class Root include PathParent - attr_reader :path + attr_accessor :path + def initialize(path) - raise unless path.is_a?(String) + raise if path.is_a?(Array) @children = {} - # TODO: Move logic from set_root_path initializer - @path = File.expand_path(path) + @path = path @root = self @all_paths = [] end @@ -123,8 +123,10 @@ module Rails end def paths + raise "You need to set a path root" unless @root.path + @paths.map do |path| - path.index('/') == 0 ? path : File.join(@root.path, path) + path.index('/') == 0 ? path : File.expand_path(File.join(@root.path, path)) end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb new file mode 100644 index 0000000000..d90582d3db --- /dev/null +++ b/railties/test/application/configuration_test.rb @@ -0,0 +1,49 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class InitializerTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + Object.send(:remove_const, :RAILS_ROOT) + end + + test "the application root is set correctly" do + require "#{app_path}/config/environment" + assert_equal app_path, Rails.application.root + end + + test "the application root can be set" do + FileUtils.mkdir_p("#{app_path}/hello") + add_to_config <<-RUBY + config.frameworks = [] + config.root = '#{app_path}/hello' + RUBY + require "#{app_path}/config/environment" + assert_equal "#{app_path}/hello", Rails.application.root + end + + test "the application root is detected as where config.ru is located" do + add_to_config <<-RUBY + config.frameworks = [] + RUBY + FileUtils.mv "#{app_path}/config.ru", "#{app_path}/config/config.ru" + require "#{app_path}/config/environment" + assert_equal "#{app_path}/config", Rails.application.root + end + + test "the application root is Dir.pwd if there is no config.ru" do + File.delete("#{app_path}/config.ru") + add_to_config <<-RUBY + config.frameworks = [] + RUBY + + Dir.chdir("#{app_path}/app") do + require "#{app_path}/config/environment" + assert_equal "#{app_path}/app", Rails.application.root + end + end + end +end
\ No newline at end of file diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 0d6eb4147a..0edb29483d 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -5,9 +5,9 @@ module ApplicationTests include ActiveSupport::Testing::Isolation def setup - require "rails/generators" build_app boot_rails + require "rails/generators" end test "generators default values" do diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index d917812383..7599bda8a2 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -8,12 +8,17 @@ else RAILS_ROOT = fixtures end +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activemodel/lib" +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib" $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" +# TODO: Fix this RAILS_ENV stuff +RAILS_ENV = 'test' +require "rails/core" require 'rails/generators' require 'rubygems' -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib" + require 'active_record' require 'action_dispatch' diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index f83e0151a4..245577e8c0 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -25,8 +25,10 @@ module TestHelpers module Paths module_function + TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp])) + def tmp_path(*args) - File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp] + args)) + File.join(TMP_PATH, *args) end def app_path(*args) @@ -88,10 +90,14 @@ module TestHelpers end end + add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' + end + + def add_to_config(str) environment = File.read("#{app_path}/config/environment.rb") if environment =~ /(\n\s*end\s*)\Z/ File.open("#{app_path}/config/environment.rb", 'w') do |f| - f.puts $` + %'\nconfig.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }\n' + $1 + f.puts $` + "\n#{str}\n" + $1 end end end diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index d50882110f..c724799d64 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -12,11 +12,32 @@ class PathsTest < ActiveSupport::TestCase assert_equal "/fiz/baz", root.path end + test "the paths object can be initialized with nil" do + assert_nothing_raised do + Rails::Application::Root.new(nil) + end + end + + test "a paths object initialized with nil can be updated" do + root = Rails::Application::Root.new(nil) + root.app = "app" + root.path = "/root" + assert_equal ["/root/app"], root.app.to_a + end + test "creating a root level path" do @root.app = "/foo/bar" assert_equal ["/foo/bar"], @root.app.to_a end + test "raises exception if root path never set" do + root = Rails::Application::Root.new(nil) + root.app = "app" + assert_raises RuntimeError do + root.app.to_a + end + end + test "creating a root level path without assignment" do @root.app "/foo/bar" assert_equal ["/foo/bar"], @root.app.to_a |