aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md5
-rw-r--r--actionpack/README.rdoc2
-rw-r--r--actionpack/lib/abstract_controller/base.rb4
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb8
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb10
-rw-r--r--actionpack/test/controller/integration_test.rb18
-rw-r--r--actionpack/test/controller/render_test.rb27
-rw-r--r--actionpack/test/dispatch/static_test.rb90
11 files changed, 188 insertions, 14 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 459f5b90e4..cf662500a4 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Integration tests support the `OPTIONS` method. *Jeremy Kemper*
+
+* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate"
+ is added to the Cache-Control header. *fxn*
+
* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski*
* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url`
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 076a93bbcd..1fdc57e14d 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -218,7 +218,7 @@ A short rundown of some of the major features:
def show
# the output of the method will be cached as
- # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
+ # ActionController::Base.page_cache_directory + "/weblog/show.html"
# and the web server will pick it up without even hitting Rails
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index fd6a46fbec..3d915cf513 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -42,8 +42,8 @@ module AbstractController
controller.public_instance_methods(true)
end
- # The list of hidden actions to an empty array. Defaults to an
- # empty array. This can be modified by other modules or subclasses
+ # The list of hidden actions. Defaults to an empty array.
+ # This can be modified by other modules or subclasses
# to specify particular actions as hidden.
#
# ==== Returns
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 1645400693..2a8e4c575e 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -110,16 +110,23 @@ module ActionController
#
# Examples:
# expires_in 20.minutes
- # expires_in 3.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
# expires_in 3.hours, 'max-stale' => 5.hours, :public => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index bea62b94d2..5ee4c044ea 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -60,6 +60,20 @@ module ActionDispatch
headers[LAST_MODIFIED] = utc_time.httpdate
end
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
+ end
+
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 404943d720..63b7422287 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,4 +1,5 @@
require 'rack/utils'
+require 'active_support/core_ext/uri'
module ActionDispatch
class FileHandler
@@ -11,14 +12,14 @@ module ActionDispatch
def match?(path)
path = path.dup
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
match = matches.detect { |m| File.file?(m) }
if match
match.sub!(@compiled_root, '')
- match
+ ::Rack::Utils.escape(match)
end
end
@@ -32,6 +33,14 @@ module ActionDispatch
"{,#{ext},/index#{ext}}"
end
end
+
+ def unescape_path(path)
+ URI.parser.unescape(path)
+ end
+
+ def escape_glob_chars(path)
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ end
end
class Static
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 08b7ff49c2..0287e7728b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -56,6 +56,12 @@ module ActionDispatch
process :head, path, parameters, headers
end
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
+ # more details.
+ def options(path, parameters = nil, headers = nil)
+ process :options, path, parameters, headers
+ end
+
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
@@ -312,7 +318,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post put head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 44e24fecd1..cae345a1d6 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -1102,14 +1102,14 @@ module ActionView
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
- # submit button label, otherwise, it uses "Update Post".
+ # button label, otherwise, it uses "Update Post".
#
- # Those labels can be customized using I18n, under the helpers.submit key and accept
- # the %{model} as translation interpolation:
+ # Those labels can be customized using I18n, under the helpers.submit key
+ # (the same as submit helper) and accept the %{model} as translation interpolation:
#
# en:
# helpers:
- # button:
+ # submit:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
@@ -1117,7 +1117,7 @@ module ActionView
#
# en:
# helpers:
- # button:
+ # submit:
# post:
# create: "Add %{model}"
#
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 99e1dc7966..64c4682015 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -105,6 +105,12 @@ class SessionTest < ActiveSupport::TestCase
@session.head(path,params,headers)
end
+ def test_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:options,path,params,headers)
+ @session.options(path,params,headers)
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -155,6 +161,16 @@ class SessionTest < ActiveSupport::TestCase
@session.xml_http_request(:head,path,params,headers)
end
+ def test_xml_http_request_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:options,path,params,headers_after_xhr)
+ @session.xml_http_request(:options,path,params,headers)
+ end
+
def test_xml_http_request_override_accept
path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
headers_after_xhr = headers.merge(
@@ -212,7 +228,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head put delete ).each do |verb|
+ %w( get post head put delete options ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index fef9fbb175..e040878b26 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -91,6 +91,16 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_in_with_must_revalidate
+ expires_in 1.minute, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_and_must_revalidate
+ expires_in 1.minute, :public => true, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_expires_in_with_public_with_more_keys
expires_in 1.minute, :public => true, 'max-stale' => 5.hours
render :action => 'hello_world'
@@ -1399,6 +1409,16 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "max-age=60, public", @response.headers["Cache-Control"]
end
+ def test_expires_in_header_with_must_revalidate
+ get :conditional_hello_with_expires_in_with_must_revalidate
+ assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public_and_must_revalidate
+ get :conditional_hello_with_expires_in_with_public_and_must_revalidate
+ assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
+ end
+
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
@@ -1413,6 +1433,13 @@ class ExpiresInRenderTest < ActionController::TestCase
get :conditional_hello_with_expires_now
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+
+ def test_date_header_when_expires_in
+ time = Time.mktime(2011,10,30)
+ Time.stubs(:now).returns(time)
+ get :conditional_hello_with_expires_in
+ assert_equal Time.now.httpdate, @response.headers["Date"]
+ end
end
class LastModifiedRenderTest < ActionController::TestCase
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index b7a53353a9..092ca3e20a 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
+require 'rbconfig'
module StaticTests
def test_serves_dynamic_content
@@ -35,6 +36,87 @@ module StaticTests
assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
end
+
+ def test_serves_static_file_with_exclamation_mark_in_filename
+ with_static_file "/foo/foo!bar.html" do |file|
+ assert_html file, get("/foo/foo%21bar.html")
+ assert_html file, get("/foo/foo!bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_dollar_sign_in_filename
+ with_static_file "/foo/foo$bar.html" do |file|
+ assert_html file, get("/foo/foo%24bar.html")
+ assert_html file, get("/foo/foo$bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_ampersand_in_filename
+ with_static_file "/foo/foo&bar.html" do |file|
+ assert_html file, get("/foo/foo%26bar.html")
+ assert_html file, get("/foo/foo&bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_apostrophe_in_filename
+ with_static_file "/foo/foo'bar.html" do |file|
+ assert_html file, get("/foo/foo%27bar.html")
+ assert_html file, get("/foo/foo'bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_parentheses_in_filename
+ with_static_file "/foo/foo(bar).html" do |file|
+ assert_html file, get("/foo/foo%28bar%29.html")
+ assert_html file, get("/foo/foo(bar).html")
+ end
+ end
+
+ def test_serves_static_file_with_plus_sign_in_filename
+ with_static_file "/foo/foo+bar.html" do |file|
+ assert_html file, get("/foo/foo%2Bbar.html")
+ assert_html file, get("/foo/foo+bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_comma_in_filename
+ with_static_file "/foo/foo,bar.html" do |file|
+ assert_html file, get("/foo/foo%2Cbar.html")
+ assert_html file, get("/foo/foo,bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_semi_colon_in_filename
+ with_static_file "/foo/foo;bar.html" do |file|
+ assert_html file, get("/foo/foo%3Bbar.html")
+ assert_html file, get("/foo/foo;bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_at_symbol_in_filename
+ with_static_file "/foo/foo@bar.html" do |file|
+ assert_html file, get("/foo/foo%40bar.html")
+ assert_html file, get("/foo/foo@bar.html")
+ end
+ end
+
+ # Windows doesn't allow \ / : * ? " < > | in filenames
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ def test_serves_static_file_with_colon
+ with_static_file "/foo/foo:bar.html" do |file|
+ assert_html file, get("/foo/foo%3Abar.html")
+ assert_html file, get("/foo/foo:bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_asterisk
+ with_static_file "/foo/foo*bar.html" do |file|
+ assert_html file, get("/foo/foo%2Abar.html")
+ assert_html file, get("/foo/foo*bar.html")
+ end
+ end
+ end
+
private
def assert_html(body, response)
@@ -45,6 +127,14 @@ module StaticTests
def get(path)
Rack::MockRequest.new(@app).request("GET", path)
end
+
+ def with_static_file(file)
+ path = "#{FIXTURE_LOAD_PATH}/public" + file
+ File.open(path, "wb+") { |f| f.write(file) }
+ yield file
+ ensure
+ File.delete(path)
+ end
end
class StaticTest < ActiveSupport::TestCase