diff options
31 files changed, 356 insertions, 206 deletions
@@ -41,8 +41,7 @@ platforms :mri_18 do end platforms :mri_19 do - # TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3 - gem "ruby-debug19", :require => "ruby-debug" unless RUBY_VERSION > "1.9.2" || ENV['TRAVIS'] + gem "ruby-debug19", :require => "ruby-debug" unless ENV['TRAVIS'] end platforms :mri do @@ -97,24 +97,24 @@ RDoc::Task.new do |rdoc| rdoc.options << '-g' # SDoc flag, link methods to GitHub rdoc.options << '-m' << RDOC_MAIN - rdoc.rdoc_files.include('railties/CHANGELOG') + rdoc.rdoc_files.include('railties/CHANGELOG.md') rdoc.rdoc_files.include('railties/MIT-LICENSE') rdoc.rdoc_files.include('railties/README.rdoc') rdoc.rdoc_files.include('railties/lib/**/*.rb') rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb') rdoc.rdoc_files.include('activerecord/README.rdoc') - rdoc.rdoc_files.include('activerecord/CHANGELOG') + rdoc.rdoc_files.include('activerecord/CHANGELOG.md') rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb') rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*') rdoc.rdoc_files.include('activeresource/README.rdoc') - rdoc.rdoc_files.include('activeresource/CHANGELOG') + rdoc.rdoc_files.include('activeresource/CHANGELOG.md') rdoc.rdoc_files.include('activeresource/lib/active_resource.rb') rdoc.rdoc_files.include('activeresource/lib/active_resource/*') rdoc.rdoc_files.include('actionpack/README.rdoc') - rdoc.rdoc_files.include('actionpack/CHANGELOG') + rdoc.rdoc_files.include('actionpack/CHANGELOG.md') rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb') rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb') @@ -122,18 +122,18 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*') rdoc.rdoc_files.include('actionmailer/README.rdoc') - rdoc.rdoc_files.include('actionmailer/CHANGELOG') + rdoc.rdoc_files.include('actionmailer/CHANGELOG.md') rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb') rdoc.rdoc_files.include('actionmailer/lib/action_mailer/mail_helper.rb') rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*') rdoc.rdoc_files.include('activesupport/README.rdoc') - rdoc.rdoc_files.include('activesupport/CHANGELOG') + rdoc.rdoc_files.include('activesupport/CHANGELOG.md') rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb') rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*') rdoc.rdoc_files.include('activemodel/README.rdoc') - rdoc.rdoc_files.include('activemodel/CHANGELOG') + rdoc.rdoc_files.include('activemodel/CHANGELOG.md') rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb') end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 0133b2ecbc..125dbf6bb5 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -182,7 +182,13 @@ module ActionController end def response_body=(val) - body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val]) + body = if val.is_a?(String) + [val] + elsif val.nil? || val.respond_to?(:each) + val + else + [val] + end super body end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index b932302a60..9500a349cb 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -84,8 +84,8 @@ module ActionController #:nodoc: # # === Custom options # - # <code>respond_with</code> also allow you to pass options that are forwarded - # to the underlying render call. Those options are only applied success + # <code>respond_with</code> also allows you to pass options that are forwarded + # to the underlying render call. Those options are only applied for success # scenarios. For instance, you can do the following in the create method above: # # def create @@ -95,7 +95,7 @@ module ActionController #:nodoc: # respond_with(@project, @task, :status => 201) # end # - # This will return status 201 if the task was saved with success. If not, + # This will return status 201 if the task was saved successfully. If not, # it will simply ignore the given options and return status 422 and the # resource errors. To customize the failure scenario, you can pass a # a block to <code>respond_with</code>: @@ -222,7 +222,7 @@ module ActionController #:nodoc: alias :navigation_location :resource_location alias :api_location :resource_location - # If a given response block was given, use it, otherwise call render on + # If a response block was given, use it, otherwise call render on # controller. # def default_render diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 505d5560b1..040b51e040 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,5 +1,3 @@ -require 'active_support/memoizable' - module ActionDispatch module Http class Headers < ::Hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 7a5237dcf3..69ca050d0c 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -155,24 +155,7 @@ module ActionDispatch @ip ||= super end - # Which IP addresses are "trusted proxies" that can be stripped from - # the right-hand-side of X-Forwarded-For. - # - # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces. - TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^(10 | # private IP 10.x.x.x - 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 # private IP 192.168.x.x - )\. - }x - - # Determines originating IP address. REMOTE_ADDR is the standard - # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or - # HTTP_X_FORWARDED_FOR are set by proxies so check for these if - # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma- - # delimited list in the case of multiple chained proxies; the last - # address which is not trusted is the originating IP. + # Originating IP address, usually set by the RemoteIp middleware. def remote_ip @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index c7d710b98e..3b813b03bb 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -2,50 +2,69 @@ module ActionDispatch class RemoteIp class IpSpoofAttackError < StandardError ; end - class RemoteIpGetter - def initialize(env, check_ip_spoofing, trusted_proxies) - @env = env - @check_ip_spoofing = check_ip_spoofing - @trusted_proxies = trusted_proxies + # IP addresses that are "trusted proxies" that can be stripped from + # the comma-delimited list in the X-Forwarded-For header. See also: + # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces + TRUSTED_PROXIES = %r{ + ^127\.0\.0\.1$ | # localhost + ^(10 | # private IP 10.x.x.x + 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 + 192\.168 # private IP 192.168.x.x + )\. + }x + + attr_reader :check_ip_spoofing, :trusted_proxies + + def initialize(app, check_ip_spoofing = true, custom_proxies = nil) + @app = app + @check_ip_spoofing = check_ip_spoofing + if custom_proxies + custom_regexp = Regexp.new(custom_proxies, "i") + @trusted_proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp) + else + @trusted_proxies = TRUSTED_PROXIES end + end - def remote_addrs - @remote_addrs ||= begin - list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : [] - list.reject { |addr| addr =~ @trusted_proxies } - end + def call(env) + env["action_dispatch.remote_ip"] = GetIp.new(env, self) + @app.call(env) + end + + class GetIp + def initialize(env, middleware) + @env, @middleware = env, middleware end + # Determines originating IP address. REMOTE_ADDR is the standard + # but will be wrong if the user is behind a proxy. Proxies will set + # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. + # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of + # multiple chained proxies. The last address which is not a known proxy + # will be the originating IP. def to_s - return remote_addrs.first if remote_addrs.any? - - forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : [] - - if client_ip = @env['HTTP_CLIENT_IP'] - if @check_ip_spoofing && !forwarded_ips.include?(client_ip) - # We don't know which came from the proxy, and which from the user - raise IpSpoofAttackError, "IP spoofing attack?!" \ - "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ - "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" - end - return client_ip + client_ip = @env['HTTP_CLIENT_IP'] + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR') + remote_addrs = ips_from('REMOTE_ADDR') + + check_ip = client_ip && @middleware.check_ip_spoofing + if check_ip && !forwarded_ips.include?(client_ip) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?!" \ + "HTTP_CLIENT_IP=#{env['HTTP_CLIENT_IP'].inspect}" \ + "HTTP_X_FORWARDED_FOR=#{env['HTTP_X_FORWARDED_FOR'].inspect}" end - return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"] + client_ip || forwarded_ips.last || remote_addrs.last end - end - def initialize(app, check_ip_spoofing = true, trusted_proxies = nil) - @app = app - @check_ip_spoofing = check_ip_spoofing - regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)' - regex << "|(#{trusted_proxies})" if trusted_proxies - @trusted_proxies = Regexp.new(regex, "i") - end + protected - def call(env) - env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) - @app.call(env) + def ips_from(header) + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + ips.reject{|ip| ip =~ @middleware.trusted_proxies } + end end + end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 21074efe86..bc8572fe69 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -120,7 +120,7 @@ module ActionView text else match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter]) + text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter]) end.html_safe end diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 3ca29f1bcf..c424dcbd8d 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -23,6 +23,12 @@ module BareMetalTest assert_equal "Hello world", string end + + test "response_body value is wrapped in an array when the value is a String" do + controller = BareController.new + controller.index + assert_equal ["Hello world"], controller.response_body + end end class HeadController < ActionController::Metal diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 02f9609483..a0afb77f05 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -194,6 +194,10 @@ class TextHelperTest < ActionView::TestCase "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>", highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful") ) + assert_equal( + "<div>abc <b>div</b></div>", + highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>') + ) end def test_excerpt diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 77a5fe1efb..92dfb844db 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -123,14 +123,14 @@ module ActiveRecord # A cached lookup for table existence. def table_exists?(name) - return true if @tables.key? name + return @tables[name] if @tables.key? name with_connection do |conn| conn.tables.each { |table| @tables[table] = true } - @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name) + @tables[name] = !@tables.key?(name) && conn.table_exists?(name) end - @tables.key? name + @tables[name] end # Clears out internal caches: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0e5e33fa02..11da84e245 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -158,7 +158,13 @@ module ActiveRecord td = table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false - td.instance_eval(&blk) if blk + if block_given? + if blk.arity == 1 + yield td + else + td.instance_eval(&blk) + end + end if options[:force] && table_exists?(table_name) drop_table(table_name) @@ -235,14 +241,19 @@ module ActiveRecord # # See also Table for details on # all of the various column transformation - def change_table(table_name, options = {}) - if supports_bulk_alter? && options[:bulk] - recorder = ActiveRecord::Migration::CommandRecorder.new(self) - yield Table.new(table_name, recorder) - bulk_change_table(table_name, recorder.commands) - else - yield Table.new(table_name, self) + def change_table(table_name, options = {}, &blk) + bulk_change = supports_bulk_alter? && options[:bulk] + recorder = bulk_change ? ActiveRecord::Migration::CommandRecorder.new(self) : self + table = Table.new(table_name, recorder) + + if block_given? + if blk.arity == 1 + yield table + else + table.instance_eval(&blk) + end end + bulk_change_table(table_name, recorder.commands) if bulk_change end # Renames a table. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7eeb3dde70..3c8e0f2052 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -191,7 +191,7 @@ module ActiveRecord join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) - relation = relation.except(:select).select("1").limit(1) + relation = relation.except(:select, :order).select("1").limit(1) case id when Array, Hash diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 3088ab012f..69754d23b9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -57,6 +57,11 @@ class FinderTest < ActiveRecord::TestCase assert Topic.first.replies.exists? end + # ensures +exists?+ runs valid SQL by excluding order value + def test_exists_with_order + assert Topic.order(:id).uniq.exists? + end + def test_does_not_exist_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index e8ad37d437..d6c7edc461 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1742,6 +1742,75 @@ if ActiveRecord::Base.connection.supports_migrations? ensure Person.connection.drop_table :testings rescue nil end + + def test_create_table_should_not_have_mixed_syntax + assert_raise(NoMethodError) do + Person.connection.create_table :testings, :force => true do |t| + t.string :foo + integer :bar + end + end + assert_raise(NameError) do + Person.connection.create_table :testings, :force => true do + t.string :foo + integer :bar + end + end + end + + def test_change_table_without_block_parameter_no_bulk + Person.connection.create_table :testings, :force => true do + string :foo + end + assert Person.connection.column_exists?(:testings, :foo, :string) + + Person.connection.change_table :testings do + remove :foo + integer :bar + end + + assert_equal %w(bar id), Person.connection.columns(:testings).map { |c| c.name }.sort + ensure + Person.connection.drop_table :testings rescue nil + end + + if ActiveRecord::Base.connection.supports_bulk_alter? + def test_change_table_without_block_parameter_with_bulk + Person.connection.create_table :testings, :force => true do + string :foo + end + assert Person.connection.column_exists?(:testings, :foo, :string) + + assert_queries(1) do + Person.connection.change_table(:testings, :bulk => true) do + integer :bar + string :foo_bar + end + end + + assert_equal %w(bar foo foo_bar id), Person.connection.columns(:testings).map { |c| c.name }.sort + ensure + Person.connection.drop_table :testings rescue nil + end + end + + def test_change_table_should_not_have_mixed_syntax + Person.connection.create_table :testings, :force => true do + string :foo + end + assert_raise(NoMethodError) do + Person.connection.change_table :testings do |t| + t.remove :foo + integer :bar + end + end + assert_raise(NameError) do + Person.connection.change_table :testings do + t.remove :foo + integer :bar + end + end + end end # SexierMigrationsTest class MigrationLoggerTest < ActiveRecord::TestCase diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index e03bfdee92..18a115b369 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 3.2.0 (unreleased) ## +* Deprecated ActiveSupport::MessageEncryptor#encrypt and decrypt. *José Valim* + * ActiveSupport::Notifications.subscribed provides subscriptions to events while a block runs. *fxn* * Module#qualified_const_(defined?|get|set) are analogous to the corresponding methods diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 656cba625c..ea37355fc1 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -153,7 +153,7 @@ module ActiveSupport @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def _one_time_conditions_valid_#{@callback_id}? - true #{key_options[0]} + true if #{key_options} end RUBY_EVAL end @@ -171,8 +171,8 @@ module ActiveSupport # if condition # before_save :filter_name, :if => :condition # filter_name # end - filter = <<-RUBY_EVAL - unless halted + <<-RUBY_EVAL + if !halted && #{@compiled_options} # This double assignment is to prevent warnings in 1.9.3. I would # remove the `result` variable, but apparently some other # generated code is depending on this variable being set sometimes @@ -181,8 +181,6 @@ module ActiveSupport halted = (#{chain.config[:terminator]}) end RUBY_EVAL - - [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") when :around # Compile around filters with conditions into proxy methods # that contain the conditions. @@ -202,7 +200,7 @@ module ActiveSupport name = "_conditional_callback_#{@kind}_#{next_id}" @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}(halted) - #{@compiled_options[0] || "if true"} && !halted + if #{@compiled_options} && !halted #{@filter} do yield self end @@ -222,10 +220,12 @@ module ActiveSupport case @kind when :after - # if condition # after_save :filter_name, :if => :condition - # filter_name - # end - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + # after_save :filter_name, :if => :condition + <<-RUBY_EVAL + if #{@compiled_options} + #{@filter} + end + RUBY_EVAL when :around <<-RUBY_EVAL value @@ -240,9 +240,7 @@ module ActiveSupport # symbols, string, procs, and objects), so compile a conditional # expression based on the options def _compile_options(options) - return [] if options[:if].empty? && options[:unless].empty? - - conditions = [] + conditions = ["true"] unless options[:if].empty? conditions << Array.wrap(_compile_filter(options[:if])) @@ -252,7 +250,7 @@ module ActiveSupport conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} end - ["if #{conditions.flatten.join(" && ")}", "end"] + conditions.flatten.join(" && ") end # Filters support: @@ -371,42 +369,37 @@ module ActiveSupport # Generate the internal runner method called by +run_callbacks+. def __define_runner(symbol) #:nodoc: body = send("_#{symbol}_callbacks").compile + runner_method = "_run_#{symbol}_callbacks" silence_warnings do - undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") + undef_method runner_method if method_defined?(runner_method) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _run_#{symbol}_callbacks(key = nil, &blk) + def #{runner_method}(key = nil, &blk) if key - name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" - - unless respond_to?(name) - self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) - end - - send(name, &blk) + self.class.__run_keyed_callback(key, :#{symbol}, self, &blk) else #{body} end end - private :_run_#{symbol}_callbacks + private :#{runner_method} RUBY_EVAL end end - # This is called the first time a callback is called with a particular - # key. It creates a new callback method for the key, calculating - # which callbacks can be omitted because of per_key conditions. + # This method calls the callback method for the given key. + # If this called first time it creates a new callback method for the key, + # calculating which callbacks can be omitted because of per_key conditions. # - def __create_keyed_callback(name, kind, object, &blk) #:nodoc: - @_keyed_callbacks ||= {} - @_keyed_callbacks[name] ||= begin + def __run_keyed_callback(key, kind, object, &blk) #:nodoc: + name = "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks" + unless object.respond_to?(name) str = send("_#{kind}_callbacks").compile(name, object) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} RUBY_EVAL - true end + object.send(name, &blk) end # This is used internally to append, prepend and skip callbacks to the diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index e14386a85d..9ef2b29580 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -10,6 +10,16 @@ module ActiveSupport # This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't # want users to be able to determine the value of the payload. class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError @@ -18,13 +28,40 @@ module ActiveSupport ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm." options = { :cipher => options } end - + @secret = secret @cipher = options[:cipher] || 'aes-256-cbc' + @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer) @serializer = options[:serializer] || Marshal end def encrypt(value) + ActiveSupport::Deprecation.warn "MessageEncryptor#encrypt is deprecated as it is not safe without a signature. " \ + "Please use MessageEncryptor#encrypt_and_sign instead." + _encrypt(value) + end + + def decrypt(value) + ActiveSupport::Deprecation.warn "MessageEncryptor#decrypt is deprecated as it is not safe without a signature. " \ + "Please use MessageEncryptor#decrypt_and_verify instead." + _decrypt(value) + end + + # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def encrypt_and_sign(value) + verifier.generate(_encrypt(value)) + end + + # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def decrypt_and_verify(value) + _decrypt(verifier.verify(value)) + end + + private + + def _encrypt(value) cipher = new_cipher # Rely on OpenSSL for the initialization vector iv = cipher.random_iv @@ -39,7 +76,7 @@ module ActiveSupport [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") end - def decrypt(encrypted_message) + def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)} @@ -55,23 +92,12 @@ module ActiveSupport raise InvalidMessage end - def encrypt_and_sign(value) - verifier.generate(encrypt(value)) + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) end - def decrypt_and_verify(value) - decrypt(verifier.verify(value)) + def verifier + @verifier end - - - - private - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end - - def verifier - MessageVerifier.new(@secret) - end end end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 83a19f8106..3e6a5c6602 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -11,46 +11,50 @@ require 'active_support/time' require 'active_support/json' class MessageEncryptorTest < ActiveSupport::TestCase - class JSONSerializer def dump(value) ActiveSupport::JSON.encode(value) end - + def load(value) ActiveSupport::JSON.decode(value) end end - + def setup - @encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64)) + @secret = SecureRandom.hex(64) + @verifier = ActiveSupport::MessageVerifier.new(@secret, :serializer => ActiveSupport::MessageEncryptor::NullSerializer) + @encryptor = ActiveSupport::MessageEncryptor.new(@secret) @data = { :some => "data", :now => Time.local(2010) } end - def test_simple_round_tripping - message = @encryptor.encrypt(@data) - assert_equal @data, @encryptor.decrypt(message) - end - def test_encrypting_twice_yields_differing_cipher_text - first_messqage = @encryptor.encrypt(@data) - second_message = @encryptor.encrypt(@data) + first_messqage = @encryptor.encrypt_and_sign(@data).split("--").first + second_message = @encryptor.encrypt_and_sign(@data).split("--").first assert_not_equal first_messqage, second_message end - def test_messing_with_either_value_causes_failure - text, iv = @encryptor.encrypt(@data).split("--") + def test_messing_with_either_encrypted_values_causes_failure + text, iv = @verifier.verify(@encryptor.encrypt_and_sign(@data)).split("--") assert_not_decrypted([iv, text] * "--") assert_not_decrypted([text, munge(iv)] * "--") assert_not_decrypted([munge(text), iv] * "--") assert_not_decrypted([munge(text), munge(iv)] * "--") end + def test_messing_with_verified_values_causes_failures + text, iv = @encryptor.encrypt_and_sign(@data).split("--") + assert_not_verified([iv, text] * "--") + assert_not_verified([text, munge(iv)] * "--") + assert_not_verified([munge(text), iv] * "--") + assert_not_verified([munge(text), munge(iv)] * "--") + end + def test_signed_round_tripping message = @encryptor.encrypt_and_sign(@data) assert_equal @data, @encryptor.decrypt_and_verify(message) end - + def test_alternative_serialization_method encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new) message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) @@ -62,19 +66,26 @@ class MessageEncryptorTest < ActiveSupport::TestCase ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), 'aes-256-cbc') end end - + private - def assert_not_decrypted(value) - assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do - @encryptor.decrypt(value) - end + + def assert_not_decrypted(value) + assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do + @encryptor.decrypt_and_verify(@verifier.generate(value)) end + end - def munge(base64_string) - bits = ActiveSupport::Base64.decode64(base64_string) - bits.reverse! - ActiveSupport::Base64.encode64s(bits) + def assert_not_verified(value) + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @encryptor.decrypt_and_verify(value) end -end + end + def munge(base64_string) + bits = ActiveSupport::Base64.decode64(base64_string) + bits.reverse! + ActiveSupport::Base64.encode64s(bits) + end end + +end
\ No newline at end of file diff --git a/railties/guides/code/getting_started/app/controllers/posts_controller.rb b/railties/guides/code/getting_started/app/controllers/posts_controller.rb index 7e903c984c..1581d4eb16 100644 --- a/railties/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/railties/guides/code/getting_started/app/controllers/posts_controller.rb @@ -62,7 +62,7 @@ class PostsController < ApplicationController respond_to do |format| if @post.update_attributes(params[:post]) format.html { redirect_to @post, notice: 'Post was successfully updated.' } - format.json { head :ok } + format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @post.errors, status: :unprocessable_entity } @@ -78,7 +78,7 @@ class PostsController < ApplicationController respond_to do |format| format.html { redirect_to posts_url } - format.json { head :ok } + format.json { head :no_content } end end end diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 4682ead66e..6f6d3bda80 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -137,7 +137,8 @@ module RailsGuides if guide =~ /\.html\.erb$/ # Generate the special pages like the home. - result = view.render(:layout => 'layout', :file => guide) + # Passing a template handler in the template name is deprecated. So pass the file name without the extension. + result = view.render(:layout => 'layout', :file => $`) else body = File.read(File.join(source_dir, guide)) body = set_header_section(body, view) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 5f370615ca..8a0a70efad 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -1071,7 +1071,7 @@ def update if @post.update_attributes(params[:post]) format.html { redirect_to(@post, :notice => 'Post was successfully updated.') } - format.json { render :json => {}, :status => :ok } + format.json { head :no_content } else format.html { render :action => "edit" } format.json { render :json => @post.errors, @@ -1100,7 +1100,7 @@ def destroy respond_to do |format| format.html { redirect_to posts_url } - format.json { head :ok } + format.json { head :no_content } end end </ruby> diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 32e361d421..7733a8f116 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -42,6 +42,8 @@ module Rails else puts "Loading #{Rails.env} environment (Rails #{Rails.version})" end + + IRB::ExtendCommandBundle.send :include, Rails::ConsoleMethods IRB.start end end diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb index 95c74baae2..23d57379ba 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -5,28 +5,32 @@ require 'action_controller' # work around the at_exit hook in test/unit, which kills IRB Test::Unit.run = true if Test::Unit.respond_to?(:run=) -# reference the global "app" instance, created on demand. To recreate the -# instance, pass a non-false value as the parameter. -def app(create=false) - @app_integration_instance = nil if create - @app_integration_instance ||= new_session do |sess| - sess.host! "www.example.com" - end -end +module Rails + module ConsoleMethods + # reference the global "app" instance, created on demand. To recreate the + # instance, pass a non-false value as the parameter. + def app(create=false) + @app_integration_instance = nil if create + @app_integration_instance ||= new_session do |sess| + sess.host! "www.example.com" + end + end -# create a new session. If a block is given, the new session will be yielded -# to the block before being returned. -def new_session - app = Rails.application - session = ActionDispatch::Integration::Session.new(app) - yield session if block_given? - session -end + # create a new session. If a block is given, the new session will be yielded + # to the block before being returned. + def new_session + app = Rails.application + session = ActionDispatch::Integration::Session.new(app) + yield session if block_given? + session + end -# reloads the environment -def reload!(print=true) - puts "Reloading..." if print - ActionDispatch::Reloader.cleanup! - ActionDispatch::Reloader.prepare! - true + # reloads the environment + def reload!(print=true) + puts "Reloading..." if print + ActionDispatch::Reloader.cleanup! + ActionDispatch::Reloader.prepare! + true + end + end end diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb index 212fc6125a..230d3d9d04 100644 --- a/railties/lib/rails/console/helpers.rb +++ b/railties/lib/rails/console/helpers.rb @@ -1,7 +1,11 @@ -def helper - @helper ||= ApplicationController.helpers -end +module Rails + module ConsoleMethods + def helper + @helper ||= ApplicationController.helpers + end -def controller - @controller ||= ApplicationController.new + def controller + @controller ||= ApplicationController.new + end + end end diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 4baa2110e7..f5c8ccf940 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -39,7 +39,7 @@ module Rails end def gitignore - copy_file "gitignore", ".gitignore" + template "gitignore", ".gitignore" end def lib diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore index 1463de6dfb..92bd3c614b 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore @@ -1,6 +1,6 @@ .bundle/ log/*.log pkg/ -test/dummy/db/*.sqlite3 -test/dummy/log/*.log -test/dummy/tmp/
\ No newline at end of file +<%= dummy_path %>/db/*.sqlite3 +<%= dummy_path %>/log/*.log +<%= dummy_path %>/tmp/
\ No newline at end of file diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 2072a2b947..1e7da5ccae 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -96,13 +96,13 @@ namespace :doc do end gem_path('activesupport') do |activesupport| - %w(README.rdoc CHANGELOG lib/active_support/**/*.rb).each do |file| + %w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file| rdoc.rdoc_files.include("#{activesupport}/#{file}") end end gem_path('railties') do |railties| - %w(README.rdoc CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| + %w(README.rdoc CHANGELOG.md lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| rdoc.rdoc_files.include("#{railties}/#{file}") end end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 8bcceb9b2c..52d92cdd96 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -121,16 +121,9 @@ namespace :test do models = changed_since_checkin.select { |path| path =~ /app[\\\/]models[\\\/].*\.rb$/ } controllers = changed_since_checkin.select { |path| path =~ /app[\\\/]controllers[\\\/].*\.rb$/ } - unit_tests = models.map do |model| - file = "test/unit/#{File.basename(model, '.rb')}_test.rb" - file if File.exist?(file) - end - functional_tests = controllers.map do |controller| - file = "test/functional/#{File.basename(controller, '.rb')}_test.rb" - file if File.exist?(file) - end - - (unit_tests.uniq + functional_tests.uniq).compact + unit_tests = models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" } + functional_tests = controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } + (unit_tests + functional_tests).uniq.select { |file| File.exist?(file) } end t.libs << 'test' diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 1528d5dd87..b3745f194e 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -18,16 +18,20 @@ class ConsoleTest < Test::Unit::TestCase Rails.application.load_console end + def irb_context + Object.new.extend(Rails::ConsoleMethods) + end + def test_app_method_should_return_integration_session TestHelpers::Rack.send :remove_method, :app load_environment - console_session = app + console_session = irb_context.app assert_instance_of ActionDispatch::Integration::Session, console_session end def test_new_session_should_return_integration_session load_environment - session = new_session + session = irb_context.new_session assert_instance_of ActionDispatch::Integration::Session, session end @@ -41,7 +45,7 @@ class ConsoleTest < Test::Unit::TestCase ActionDispatch::Reloader.to_prepare { c = 3 } # Hide Reloading... output - silence_stream(STDOUT) { reload! } + silence_stream(STDOUT) { irb_context.reload! } assert_equal 1, a assert_equal 2, b @@ -66,12 +70,14 @@ class ConsoleTest < Test::Unit::TestCase MODEL assert !User.new.respond_to?(:age) - silence_stream(STDOUT) { reload! } + silence_stream(STDOUT) { irb_context.reload! } + session = irb_context.new_session assert User.new.respond_to?(:age) end def test_access_to_helpers load_environment + helper = irb_context.helper assert_not_nil helper assert_instance_of ActionView::Base, helper assert_equal 'Once upon a time in a world...', diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 9183945619..b70f9f4d9c 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -236,6 +236,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "spec/dummy/config/application.rb" assert_no_file "test" end + + def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path + FileUtils.cd(Rails.root) + run_generator([destination_root, "--dummy_path", "spec/dummy" "--skip-test-unit"]) + assert_file ".gitignore" do |contents| + assert_match(/spec\/dummy/, contents) + end + end def test_skipping_test_unit run_generator [destination_root, "--skip-test-unit"] |