<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,7 @@
 *SVN*
 
+* Routing: respond with 405 Method Not Allowed status when the route path matches but the HTTP method does not.  #6953 [Josh Peek, defeated, Dan Kubb, Coda Hale]
+
 * Add support for assert_select_rjs with :show and :hide. #7780 [dchelimsky]
 
 * Make assert_select's failure messages clearer about what failed. #7779 [dchelimsky]</diff>
      <filename>actionpack/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -22,6 +22,24 @@ module ActionController #:nodoc:
       @failures = failures
     end
   end
+  class MethodNotAllowed &lt; ActionControllerError #:nodoc:
+    attr_reader :allowed_methods
+
+    def initialize(*allowed_methods)
+      super(&quot;Only #{allowed_methods.to_sentence} requests are allowed.&quot;)
+      @allowed_methods = allowed_methods
+    end
+
+    def allowed_methods_header
+      allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
+    end
+
+    def handle_response!(response)
+      response.headers['Allow'] ||= allowed_methods_header
+    end
+  end
+  class NotImplemented &lt; MethodNotAllowed #:nodoc:
+  end
   class UnknownController &lt; ActionControllerError #:nodoc:
   end
   class UnknownAction &lt; ActionControllerError #:nodoc:</diff>
      <filename>actionpack/lib/action_controller/base.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,12 +13,14 @@ module ActionController #:nodoc:
 
     DEFAULT_RESCUE_RESPONSE = :internal_server_error
     DEFAULT_RESCUE_RESPONSES = {
-      'ActionController::RoutingError'    =&gt; :not_found,
-      'ActionController::UnknownAction'   =&gt; :not_found,
-      'ActiveRecord::RecordNotFound'      =&gt; :not_found,
-      'ActiveRecord::StaleObjectError'    =&gt; :conflict,
-      'ActiveRecord::RecordInvalid'       =&gt; :unprocessable_entity,
-      'ActiveRecord::RecordNotSaved'      =&gt; :unprocessable_entity
+      'ActionController::RoutingError'     =&gt; :not_found,
+      'ActionController::UnknownAction'    =&gt; :not_found,
+      'ActiveRecord::RecordNotFound'       =&gt; :not_found,
+      'ActiveRecord::StaleObjectError'     =&gt; :conflict,
+      'ActiveRecord::RecordInvalid'        =&gt; :unprocessable_entity,
+      'ActiveRecord::RecordNotSaved'       =&gt; :unprocessable_entity,
+      'ActionController::MethodNotAllowed' =&gt; :method_not_allowed,
+      'ActionController::NotImplemented'   =&gt; :not_implemented
     }
 
     DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
@@ -56,6 +58,12 @@ module ActionController #:nodoc:
         log_error(exception) if logger
         erase_results if performed?
 
+        # Let the exception alter the response if it wants.
+        # For example, MethodNotAllowed sets the Allow header.
+        if exception.respond_to?(:handle_response!)
+          exception.handle_response!(response)
+        end
+
         if consider_all_requests_local || local_request?
           rescue_action_locally(exception)
         else</diff>
      <filename>actionpack/lib/action_controller/rescue.rb</filename>
    </modified>
    <modified>
      <diff>@@ -251,6 +251,8 @@ module ActionController
     # TODO: , (comma) should be an allowed path character.
     SEPARATORS = %w( / ; . , ? )
 
+    HTTP_METHODS = [:get, :head, :post, :put, :delete]
+
     # The root paths which may contain controller files
     mattr_accessor :controller_paths
     self.controller_paths = []
@@ -1345,7 +1347,16 @@ module ActionController
         routes.each do |route|
           result = route.recognize(path, environment) and return result
         end
-        raise RoutingError, &quot;no route found to match #{path.inspect} with #{environment.inspect}&quot;
+
+        allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method =&gt; verb) } }
+
+        if environment[:method] &amp;&amp; !HTTP_METHODS.include?(environment[:method])
+          raise NotImplemented.new(*allows)
+        elsif !allows.empty?
+          raise MethodNotAllowed.new(*allows)
+        else
+          raise RoutingError, &quot;No route matches #{path.inspect} with #{environment.inspect}&quot;
+        end
       end
   
       def routes_by_controller</diff>
      <filename>actionpack/lib/action_controller/routing.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,6 +7,14 @@ class RescueController &lt; ActionController::Base
     render :text =&gt; 'already rendered'
     raise &quot;don't panic!&quot;
   end
+
+  def method_not_allowed
+    raise ActionController::MethodNotAllowed.new(:get, :head, :put)
+  end
+  
+  def not_implemented
+    raise ActionController::NotImplemented.new(:get, :put)
+  end
 end
 
 
@@ -144,6 +152,8 @@ class RescueTest &lt; Test::Unit::TestCase
     assert_equal :conflict, responses['ActiveRecord::StaleObjectError']
     assert_equal :unprocessable_entity, responses['ActiveRecord::RecordInvalid']
     assert_equal :unprocessable_entity, responses['ActiveRecord::RecordNotSaved']
