Skip to content

Commit

Permalink
Prevent conflict between mime types and Object methods
Browse files Browse the repository at this point in the history
Assuming the type ":touch", Collector.new was calling
send(:touch), which instead of triggering method_missing
and generating a new collector method, actually
invoked the private method `touch` inherited from
Object.

By generating the method for each mime type as it
is registered, the private methods on Object can
never be reached by `send`, because the `Collector`
will have them before `send` is called on it.

To do this, a callback mechanism was added to Mime::Type

This allows someone to add a callback for whenever
a new mime type is registered. The callback then
gets called with the new mime as a parameter.

This is then used in AbstractController::Collector
to generate new collector methods after each mime
is registered.
  • Loading branch information
mirceapricop authored and mircea committed Jul 6, 2012
1 parent 07314e6 commit 021f3d2
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 2 deletions.
4 changes: 4 additions & 0 deletions actionpack/lib/abstract_controller/collector.rb
Expand Up @@ -16,6 +16,10 @@ def #{sym}(*args, &block) # def html(*args, &block)
generate_method_for_mime(mime)
end

Mime::Type.register_callback do |mime|
generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym)
end

protected

def method_missing(symbol, &block)
Expand Down
13 changes: 12 additions & 1 deletion actionpack/lib/action_dispatch/http/mime_type.rb
Expand Up @@ -58,6 +58,8 @@ class Type
cattr_reader :browser_generated_types
attr_reader :symbol

@register_callbacks = []

# A simple helper class used in parsing the accept header
class AcceptItem #:nodoc:
attr_accessor :order, :name, :q
Expand Down Expand Up @@ -89,6 +91,10 @@ class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/

def register_callback(&block)
@register_callbacks << block
end

def lookup(string)
LOOKUP[string]
end
Expand All @@ -106,10 +112,15 @@ def register_alias(string, symbol, extension_synonyms = [])
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))

SET << Mime.const_get(symbol.upcase)
new_mime = Mime.const_get(symbol.upcase)
SET << new_mime

([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }

@register_callbacks.each do |callback|
callback.call(new_mime)
end
end

def parse(accept_header)
Expand Down
4 changes: 3 additions & 1 deletion actionpack/test/controller/mime_responds_test.rb
Expand Up @@ -505,7 +505,7 @@ def test_invalid_format
end

class RespondWithController < ActionController::Base
respond_to :html, :json
respond_to :html, :json, :touch
respond_to :xml, :except => :using_resource_with_block
respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]

Expand Down Expand Up @@ -623,12 +623,14 @@ def setup
super
@request.host = "www.example.com"
Mime::Type.register_alias('text/html', :iphone)
Mime::Type.register_alias('text/html', :touch)
Mime::Type.register('text/x-mobile', :mobile)
end

def teardown
super
Mime::Type.unregister(:iphone)
Mime::Type.unregister(:touch)
Mime::Type.unregister(:mobile)
end

Expand Down
14 changes: 14 additions & 0 deletions actionpack/test/dispatch/mime_type_test.rb
Expand Up @@ -118,6 +118,20 @@ class MimeTypeTest < ActiveSupport::TestCase
end
end

test "register callbacks" do
begin
registered_mimes = []
Mime::Type.register_callback do |mime|
registered_mimes << mime
end

Mime::Type.register("text/foo", :foo)
assert_equal registered_mimes, [Mime::FOO]
ensure
Mime::Type.unregister(:FOO)
end
end

test "custom type with extension aliases" do
begin
Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
Expand Down

0 comments on commit 021f3d2

Please sign in to comment.