Skip to content

Commit

Permalink
Disable the Accept header by default
Browse files Browse the repository at this point in the history
The accept header is poorly implemented by browsers and causes strange errors when used on public sites where crawlers make requests too.  You should use formatted urls (e.g. /people/1.xml) to support API clients. Alternatively to re-enable it you need to set:

config.action_controller.use_accept_header = true

A special case remains for ajax requests which will have a javascript format for the base resource (/people/1) if the X-Requested-With header is present.  This lets ajax pages still use format.js despite there being no params[:format]
  • Loading branch information
NZKoz committed Jul 7, 2008
1 parent afa0c7f commit 2f4aaed
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 30 deletions.
10 changes: 10 additions & 0 deletions actionpack/CHANGELOG
@@ -1,5 +1,15 @@
*Edge*

* Disable the Accept header by default [Michael Koziarski]

The accept header is poorly implemented by browsers and causes strange
errors when used on public sites where crawlers make requests too. You
should use formatted urls (e.g. /people/1.xml) to support API clients.

Alternatively to re-enable it you need to set:

config.action_controller.use_accept_header = true

* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]

* Deprecated TemplateHandler line offset [Josh Peek]
Expand Down
10 changes: 10 additions & 0 deletions actionpack/lib/action_controller/base.rb
Expand Up @@ -340,6 +340,16 @@ class Base
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true

# Indicates whether the response format should be determined by examining the Accept HTTP header,
# or by using the simpler params + ajax rules.
#
# If this is set to +true+ then +respond_to+ and +Request#format+ will take the Accept header into
# account. If it is set to false (the default) then the request format will be determined solely
# by examining params[:format]. If params format is missing, the format will be either HTML or
# Javascript depending on whether the request is an AJAX request.
cattr_accessor :use_accept_header
self.use_accept_header = false

# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
Expand Down
5 changes: 1 addition & 4 deletions actionpack/lib/action_controller/caching/actions.rb
Expand Up @@ -166,10 +166,7 @@ def extract_extension(request)

# If there's no extension in the path, check request.format
if extension.nil?
extension = request.format.to_sym.to_s
if extension=='all'
extension = nil
end
extension = request.cache_format
end
extension
end
Expand Down
6 changes: 5 additions & 1 deletion actionpack/lib/action_controller/mime_responds.rb
Expand Up @@ -114,7 +114,11 @@ def initialize(controller)
@request = controller.request
@response = controller.response

@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
if ActionController::Base.use_accept_header
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
else
@mime_type_priority = [@request.format]
end

@order = []
@responses = {}
Expand Down
34 changes: 25 additions & 9 deletions actionpack/lib/action_controller/request.rb
Expand Up @@ -89,14 +89,23 @@ def accepts
end
end

# Returns the Mime type for the format used in the request. If there is no format available, the first of the
# accept types will be used. Examples:
# Returns the Mime type for the format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
# GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
@format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
@format ||= begin
if parameters[:format]
Mime::Type.lookup_by_extension(parameters[:format])
elsif ActionController::Base.use_accept_header
accepts.first
elsif xhr?
Mime::Type.lookup_by_extension("js")
else
Mime::Type.lookup_by_extension("html")
end
end
end


Expand All @@ -116,19 +125,26 @@ def format=(extension)
@format = Mime::Type.lookup_by_extension(parameters[:format])
end

# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
# If no format is given it returns <tt>:js</tt>for AJAX requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters[:format]

case
when parameter_format.blank? && !xhr?
:html
when parameter_format.blank? && xhr?
if parameter_format
parameter_format.to_sym
elsif xhr?
:js
else
parameter_format.to_sym
:html
end
end

def cache_format
parameter_format = parameters[:format]
parameter_format && parameter_format.to_sym
end

# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
Expand Down
10 changes: 3 additions & 7 deletions actionpack/lib/action_view/base.rb
Expand Up @@ -256,13 +256,9 @@ def file_public?(template_path)#:nodoc:
template_path.split('/').last[0,1] != '_'
end

# Returns a symbolized version of the <tt>:format</tt> parameter of the request,
# or <tt>:html</tt> by default.
#
# EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for
# whether it contains the JavaScript mime type as its first priority. If that's the case,
# it will be used. This ensures that Ajax applications can use the same URL to support both
# JavaScript and non-JavaScript users.
# The format to be used when choosing between multiple templates with
# the same name but differing formats. See +Request#template_format+
# for more details.
def template_format
return @template_format if @template_format

Expand Down
10 changes: 2 additions & 8 deletions actionpack/test/controller/caching_test.rb
Expand Up @@ -131,8 +131,7 @@ def test_should_cache_ok_at_custom_path
end

def test_page_caching_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :ok
get :ok, :format=>'json'
assert_page_not_cached :ok
end

Expand Down Expand Up @@ -219,6 +218,7 @@ def request
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
def cache_format; nil end
self
EVAL
end
Expand Down Expand Up @@ -414,12 +414,6 @@ def test_xml_version_of_resource_is_treated_as_different_cache
assert_equal 'application/xml', @response.content_type
reset!

@request.env['HTTP_ACCEPT'] = "application/xml"
get :index
assert_equal cached_time, @response.body
assert_equal 'application/xml', @response.content_type
reset!

get :expire_xml
reset!

Expand Down
14 changes: 14 additions & 0 deletions actionpack/test/controller/content_type_test.rb
Expand Up @@ -114,6 +114,20 @@ def test_change_for_rxml
assert_equal Mime::HTML, @response.content_type
assert_equal "utf-8", @response.charset
end
end

class AcceptBasedContentTypeTest < ActionController::TestCase

tests ContentTypeController

def setup
ActionController::Base.use_accept_header = true
end

def tear_down
ActionController::Base.use_accept_header = false
end


def test_render_default_content_types_for_respond_to
@request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
Expand Down
5 changes: 5 additions & 0 deletions actionpack/test/controller/mime_responds_test.rb
Expand Up @@ -166,13 +166,18 @@ def set_layout

class MimeControllerTest < Test::Unit::TestCase
def setup
ActionController::Base.use_accept_header = true
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new

@controller = RespondToController.new
@request.host = "www.example.com"
end

def teardown
ActionController::Base.use_accept_header = false
end

def test_html
@request.env["HTTP_ACCEPT"] = "text/html"
get :js_or_html
Expand Down
2 changes: 1 addition & 1 deletion actionpack/test/controller/request_test.rb
Expand Up @@ -386,7 +386,7 @@ def test_txt_format

def test_nil_format
@request.instance_eval { @parameters = { :format => nil } }
@request.env["HTTP_ACCEPT"] = "text/javascript"
@request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
assert_equal Mime::JS, @request.format
end

Expand Down

2 comments on commit 2f4aaed

@mikekelly
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omgsadface

@iain
Copy link
Contributor

@iain iain commented on 2f4aaed Aug 31, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you!

Please sign in to comment.