diff --git a/app/metal/itex.rb b/app/metal/itex.rb index b9091c9f..867ab590 100644 --- a/app/metal/itex.rb +++ b/app/metal/itex.rb @@ -18,7 +18,7 @@ class Itex begin require 'nokogiri' def self.xmlparse(text) - Nokogiri::XML(text) { |config| config.options = Nokogiri::XML::ParseOptions::STRICT } + Nokogiri::XML(text) { |config| config.strict } end rescue LoadError require 'rexml/document' diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 338e11d0..9c6b1c3d 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -33,6 +33,7 @@ <%= @web && @web.additional_style ? @web.additional_style.html_safe : '' %> /*]]>*/--> <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> <%- if @web -%> <%- if @web.markup == :markdownMML -%> diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 47df15e9..a75bd522 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -5,3 +5,25 @@ function toggleView(id) (document.getElementById(id).style.display == 'block') ? document.getElementById(id).style.display='none' : document.getElementById(id).style.display='block'; } +/* + * Registers a callback which copies the csrf token into the + * X-CSRF-Token header with each ajax request. Necessary to + * work with rails applications which have fixed + * CVE-2011-0447 +*/ + +Ajax.Responders.register({ + onCreate: function(request) { + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + } +}); diff --git a/vendor/rails/actionmailer/CHANGELOG b/vendor/rails/actionmailer/CHANGELOG index 2b3e4ed7..59f5b187 100644 --- a/vendor/rails/actionmailer/CHANGELOG +++ b/vendor/rails/actionmailer/CHANGELOG @@ -1,3 +1,4 @@ +*2.3.11 (February 9, 2011)* *2.3.10 (October 15, 2010)* *2.3.9 (September 4, 2010)* *2.3.8 (May 24, 2010)* diff --git a/vendor/rails/actionmailer/Rakefile b/vendor/rails/actionmailer/Rakefile index fa671826..e7ef2b23 100644 --- a/vendor/rails/actionmailer/Rakefile +++ b/vendor/rails/actionmailer/Rakefile @@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.11' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' diff --git a/vendor/rails/actionmailer/lib/action_mailer/base.rb b/vendor/rails/actionmailer/lib/action_mailer/base.rb index f8a0a2bc..3e4e7d1f 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/base.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/base.rb @@ -195,6 +195,39 @@ module ActionMailer #:nodoc: # end # end # + # = Multipart Emails with Attachments + # + # Multipart emails that also have attachments can be created by nesting a "multipart/alternative" part + # within an email that has its content type set to "multipart/mixed". This would also need two templates + # in place within +app/views/mailer+ called "welcome_email.text.html.erb" and "welcome_email.text.plain.erb" + # + # class ApplicationMailer < ActionMailer::Base + # def signup_notification(recipient) + # recipients recipient.email_address_with_name + # subject "New account information" + # from "system@example.com" + # content_type "multipart/mixed" + # + # part "multipart/alternative" do |alternative| + # + # alternative.part "text/html" do |html| + # html.body = render_message("welcome_email.text.html", :message => "

HTML content