+    assert_equal :method_not_allowed, responses['ActionController::MethodNotAllowed']
+    assert_equal :not_implemented, responses['ActionController::NotImplemented']
   end
 
   def test_rescue_templates
@@ -176,6 +186,22 @@ class RescueTest &lt; Test::Unit::TestCase
       assert_nil @controller.send(:clean_backtrace, Exception.new)
     end
   end
+  
+  def test_not_implemented
+    with_all_requests_local false do
+      head :not_implemented
+    end
+    assert_response :not_implemented
+    assert_equal &quot;GET, PUT&quot;, @response.headers['Allow']
+  end
+
+  def test_method_not_allowed
+    with_all_requests_local false do
+      get :method_not_allowed
+    end
+    assert_response :method_not_allowed
+    assert_equal &quot;GET, HEAD, PUT&quot;, @response.headers['Allow']
+  end
 
   protected
     def with_all_requests_local(local = true)</diff>
      <filename>actionpack/test/controller/rescue_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -188,7 +188,7 @@ class ResourcesTest &lt; Test::Unit::TestCase
     with_restful_routing :messages do
       assert_restful_routes_for :messages do |options|
         assert_recognizes(options.merge(:action =&gt; &quot;new&quot;), :path =&gt; &quot;/messages/new&quot;, :method =&gt; :get)
-        assert_raises(ActionController::RoutingError) do
+        assert_raises(ActionController::MethodNotAllowed) do
           ActionController::Routing::Routes.recognize_path(&quot;/messages/new&quot;, :method =&gt; :post)
         end
       end
@@ -384,11 +384,11 @@ class ResourcesTest &lt; Test::Unit::TestCase
       options = { :controller =&gt; controller_name.to_s }
       collection_path = &quot;/#{controller_name}&quot;
 
-      assert_raises(ActionController::RoutingError) do
+      assert_raises(ActionController::MethodNotAllowed) do
         assert_recognizes(options.merge(:action =&gt; 'update'), :path =&gt; collection_path, :method =&gt; :put)
       end
 
-      assert_raises(ActionController::RoutingError) do
+      assert_raises(ActionController::MethodNotAllowed) do
         assert_recognizes(options.merge(:action =&gt; 'destroy'), :path =&gt; collection_path, :method =&gt; :delete)
       end
     end</diff>
      <filename>actionpack/test/controller/resources_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1590,8 +1590,13 @@ class RouteSetTest &lt; Test::Unit::TestCase
     assert_nothing_raised { set.recognize(request) }
     assert_equal(&quot;update&quot;, request.path_parameters[:action])
 
-    request.method = :update
-    assert_raises(ActionController::RoutingError) { set.recognize(request) }
+    begin
+      request.method = :bacon
+      set.recognize(request)
+      flunk 'Should have raised NotImplemented'
+    rescue ActionController::NotImplemented =&gt; e
+      assert_equal [:get, :post, :put, :delete], e.allowed_methods
+    end
 
     request.path = &quot;/people/5&quot;
     request.method = :get
@@ -1608,10 +1613,15 @@ class RouteSetTest &lt; Test::Unit::TestCase
     assert_nothing_raised { set.recognize(request) }
     assert_equal(&quot;destroy&quot;, request.path_parameters[:action])
     assert_equal(&quot;5&quot;, request.path_parameters[:id])
-    
-    request.method = :post
-    assert_raises(ActionController::RoutingError) { set.recognize(request) }
-    
+
+    begin
+      request.method = :post
+      set.recognize(request)
+      flunk 'Should have raised MethodNotAllowed'
+    rescue ActionController::MethodNotAllowed =&gt; e
+      assert_equal [:get, :put, :delete], e.allowed_methods
+    end
+
   ensure
     Object.send(:remove_const, :PeopleController)
   end</diff>
      <filename>actionpack/test/controller/routing_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>42ebf559cc6ed80fa3bf55ecc1dea7c9e593f45e</id>
    </parent>
  </parents>
  <author>
    <name>Jeremy Kemper</name>
    <login>jeremy</login>
    <email>jeremy@bitsweat.net</email>
  </author>
  <url>http://github.com/rails/rails/commit/dcaa074abf5691a933b9c55159cc7d98a02b3b2f</url>
  <id>dcaa074abf5691a933b9c55159cc7d98a02b3b2f</id>
  <committed-date>2007-05-26T13:07:34-07:00</committed-date>
  <authored-date>2007-05-26T13:07:34-07:00</authored-date>
  <message>Routing: respond with 405 Method Not Allowed status when the route path matches but the HTTP method does not. Closes #6953.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6862 5ecf4fe2-1ee6-0310-87b1-e25e094e27de</message>
  <tree>4d58fee286eaa957e141939dcbd898055cab2325</tree>
  <committer>
    <name>Jeremy Kemper</name>
    <login>jeremy</login>
    <email>jeremy@bitsweat.net</email>
  </committer>
</commit>