") + # end + # + # alternative.part "text/plain" do |plain| + # plain.body = render_message("welcome_email.text.plain", :message => "text content") + # end + # + # end + # + # attachment :content_type => "image/png", + # :body => File.read(File.join(RAILS_ROOT, 'public/images/rails.png')) + # + # attachment "application/pdf" do |a| + # a.body = File.read('/Users/mikel/Code/mail/spec/fixtures/attachments/test.pdf') + # end + # end + # end # # = Configuration options # diff --git a/vendor/rails/actionmailer/lib/action_mailer/version.rb b/vendor/rails/actionmailer/lib/action_mailer/version.rb index 8167d903..da9b986a 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/version.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/version.rb @@ -2,7 +2,7 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG index 87e86308..af6b0be2 100644 --- a/vendor/rails/actionpack/CHANGELOG +++ b/vendor/rails/actionpack/CHANGELOG @@ -1,3 +1,7 @@ +*2.3.11 (February 9, 2011)* + +* Two security fixes. CVE-2011-0446, CVE-2011-0447 + *2.3.10 (October 15, 2010)* *2.3.9 (September 4, 2010)* diff --git a/vendor/rails/actionpack/Rakefile b/vendor/rails/actionpack/Rakefile index b97eae09..4c7c4b7b 100644 --- a/vendor/rails/actionpack/Rakefile +++ b/vendor/rails/actionpack/Rakefile @@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.11' + PKG_BUILD) s.add_dependency('rack', '~> 1.1.0') s.require_path = 'lib' diff --git a/vendor/rails/actionpack/lib/action_controller/cookies.rb b/vendor/rails/actionpack/lib/action_controller/cookies.rb index 4857de7a..9eab87b7 100644 --- a/vendor/rails/actionpack/lib/action_controller/cookies.rb +++ b/vendor/rails/actionpack/lib/action_controller/cookies.rb @@ -60,7 +60,7 @@ module ActionController #:nodoc: attr_reader :controller def initialize(controller) - @controller, @cookies = controller, controller.request.cookies + @controller, @cookies, @secure = controller, controller.request.cookies, controller.request.ssl? super() update(@cookies) end @@ -81,7 +81,7 @@ module ActionController #:nodoc: options[:path] = "/" unless options.has_key?(:path) super(key.to_s, options[:value]) - @controller.response.set_cookie(key, options) + @controller.response.set_cookie(key, options) if write_cookie?(options) end # Removes the cookie on the client machine by setting the value to an empty string @@ -126,6 +126,12 @@ module ActionController #:nodoc: def signed @signed ||= SignedCookieJar.new(self) end + + private + + def write_cookie?(cookie) + @secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development? + end end class PermanentCookieJar < CookieJar #:nodoc: diff --git a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb index 24821fff..00308579 100644 --- a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb @@ -76,7 +76,11 @@ module ActionController #:nodoc: 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) + verified_request? || handle_unverified_request + end + + def handle_unverified_request + reset_session end # Returns true or false if a request is verified. Checks: @@ -85,11 +89,10 @@ module ActionController #:nodoc: # * is it a GET request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? def verified_request? - !protect_against_forgery? || - request.method == :get || - request.xhr? || - !verifiable_request_format? || - form_authenticity_token == form_authenticity_param + !protect_against_forgery? || + request.get? || + form_authenticity_token == form_authenticity_param || + form_authenticity_token == request.headers['X-CSRF-Token'] end def form_authenticity_param diff --git a/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb b/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb index 51acab24..de0163d2 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb @@ -195,22 +195,8 @@ module ActionController request_cookies = env["rack.request.cookie_hash"] if (request_cookies.nil? || request_cookies[@key] != sid) || options[:expire_after] - cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid) - cookie << "; domain=#{options[:domain]}" if options[:domain] - cookie << "; path=#{options[:path]}" if options[:path] - if options[:expire_after] - expiry = Time.now + options[:expire_after] - cookie << "; expires=#{expiry.httpdate}" - end - cookie << "; secure" if options[:secure] - cookie << "; HttpOnly" if options[:httponly] - - headers = response[1] - unless headers[SET_COOKIE].blank? - headers[SET_COOKIE] << "\n#{cookie}" - else - headers[SET_COOKIE] = cookie - end + cookie = {:value => sid} + Rack::Utils.set_cookie_header!(response[1], @key, cookie.merge(options)) end end diff --git a/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb b/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb index 31b63663..2053aabc 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb @@ -52,7 +52,6 @@ module ActionController ENV_SESSION_KEY = "rack.session".freeze ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze - HTTP_SET_COOKIE = "Set-Cookie".freeze # Raised when storing more than 4K of session data. class CookieOverflow < StandardError; end @@ -116,9 +115,7 @@ module ActionController cookie[:expires] = Time.now + options[:expire_after] end - cookie = build_cookie(@key, cookie.merge(options)) - headers[HTTP_SET_COOKIE] = [] if headers[HTTP_SET_COOKIE].blank? - headers[HTTP_SET_COOKIE] << cookie + Rack::Utils.set_cookie_header!(headers, @key, cookie.merge(options)) end [status, headers, body] @@ -130,26 +127,6 @@ module ActionController env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options) end - - # Should be in Rack::Utils soon - def build_cookie(key, value) - case value - when Hash - domain = "; domain=" + value[:domain] if value[:domain] - path = "; path=" + value[:path] if value[:path] - # According to RFC 2109, we need dashes here. - # N.B.: cgi.rb uses spaces... - expires = "; expires=" + value[:expires].clone.gmtime. - strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] - secure = "; secure" if value[:secure] - httponly = "; HttpOnly" if value[:httponly] - value = value[:value] - end - value = [value] unless Array === value - cookie = Rack::Utils.escape(key) + "=" + - value.map { |v| Rack::Utils.escape(v) }.join("&") + - "#{domain}#{path}#{expires}#{secure}#{httponly}" - end def load_session(env) data = unpacked_cookie_data(env) diff --git a/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb b/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb index 402681cd..2bb1b133 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/mem_cache_store.rb @@ -1,6 +1,6 @@ begin require_library_or_gem 'memcache' - + require 'thread' module ActionController module Session class MemCacheStore < AbstractStore diff --git a/vendor/rails/actionpack/lib/action_pack/version.rb b/vendor/rails/actionpack/lib/action_pack/version.rb index 3880c00a..227ef601 100644 --- a/vendor/rails/actionpack/lib/action_pack/version.rb +++ b/vendor/rails/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionpack/lib/action_view/helpers.rb b/vendor/rails/actionpack/lib/action_view/helpers.rb index cea894dd..debd2e75 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers.rb @@ -6,6 +6,7 @@ module ActionView #:nodoc: autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper' autoload :CacheHelper, 'action_view/helpers/cache_helper' autoload :CaptureHelper, 'action_view/helpers/capture_helper' + autoload :CsrfHelper, 'action_view/helpers/csrf_helper' autoload :DateHelper, 'action_view/helpers/date_helper' autoload :DebugHelper, 'action_view/helpers/debug_helper' autoload :FormHelper, 'action_view/helpers/form_helper' @@ -38,6 +39,7 @@ module ActionView #:nodoc: include BenchmarkHelper include CacheHelper include CaptureHelper + include CsrfHelper include DateHelper include DebugHelper include FormHelper diff --git a/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 38292681..1a8af3d3 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -1,6 +1,7 @@ require 'cgi' require 'action_view/helpers/url_helper' require 'action_view/helpers/tag_helper' +require 'thread' module ActionView module Helpers #:nodoc: diff --git a/vendor/rails/actionpack/lib/action_view/helpers/csrf_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/csrf_helper.rb new file mode 100644 index 00000000..e0e6c9a6 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/csrf_helper.rb @@ -0,0 +1,14 @@ +module ActionView + # = Action View CSRF Helper + module Helpers + module CsrfHelper + # Returns a meta tag with the cross-site request forgery protection token + # for forms to use. Place this in your head. + def csrf_meta_tag + if protect_against_forgery? + %(\n).html_safe + end + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb index 38c44a2d..389212df 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb @@ -665,7 +665,7 @@ module ActionView # # The HTML specification says unchecked check boxes are not successful, and # thus web browsers do not send them. Unfortunately this introduces a gotcha: - # if an Invoice model has a +paid+ flag, and in the form that edits a paid + # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # @@ -673,12 +673,15 @@ module ActionView # # wouldn't update the flag. # - # To prevent this the helper generates a hidden field with the same name as - # the checkbox after the very check box. So, the client either sends only the - # hidden field (representing the check box is unchecked), or both fields. - # Since the HTML specification says key/value pairs have to be sent in the - # same order they appear in the form and Rails parameters extraction always - # gets the first occurrence of any given key, that works in ordinary forms. + # To prevent this the helper generates an auxiliary hidden field before + # the very check box. The hidden field has the same name and its + # attributes mimick an unchecked check box. + # + # This way, the client either sends only the hidden field (representing + # the check box is unchecked), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. # # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in @@ -689,22 +692,26 @@ module ActionView # <% end %> # # because parameter name repetition is precisely what Rails seeks to distinguish - # the elements of the array. + # the elements of the array. For each item with a checked check box you + # get an extra ghost item with only that attribute, assigned to "0". + # + # In that case it is preferable to either use +check_box_tag+ or to use + # hashes instead of arrays. # # ==== Examples # # Let's say that @post.validated? is 1: # check_box("post", "validated") - # # => - # # + # # => + # # # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") - # # => - # # + # # => + # # # # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no") - # # => - # # + # # => + # # # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value) diff --git a/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb index 74cb4486..8654adb3 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb @@ -471,7 +471,8 @@ module ActionView email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") if encode == "javascript" - "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| + html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+html_escape(email_address)+extras })) + "document.write('#{escape_javascript(html)}');".each_byte do |c| string << sprintf("%%%x", c) end "" diff --git a/vendor/rails/actionpack/lib/action_view/renderable_partial.rb b/vendor/rails/actionpack/lib/action_view/renderable_partial.rb index 3ea836fa..fb446145 100644 --- a/vendor/rails/actionpack/lib/action_view/renderable_partial.rb +++ b/vendor/rails/actionpack/lib/action_view/renderable_partial.rb @@ -27,7 +27,7 @@ module ActionView def render_partial(view, object = nil, local_assigns = {}, as = nil) object ||= local_assigns[:object] || local_assigns[variable_name] - if object.nil? && view.respond_to?(:controller) + if object.nil? && !local_assigns_key?(local_assigns) && view.respond_to?(:controller) ivar = :"@#{variable_name}" object = if view.controller.instance_variable_defined?(ivar) @@ -43,5 +43,11 @@ module ActionView render_template(view, local_assigns) end + + private + + def local_assigns_key?(local_assigns) + local_assigns.key?(:object) || local_assigns.key?(variable_name) + end end end diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb index a312f7f6..f517fdfe 100644 --- a/vendor/rails/actionpack/test/controller/cookie_test.rb +++ b/vendor/rails/actionpack/test/controller/cookie_test.rb @@ -100,11 +100,26 @@ class CookieTest < ActionController::TestCase end def test_setting_cookie_with_secure + @request.env["HTTPS"] = "on" get :authenticate_with_secure assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"] assert_equal({"user_name" => "david"}, @response.cookies) end + def test_setting_cookie_with_secure_in_development + with_environment(:development) do + get :authenticate_with_secure + assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => "david"}, @response.cookies) + end + end + + def test_not_setting_cookie_with_secure + get :authenticate_with_secure + assert_not_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"] + assert_not_equal({"user_name" => "david"}, @response.cookies) + end + def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size @@ -177,4 +192,17 @@ class CookieTest < ActionController::TestCase assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"].first assert_equal 100, @controller.send(:cookies).signed[:remember_me] end + + private + def with_environment(enviroment) + old_rails = Object.const_get(:Rails) rescue nil + mod = Object.const_set(:Rails, Module.new) + (class << mod; self; end).instance_eval do + define_method(:env) { @_env ||= ActiveSupport::StringInquirer.new(enviroment.to_s) } + end + yield + ensure + Object.module_eval { remove_const(:Rails) } if defined?(Rails) + Object.const_set(:Rails, old_rails) if old_rails + end end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/reloader_test.rb b/vendor/rails/actionpack/test/controller/reloader_test.rb index e5305493..d78f8111 100644 --- a/vendor/rails/actionpack/test/controller/reloader_test.rb +++ b/vendor/rails/actionpack/test/controller/reloader_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'thread' class ReloaderTests < ActiveSupport::TestCase Reloader = ActionController::Reloader diff --git a/vendor/rails/actionpack/test/controller/render_test.rb b/vendor/rails/actionpack/test/controller/render_test.rb index 42f776c7..288260a2 100644 --- a/vendor/rails/actionpack/test/controller/render_test.rb +++ b/vendor/rails/actionpack/test/controller/render_test.rb @@ -716,6 +716,11 @@ class TestController < ActionController::Base render :partial => "customer" end + def partial_with_implicit_local_assignment_and_nil_local + @customer = Customer.new("Marcel") + render :partial => "customer", :locals => { :customer => nil } + end + def render_call_to_partial_with_layout render :action => "calling_partial_with_layout" end @@ -1543,6 +1548,13 @@ class RenderTest < ActionController::TestCase end end + def test_partial_with_implicit_local_assignment_and_nil_local + assert_not_deprecated do + get :partial_with_implicit_local_assignment_and_nil_local + assert_equal "Hello: Anonymous", @response.body + end + end + def test_render_missing_partial_template assert_raise(ActionView::MissingTemplate) do get :missing_partial diff --git a/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb b/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb index c6ad4b92..75029057 100644 --- a/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb +++ b/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb @@ -23,6 +23,10 @@ module RequestForgeryProtectionActions render :text => 'pwn' end + def meta + render :inline => "<%= csrf_meta_tag %>" + end + def rescue_action(e) raise e end end @@ -32,6 +36,16 @@ class RequestForgeryProtectionController < ActionController::Base protect_from_forgery :only => :index end +class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base + include RequestForgeryProtectionActions + protect_from_forgery :only => %w(index meta) + + def handle_unverified_request + raise(ActionController::InvalidAuthenticityToken) + end +end + + class FreeCookieController < RequestForgeryProtectionController self.allow_forgery_protection = false @@ -54,158 +68,92 @@ end # common test methods module RequestForgeryProtectionTests - def teardown - ActionController::Base.request_forgery_protection_token = nil + def setup + @token = "cf50faa3fe97702ca1ae" + + ActiveSupport::SecureRandom.stubs(:base64).returns(@token) + ActionController::Base.request_forgery_protection_token = :authenticity_token end - + def test_should_render_form_with_token_tag - get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token - end - - def test_should_render_button_to_with_token_tag - get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token - end - - def test_should_render_remote_form_with_only_one_token_parameter - get :remote_form - assert_equal 1, @response.body.scan(@token).size - end - - def test_should_allow_get - get :index - assert_response :success - end - - def test_should_allow_post_without_token_on_unsafe_action - post :unsafe - assert_response :success - end - - def test_should_not_allow_html_post_without_token - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } - end - - def test_should_not_allow_html_put_without_token - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } - end - - def test_should_not_allow_html_delete_without_token - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } - end - - def test_should_allow_api_formatted_post_without_token - assert_nothing_raised do - post :index, :format => 'xml' + assert_not_blocked do + get :index end + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token end - def test_should_not_allow_api_formatted_put_without_token - assert_nothing_raised do - put :index, :format => 'xml' + def test_should_render_button_to_with_token_tag + assert_not_blocked do + get :show_button end + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token end - def test_should_allow_api_formatted_delete_without_token - assert_nothing_raised do - delete :index, :format => 'xml' - end + def test_should_allow_get + assert_not_blocked { get :index } end - def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - post :index, :format => 'xml' - end + def test_should_allow_post_without_token_on_unsafe_action + assert_not_blocked { post :unsafe } end - def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - put :index, :format => 'xml' - end + def test_should_not_allow_post_without_token + assert_blocked { post :index } end - def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - delete :index, :format => 'xml' - end + def test_should_not_allow_post_without_token_irrespective_of_format + assert_blocked { post :index, :format=>'xml' } end - def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s - post :index, :format => 'xml' - end + def test_should_not_allow_put_without_token + assert_blocked { put :index } end - def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s - put :index, :format => 'xml' - end + def test_should_not_allow_delete_without_token + assert_blocked { delete :index } end - def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token - assert_raise(ActionController::InvalidAuthenticityToken) do - @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s - delete :index, :format => 'xml' - end + def test_should_not_allow_xhr_post_without_token + assert_blocked { xhr :post, :index } end - - def test_should_allow_xhr_post_without_token - assert_nothing_raised { xhr :post, :index } - end - - def test_should_allow_xhr_put_without_token - assert_nothing_raised { xhr :put, :index } - end - - def test_should_allow_xhr_delete_without_token - assert_nothing_raised { xhr :delete, :index } - end - - def test_should_allow_xhr_post_with_encoded_form_content_type_without_token - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_nothing_raised { xhr :post, :index } - end - + def test_should_allow_post_with_token - post :index, :authenticity_token => @token - assert_response :success + assert_not_blocked { post :index, :authenticity_token => @token } end def test_should_allow_put_with_token - put :index, :authenticity_token => @token - assert_response :success + assert_not_blocked { put :index, :authenticity_token => @token } end def test_should_allow_delete_with_token - delete :index, :authenticity_token => @token + assert_not_blocked { delete :index, :authenticity_token => @token } + end + + def test_should_allow_post_with_token_in_header + @request.env['HTTP_X_CSRF_TOKEN'] = @token + assert_not_blocked { post :index } + end + + def test_should_allow_delete_with_token_in_header + @request.env['HTTP_X_CSRF_TOKEN'] = @token + assert_not_blocked { delete :index } + end + + def test_should_allow_put_with_token_in_header + @request.env['HTTP_X_CSRF_TOKEN'] = @token + assert_not_blocked { put :index } + end + + def assert_blocked + session[:something_like_user_id] = 1 + yield + assert_nil session[:something_like_user_id], "session values are still present" assert_response :success end - def test_should_allow_post_with_xml - @request.env['CONTENT_TYPE'] = Mime::XML.to_s - post :index, :format => 'xml' - assert_response :success - end - - def test_should_allow_put_with_xml - @request.env['CONTENT_TYPE'] = Mime::XML.to_s - put :index, :format => 'xml' - assert_response :success - end - - def test_should_allow_delete_with_xml - @request.env['CONTENT_TYPE'] = Mime::XML.to_s - delete :index, :format => 'xml' + def assert_not_blocked + assert_nothing_raised { yield } assert_response :success end end @@ -214,15 +162,20 @@ end class RequestForgeryProtectionControllerTest < ActionController::TestCase include RequestForgeryProtectionTests - def setup - @controller = RequestForgeryProtectionController.new - @request = ActionController::TestRequest.new - @request.format = :html - @response = ActionController::TestResponse.new - @token = "cf50faa3fe97702ca1ae" - ActiveSupport::SecureRandom.stubs(:base64).returns(@token) - ActionController::Base.request_forgery_protection_token = :authenticity_token + test 'should emit a csrf-token meta tag' do + ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?') + get :meta + assert_equal %(\n), @response.body + end +end + +class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase + include RequestForgeryProtectionTests + def assert_blocked + assert_raises(ActionController::InvalidAuthenticityToken) do + yield + end end end @@ -251,15 +204,30 @@ class FreeCookieControllerTest < ActionController::TestCase assert_nothing_raised { send(method, :index)} end end + + test 'should not emit a csrf-token meta tag' do + get :meta + assert_blank @response.body + end end + + + + class CustomAuthenticityParamControllerTest < ActionController::TestCase def setup + ActionController::Base.request_forgery_protection_token = :custom_token_name + super + end + + def teardown ActionController::Base.request_forgery_protection_token = :authenticity_token + super end def test_should_allow_custom_token - post :index, :authenticity_token => 'foobar' + post :index, :custom_token_name => 'foobar' assert_response :ok end end diff --git a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb index d467af7e..b7b922c3 100644 --- a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb +++ b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb @@ -106,7 +106,7 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do get '/set_session_value' assert_response :success - assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"], + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", headers['Set-Cookie'] end end @@ -159,7 +159,7 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set(:secure => true) do get '/set_session_value', nil, 'HTTPS' => 'on' assert_response :success - assert_equal ["_myapp_session=#{response.body}; path=/; secure; HttpOnly"], + assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly", headers['Set-Cookie'] end end @@ -195,12 +195,12 @@ class CookieStoreTest < ActionController::IntegrationTest get '/set_session_value' assert_response :success session_payload = response.body - assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"], + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", headers['Set-Cookie'] get '/call_reset_session' assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_not_equal "", headers['Set-Cookie'] assert_not_equal session_payload, cookies[SessionKey] get '/get_session_value' @@ -214,7 +214,7 @@ class CookieStoreTest < ActionController::IntegrationTest get '/set_session_value' assert_response :success session_payload = response.body - assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"], + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", headers['Set-Cookie'] get '/call_session_clear' diff --git a/vendor/rails/actionpack/test/template/url_helper_test.rb b/vendor/rails/actionpack/test/template/url_helper_test.rb index 9d541fc1..480624f4 100644 --- a/vendor/rails/actionpack/test/template/url_helper_test.rb +++ b/vendor/rails/actionpack/test/template/url_helper_test.rb @@ -333,11 +333,11 @@ class UrlHelperTest < ActionView::TestCase end def test_mail_to_with_javascript - assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript") + assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript") end def test_mail_to_with_javascript_unicode - assert_dom_equal "", mail_to("unicode@example.com", "Ășnicode", :encode => "javascript") + assert_dom_equal "", mail_to("unicode@example.com", "Ășnicode", :encode => "javascript") end def test_mail_with_options @@ -361,8 +361,8 @@ class UrlHelperTest < ActionView::TestCase assert_dom_equal "me(at)domain.com", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)") assert_dom_equal "My email", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)") assert_dom_equal "me(at)domain(dot)com", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal "", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end def protect_against_forgery? diff --git a/vendor/rails/activerecord/CHANGELOG b/vendor/rails/activerecord/CHANGELOG index 9383f962..a8fb4dbb 100644 --- a/vendor/rails/activerecord/CHANGELOG +++ b/vendor/rails/activerecord/CHANGELOG @@ -1,3 +1,5 @@ +*2.3.11 (February 9, 2011)* + *2.3.10 (October 15, 2010)* * Security Release to fix CVE-2010-3933 diff --git a/vendor/rails/activerecord/Rakefile b/vendor/rails/activerecord/Rakefile index e0684466..613bda69 100644 --- a/vendor/rails/activerecord/Rakefile +++ b/vendor/rails/activerecord/Rakefile @@ -192,7 +192,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.11' + PKG_BUILD) s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite" diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb index fbff07a2..3a602e49 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb @@ -353,15 +353,14 @@ module ActiveRecord if @target.is_a?(Array) && @target.any? @target = find_target.map do |f| i = @target.index(f) - if i - @target.delete_at(i).tap do |t| - keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) - t.attributes = f.attributes.except(*keys) - end + t = @target.delete_at(i) if i + if t && t.changed? + t else + f.mark_for_destruction if t && t.marked_for_destruction? f end - end + @target + end + @target.find_all {|t| t.new_record?} else @target = find_target end @@ -375,16 +374,17 @@ module ActiveRecord target end - def method_missing(method, *args) + def method_missing(method, *args, &block) case method.to_s when 'find_or_create' return find(:first, :conditions => args.first) || create(args.first) when /^find_or_create_by_(.*)$/ rest = $1 - return send("find_by_#{rest}", *args) || - method_missing("create_by_#{rest}", *args) + find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args) + return send("find_by_#{rest}", find_args) || + method_missing("create_by_#{rest}", *args, &block) when /^create_by_(.*)$/ - return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }) + return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }, &block) end if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) @@ -434,20 +434,32 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? @target ||= [] unless loaded? - index = @target.index(record) - unless @reflection.options[:uniq] && index - if index - @target[index] = record - else - @target << record - end - end + @target << record unless @reflection.options[:uniq] && @target.include?(record) callback(:after_add, record) set_inverse_instance(record, @owner) record end private + # Separate the "finder" args from the "create" args given to a + # find_or_create_by_ call. Returns an array with the + # parameter values in the same order as the keys in the + # "names" array. This code was based on code in base.rb's + # method_missing method. + def pull_finder_args_from(names, *args) + attributes = names.collect { |name| name.intern } + attribute_hash = {} + args.each_with_index do |arg, i| + if arg.is_a?(Hash) + attribute_hash.merge! arg + else + attribute_hash[attributes[i]] = arg + end + end + attribute_hash = attribute_hash.with_indifferent_access + attributes.collect { |attr| attribute_hash[attr] } + end + def create_record(attrs) attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash) ensure_owner_is_not_new diff --git a/vendor/rails/activerecord/lib/active_record/calculations.rb b/vendor/rails/activerecord/lib/active_record/calculations.rb index 0df2b6bd..f6249e52 100644 --- a/vendor/rails/activerecord/lib/active_record/calculations.rb +++ b/vendor/rails/activerecord/lib/active_record/calculations.rb @@ -187,7 +187,7 @@ module ActiveRecord # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround - sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] + options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group] if options[:from] sql << " FROM #{options[:from]} " elsif scope && scope[:from] && !use_workaround @@ -211,8 +211,8 @@ module ActiveRecord add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) if options[:group] - group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field - sql << " GROUP BY #{options[group_key]} " + group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields + sql << " GROUP BY #{options[group_key].join(',')} " end if options[:group] && options[:having] @@ -239,24 +239,31 @@ module ActiveRecord end def execute_grouped_calculation(operation, column_name, column, options) #:nodoc: - group_attr = options[:group].to_s - association = reflect_on_association(group_attr.to_sym) - associated = association && association.macro == :belongs_to # only count belongs_to associations - group_field = associated ? association.primary_key_name : group_attr - group_alias = column_alias_for(group_field) - group_column = column_for group_field - sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias)) + group_attr = options[:group] + association = reflect_on_association(group_attr.to_s.to_sym) + associated = association && association.macro == :belongs_to # only count belongs_to associations + group_fields = Array(associated ? association.primary_key_name : group_attr) + group_aliases = [] + group_columns = {} + + group_fields.each do |field| + group_aliases << column_alias_for(field) + group_columns[column_alias_for(field)] = column_for(field) + end + + sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases)) calculated_data = connection.select_all(sql) aggregate_alias = column_alias_for(operation, column_name) if association - key_ids = calculated_data.collect { |row| row[group_alias] } + key_ids = calculated_data.collect { |row| row[group_aliases.first] } key_records = association.klass.base_class.find(key_ids) key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } end calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| - key = type_cast_calculated_value(row[group_alias], group_column) + key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])} + key = key.first if key.size == 1 key = key_records[key] if associated value = row[aggregate_alias] all[key] = type_cast_calculated_value(value, column, operation) diff --git a/vendor/rails/activerecord/lib/active_record/version.rb b/vendor/rails/activerecord/lib/active_record/version.rb index 06680c37..f106e409 100644 --- a/vendor/rails/activerecord/lib/active_record/version.rb +++ b/vendor/rails/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb index c626d457..3996b847 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb @@ -65,6 +65,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal person, person.readers.first.person end + def test_find_or_create_by_with_additional_parameters + post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body' + comment = post.comments.create! :body => 'test comment body', :type => 'test' + + assert_equal comment, post.comments.find_or_create_by_body('test comment body') + + post.comments.find_or_create_by_body(:body => 'other test comment body', :type => 'test') + assert_equal 2, post.comments.count + assert_equal 2, post.comments.length + post.comments.find_or_create_by_body('other other test comment body', :type => 'test') + assert_equal 3, post.comments.count + assert_equal 3, post.comments.length + post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test') + assert_equal 4, post.comments.count + assert_equal 4, post.comments.length + end + + def test_find_or_create_by_with_block + post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body' + comment = post.comments.find_or_create_by_body('other test comment body') { |comment| comment.type = 'test' } + assert_equal 'test', comment.type + end + def test_find_or_create person = Person.create! :first_name => 'tenderlove' post = Post.find :first @@ -843,6 +866,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" end + def test_destroy_all_with_creates_and_scope_that_doesnt_match_created_records + company = companies(:first_firm) + unloaded_client_matching_scope = companies(:second_client) + created_client_matching_scope = company.clients_of_firm.create!(:name => "Somesoft") + created_client_not_matching_scope = company.clients_of_firm.create!(:name => "OtherCo") + destroyed = company.clients_of_firm.with_oft_in_name.destroy_all + assert destroyed.include?(unloaded_client_matching_scope), "unloaded clients matching the scope destroy_all on should have been destroyed" + assert destroyed.include?(created_client_matching_scope), "loaded clients matching the scope destroy_all on should have been destroyed" + assert !destroyed.include?(created_client_not_matching_scope), "loaded clients not matching the scope destroy_all on should not have been destroyed" + end + def test_dependence firm = companies(:first_firm) assert_equal 2, firm.clients.size diff --git a/vendor/rails/activerecord/test/cases/associations_test.rb b/vendor/rails/activerecord/test/cases/associations_test.rb index 47cb0d10..048d042b 100644 --- a/vendor/rails/activerecord/test/cases/associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations_test.rb @@ -18,8 +18,6 @@ require 'models/person' require 'models/reader' require 'models/parrot' require 'models/pirate' -require 'models/ship' -require 'models/ship_part' require 'models/treasure' require 'models/price_estimate' require 'models/club' @@ -31,23 +29,6 @@ class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, :computers, :people, :readers - def test_loading_the_association_target_should_keep_child_records_marked_for_destruction - ship = Ship.create!(:name => "The good ship Dollypop") - part = ship.parts.create!(:name => "Mast") - part.mark_for_destruction - ship.parts.send(:load_target) - assert ship.parts[0].marked_for_destruction? - end - - def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction - ship = Ship.create!(:name => "The good ship Dollypop") - part = ship.parts.create!(:name => "Mast") - part.mark_for_destruction - ShipPart.find(part.id).update_attribute(:name, 'Deck') - ship.parts.send(:load_target) - assert_equal 'Deck', ship.parts[0].name - end - def test_include_with_order_works assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)} assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)} diff --git a/vendor/rails/activerecord/test/cases/calculations_test.rb b/vendor/rails/activerecord/test/cases/calculations_test.rb index 503b70aa..d5bb3589 100644 --- a/vendor/rails/activerecord/test/cases/calculations_test.rb +++ b/vendor/rails/activerecord/test/cases/calculations_test.rb @@ -58,6 +58,19 @@ class CalculationsTest < ActiveRecord::TestCase [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } end + def test_should_group_by_multiple_fields + c = Account.count(:all, :group => ['firm_id', :credit_limit]) + [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } + end + + def test_should_group_by_multiple_fields_having_functions + c = Topic.count(:all, :group => [:author_name, 'COALESCE(type, title)']) + assert_equal 1, c[["Nick", "The Third Topic of the day"]] + assert_equal 1, c[["Mary", "Reply"]] + assert_equal 1, c[["David", "The First Topic"]] + assert_equal 1, c[["Carl", "Reply"]] + end + def test_should_group_by_summed_field c = Account.sum(:credit_limit, :group => :firm_id) assert_equal 50, c[1] diff --git a/vendor/rails/activerecord/test/cases/nested_attributes_test.rb b/vendor/rails/activerecord/test/cases/nested_attributes_test.rb index 3adcc88d..6b144a14 100644 --- a/vendor/rails/activerecord/test/cases/nested_attributes_test.rb +++ b/vendor/rails/activerecord/test/cases/nested_attributes_test.rb @@ -808,13 +808,7 @@ class TestHasManyAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveR @part = @ship.parts.create!(:name => "Mast") @trinket = @part.trinkets.create!(:name => "Necklace") end - - test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do - @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}] - assert_equal 1, @ship.parts.proxy_target.size - assert_equal 'Deck', @ship.parts[0].name - end - + test "when grandchild changed in memory, saving parent should save grandchild" do @trinket.name = "changed" @ship.save diff --git a/vendor/rails/activerecord/test/models/company.rb b/vendor/rails/activerecord/test/models/company.rb index de1a1cc5..ced2faab 100644 --- a/vendor/rails/activerecord/test/models/company.rb +++ b/vendor/rails/activerecord/test/models/company.rb @@ -12,6 +12,8 @@ class Company < AbstractCompany has_many :contracts has_many :developers, :through => :contracts + named_scope :with_oft_in_name, :conditions => "name LIKE '%oft%'" + def arbitrary_method "I am Jack's profound disappointment" end diff --git a/vendor/rails/activeresource/CHANGELOG b/vendor/rails/activeresource/CHANGELOG index f779b6f9..bbd33e6c 100644 --- a/vendor/rails/activeresource/CHANGELOG +++ b/vendor/rails/activeresource/CHANGELOG @@ -1,3 +1,4 @@ +*2.3.11 (February 9, 2011)* *2.3.10 (October 15, 2010)* *2.3.9 (September 4, 2010)* *2.3.8 (May 24, 2010)* diff --git a/vendor/rails/activeresource/Rakefile b/vendor/rails/activeresource/Rakefile index ed214f74..3aa276b1 100644 --- a/vendor/rails/activeresource/Rakefile +++ b/vendor/rails/activeresource/Rakefile @@ -66,7 +66,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.11' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'active_resource' diff --git a/vendor/rails/activeresource/lib/active_resource/version.rb b/vendor/rails/activeresource/lib/active_resource/version.rb index cc4540ac..0e36311c 100644 --- a/vendor/rails/activeresource/lib/active_resource/version.rb +++ b/vendor/rails/activeresource/lib/active_resource/version.rb @@ -2,7 +2,7 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activesupport/CHANGELOG b/vendor/rails/activesupport/CHANGELOG index e6edc33a..600b5703 100644 --- a/vendor/rails/activesupport/CHANGELOG +++ b/vendor/rails/activesupport/CHANGELOG @@ -1,3 +1,5 @@ +*2.3.11 (February 9, 2011)* + *2.3.10 (October 15, 2010)* diff --git a/vendor/rails/activesupport/lib/active_support/basic_object.rb b/vendor/rails/activesupport/lib/active_support/basic_object.rb index 1f77209e..3b5277c2 100644 --- a/vendor/rails/activesupport/lib/active_support/basic_object.rb +++ b/vendor/rails/activesupport/lib/active_support/basic_object.rb @@ -1,13 +1,7 @@ -# A base class with no predefined methods that tries to behave like Builder's -# BlankSlate in Ruby 1.9. In Ruby pre-1.9, this is actually the -# Builder::BlankSlate class. -# -# Ruby 1.9 introduces BasicObject which differs slightly from Builder's -# BlankSlate that has been used so far. ActiveSupport::BasicObject provides a -# barebones base class that emulates Builder::BlankSlate while still relying on -# Ruby 1.9's BasicObject in Ruby 1.9. module ActiveSupport if defined? ::BasicObject + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. class BasicObject < ::BasicObject undef_method :== undef_method :equal? @@ -18,7 +12,10 @@ module ActiveSupport end end else - require 'blankslate' - BasicObject = BlankSlate + class BasicObject #:nodoc: + instance_methods.each do |m| + undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/ + end + end end end diff --git a/vendor/rails/activesupport/lib/active_support/buffered_logger.rb b/vendor/rails/activesupport/lib/active_support/buffered_logger.rb index 6c35b190..63c61543 100644 --- a/vendor/rails/activesupport/lib/active_support/buffered_logger.rb +++ b/vendor/rails/activesupport/lib/active_support/buffered_logger.rb @@ -1,3 +1,5 @@ +require 'thread' + module ActiveSupport # Inspired by the buffered logger idea by Ezra class BufferedLogger diff --git a/vendor/rails/activesupport/lib/active_support/locale/en.yml b/vendor/rails/activesupport/lib/active_support/locale/en.yml index e604c9ee..49ad192b 100644 --- a/vendor/rails/activesupport/lib/active_support/locale/en.yml +++ b/vendor/rails/activesupport/lib/active_support/locale/en.yml @@ -15,7 +15,10 @@ en: month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] # Used in date_select and datime_select. - order: [ :year, :month, :day ] + order: + - :year + - :month + - :day time: formats: diff --git a/vendor/rails/activesupport/lib/active_support/version.rb b/vendor/rails/activesupport/lib/active_support/version.rb index 835a507d..922d12ff 100644 --- a/vendor/rails/activesupport/lib/active_support/version.rb +++ b/vendor/rails/activesupport/lib/active_support/version.rb @@ -2,7 +2,7 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activesupport/test/core_ext/module/synchronization_test.rb b/vendor/rails/activesupport/test/core_ext/module/synchronization_test.rb index c28bc9b0..73430223 100644 --- a/vendor/rails/activesupport/test/core_ext/module/synchronization_test.rb +++ b/vendor/rails/activesupport/test/core_ext/module/synchronization_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'thread' class SynchronizationTest < Test::Unit::TestCase def setup diff --git a/vendor/rails/railties/CHANGELOG b/vendor/rails/railties/CHANGELOG index bdbec15b..2e292142 100644 --- a/vendor/rails/railties/CHANGELOG +++ b/vendor/rails/railties/CHANGELOG @@ -1,3 +1,5 @@ +*2.3.11 (February 9, 2011)* + *2.3.10 (October 15, 2010)* *2.3.9 (September 4, 2010)* diff --git a/vendor/rails/railties/Rakefile b/vendor/rails/railties/Rakefile index 20865bd4..ebf7c68e 100644 --- a/vendor/rails/railties/Rakefile +++ b/vendor/rails/railties/Rakefile @@ -313,11 +313,11 @@ spec = Gem::Specification.new do |s| EOF s.add_dependency('rake', '>= 0.8.3') - s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD) - s.add_dependency('activerecord', '= 2.3.10' + PKG_BUILD) - s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD) - s.add_dependency('actionmailer', '= 2.3.10' + PKG_BUILD) - s.add_dependency('activeresource', '= 2.3.10' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.11' + PKG_BUILD) + s.add_dependency('activerecord', '= 2.3.11' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.11' + PKG_BUILD) + s.add_dependency('actionmailer', '= 2.3.11' + PKG_BUILD) + s.add_dependency('activeresource', '= 2.3.11' + PKG_BUILD) s.rdoc_options << '--exclude' << '.' s.has_rdoc = false diff --git a/vendor/rails/railties/guides/source/action_mailer_basics.textile b/vendor/rails/railties/guides/source/action_mailer_basics.textile index 9476635a..ed42f518 100644 --- a/vendor/rails/railties/guides/source/action_mailer_basics.textile +++ b/vendor/rails/railties/guides/source/action_mailer_basics.textile @@ -218,7 +218,9 @@ If you set a default +:host+ for your mailers you need to pass +:only_path => fa h4. Sending Multipart Emails -Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. +Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a "multipart/alternative" email with the HTML and text versions setup as different parts. + +A "multipart/alternative" content type tells your email client that there are several representations of the same content available and that the email client is free to choose any one to display to the user. In this case we are giving a plain text and HTML version of the same message. But this could also be a text version, and a recording of someone speaking the the same message. It is important to use "multipart/alternative" only when each part has the same content. To explicitly specify multipart messages, you can do something like: @@ -242,7 +244,11 @@ end h4. Sending Emails with Attachments -Attachments can be added by using the +attachment+ method: +Attachments can be added by using the +attachment+ method. The +attachment+ method has two variations, you can either pass the body in as an option, or create it within a block. + +Usually you will use the variation shown below for the "image/jpeg" attachment, here you just pass in the content type and body as a options hash to the attachment method. However, if you need to do some processing to create the attachment, such as with the PDF below, then the block variation can be used. + +This email uses the "multipart/mixed" content type because each part is a different block of content. This indicates to the email client that it must show all the parts that it can display to the end user. class UserMailer < ActionMailer::Base @@ -250,13 +256,14 @@ class UserMailer < ActionMailer::Base recipients user.email_address subject "New account information" from "system@example.com" - content_type "multipart/alternative" + content_type "multipart/mixed" attachment :content_type => "image/jpeg", :body => File.read("an-image.jpg") attachment "application/pdf" do |a| - a.body = generate_your_pdf_here() + pdf = generate_your_pdf_here(:name => user) + a.body = pdf end end end @@ -266,7 +273,11 @@ h4. Sending Multipart Emails with Attachments Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename. You must declare which template you are using for each content type via the +part+ method. -In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder. +Here we are making the email "multipart/mixed" with three top level parts, a "multipart/alternative", an "image/jpeg" and an "application/pdf". Within the "multipart/alternative" we are nesting a "text/html" and "text/plain" version of the same message. + +This tells the email client that each of the top level parts should be shown to the end user, however, the first part has the content type "multipart/alternative" and provides two versions of the same message, a plain text and HTML version. + +In the following example, there would be two template files, +welcome_email.text.html.erb+ and +welcome_email.text.plain.erb+ in the +app/views/user_mailer+ folder. class UserMailer < ActionMailer::Base @@ -274,22 +285,28 @@ class UserMailer < ActionMailer::Base recipients user.email_address subject "New account information" from "system@example.com" - content_type "multipart/alternative" + content_type "multipart/mixed" - part "text/html" do |p| - p.body = render_message("welcome_email_html", :message => "

HTML content

") - end + part "multipart/alternative" do |alternative| + + alternative.part "text/html" do |html| + html.body = render_message("welcome_email.text.html", :message => "

HTML content

") + end + + alternative.part "text/plain" do |plain| + plain.body = render_message("welcome_email.text.plain", :message => "text content") + end - part "text/plain" do |p| - p.body = render_message("welcome_email_plain", :message => "text content") end attachment :content_type => "image/jpeg", :body => File.read("an-image.jpg") attachment "application/pdf" do |a| - a.body = generate_your_pdf_here() + pdf = generate_your_pdf_here(:name => user) + a.body = pdf end + end end
diff --git a/vendor/rails/railties/lib/rails/version.rb b/vendor/rails/railties/lib/rails/version.rb index 28ff027a..2f557201 100644 --- a/vendor/rails/railties/lib/rails/version.rb +++ b/vendor/rails/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 10 + TINY = 11 STRING = [MAJOR, MINOR, TINY].join('.') end