2016-08-06 12:54:50 -04:00
|
|
|
require "abstract_unit"
|
|
|
|
require "zlib"
|
2009-03-10 00:31:04 -04:00
|
|
|
|
2010-07-29 08:12:25 -04:00
|
|
|
module StaticTests
|
2015-05-01 22:12:45 -04:00
|
|
|
DummyApp = lambda { |env|
|
2016-08-16 03:30:11 -04:00
|
|
|
[200, { "Content-Type" => "text/plain" }, ["Hello, World!"]]
|
2015-05-01 22:12:45 -04:00
|
|
|
}
|
|
|
|
|
2015-03-26 14:46:19 -04:00
|
|
|
def setup
|
2015-04-14 08:54:13 -04:00
|
|
|
silence_warnings do
|
|
|
|
@default_internal_encoding = Encoding.default_internal
|
|
|
|
@default_external_encoding = Encoding.default_external
|
|
|
|
end
|
2015-03-26 14:46:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def teardown
|
2015-04-14 08:54:13 -04:00
|
|
|
silence_warnings do
|
|
|
|
Encoding.default_internal = @default_internal_encoding
|
|
|
|
Encoding.default_external = @default_external_encoding
|
|
|
|
end
|
2015-03-26 14:46:19 -04:00
|
|
|
end
|
|
|
|
|
2010-07-29 08:12:25 -04:00
|
|
|
def test_serves_dynamic_content
|
2010-10-04 12:06:04 -04:00
|
|
|
assert_equal "Hello, World!", get("/nofile").body
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
|
|
|
|
2012-03-08 06:26:10 -05:00
|
|
|
def test_handles_urls_with_bad_encoding
|
2012-12-07 01:02:43 -05:00
|
|
|
assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
|
2012-03-08 06:26:10 -05:00
|
|
|
end
|
|
|
|
|
2015-03-26 14:46:19 -04:00
|
|
|
def test_handles_urls_with_ascii_8bit
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body
|
2015-03-26 14:46:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_handles_urls_with_ascii_8bit_on_win_31j
|
2015-04-14 08:54:13 -04:00
|
|
|
silence_warnings do
|
|
|
|
Encoding.default_internal = "Windows-31J"
|
|
|
|
Encoding.default_external = "Windows-31J"
|
|
|
|
end
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding("ASCII-8BIT")).body
|
2015-03-26 14:46:19 -04:00
|
|
|
end
|
|
|
|
|
2016-01-12 14:05:54 -05:00
|
|
|
def test_handles_urls_with_null_byte
|
|
|
|
assert_equal "Hello, World!", get("/doorkeeper%00").body
|
|
|
|
end
|
|
|
|
|
2010-07-29 08:12:25 -04:00
|
|
|
def test_serves_static_index_at_root
|
2010-10-04 12:06:04 -04:00
|
|
|
assert_html "/index.html", get("/index.html")
|
|
|
|
assert_html "/index.html", get("/index")
|
|
|
|
assert_html "/index.html", get("/")
|
|
|
|
assert_html "/index.html", get("")
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
|
|
|
|
2010-07-29 08:12:25 -04:00
|
|
|
def test_serves_static_file_in_directory
|
2010-10-04 12:06:04 -04:00
|
|
|
assert_html "/foo/bar.html", get("/foo/bar.html")
|
|
|
|
assert_html "/foo/bar.html", get("/foo/bar/")
|
|
|
|
assert_html "/foo/bar.html", get("/foo/bar")
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
|
|
|
|
2010-07-29 08:12:25 -04:00
|
|
|
def test_serves_static_index_file_in_directory
|
2010-10-04 12:06:04 -04:00
|
|
|
assert_html "/foo/index.html", get("/foo/index.html")
|
2015-05-04 15:55:23 -04:00
|
|
|
assert_html "/foo/index.html", get("/foo/index")
|
2010-10-04 12:06:04 -04:00
|
|
|
assert_html "/foo/index.html", get("/foo/")
|
|
|
|
assert_html "/foo/index.html", get("/foo")
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
|
|
|
|
2014-08-18 12:20:06 -04:00
|
|
|
def test_serves_file_with_same_name_before_index_in_directory
|
|
|
|
assert_html "/bar.html", get("/bar")
|
|
|
|
end
|
|
|
|
|
2012-02-05 08:34:29 -05:00
|
|
|
def test_served_static_file_with_non_english_filename
|
|
|
|
assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
|
|
|
|
end
|
|
|
|
|
2012-02-19 23:46:45 -05:00
|
|
|
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
|
|
|
|
|
2014-08-11 14:29:25 -04:00
|
|
|
def test_serves_gzip_files_when_header_set
|
|
|
|
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
|
2016-08-06 12:54:50 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip")
|
2014-08-11 14:29:25 -04:00
|
|
|
assert_gzip file_name, response
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal "application/javascript", response.headers["Content-Type"]
|
|
|
|
assert_equal "Accept-Encoding", response.headers["Vary"]
|
|
|
|
assert_equal "gzip", response.headers["Content-Encoding"]
|
2014-08-11 14:29:25 -04:00
|
|
|
|
2016-10-28 23:05:58 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "Gzip")
|
|
|
|
assert_gzip file_name, response
|
2014-08-21 12:52:25 -04:00
|
|
|
|
2016-10-28 23:05:58 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "GZIP")
|
|
|
|
assert_gzip file_name, response
|
2014-08-21 12:52:25 -04:00
|
|
|
|
2016-10-28 23:05:58 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "compress;q=0.5, gzip;q=1.0")
|
|
|
|
assert_gzip file_name, response
|
Don't raise ActionController::UnknownHttpMethod from ActionDispatch::Static
The `ActionDispatch::Static` middleware is used low down in the stack to serve
static assets before doing much processing. Since it's called from so low in
the stack, we don't have access to the request ID at this point, and generally
won't have any exception handling defined (by default `ShowExceptions` is added
to the stack quite a bit higher and relies on logging and request ID).
Before https://github.com/rails/rails/commit/8f27d6036a2ddc3cb7a7ad98afa2666ec163c2c3
this middleware would ignore unknown HTTP methods, and an exception about these
would be raised higher in the stack. After that commit, however, that exception
will be raised here.
If we want to keep `ActionDispatch::Static` so low in the stack (I think we do)
we should suppress the `ActionController::UnknownHttpMethod` exception here,
and instead let it be raised higher up the stack, once we've had a chance to
define exception handling behaviour.
This PR updates `ActionDispatch::Static` so it passes `Rack::Request` objects to
`ActionDispatch::FileHandler`, which won't raise an
`ActionController::UnknownHttpMethod` error. If an unknown method is
passed, it should exception higher in the stack instead, once we've had a
chance to define exception handling behaviour.`
2016-07-12 07:34:21 -04:00
|
|
|
|
2016-10-28 23:05:58 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "")
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_not_equal "gzip", response.headers["Content-Encoding"]
|
2014-08-21 12:52:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_does_not_modify_path_info
|
|
|
|
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
|
2016-08-16 03:30:11 -04:00
|
|
|
env = { "PATH_INFO" => file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "REQUEST_METHOD" => "POST" }
|
2014-08-21 12:52:25 -04:00
|
|
|
@app.call(env)
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal file_name, env["PATH_INFO"]
|
2014-08-21 12:52:25 -04:00
|
|
|
end
|
|
|
|
|
2017-02-05 20:00:18 -05:00
|
|
|
def test_serves_gzip_with_proper_content_type_fallback
|
2014-08-21 12:52:25 -04:00
|
|
|
file_name = "/gzip/foo.zoo"
|
2016-08-06 12:54:50 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip")
|
2016-10-28 23:05:58 -04:00
|
|
|
assert_gzip file_name, response
|
2014-08-21 12:52:25 -04:00
|
|
|
|
|
|
|
default_response = get(file_name) # no gzip
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal default_response.headers["Content-Type"], response.headers["Content-Type"]
|
2014-08-11 14:29:25 -04:00
|
|
|
end
|
|
|
|
|
2015-03-10 03:51:51 -04:00
|
|
|
def test_serves_gzip_files_with_not_modified
|
|
|
|
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
|
|
|
|
last_modified = File.mtime(File.join(@root, "#{file_name}.gz"))
|
2016-08-06 12:54:50 -04:00
|
|
|
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "HTTP_IF_MODIFIED_SINCE" => last_modified.httpdate)
|
2015-03-10 03:51:51 -04:00
|
|
|
assert_equal 304, response.status
|
2016-12-24 12:29:52 -05:00
|
|
|
assert_nil response.headers["Content-Type"]
|
|
|
|
assert_nil response.headers["Content-Encoding"]
|
|
|
|
assert_nil response.headers["Vary"]
|
2015-03-10 03:51:51 -04:00
|
|
|
end
|
|
|
|
|
2015-05-01 22:12:45 -04:00
|
|
|
def test_serves_files_with_headers
|
|
|
|
headers = {
|
2016-08-06 12:54:50 -04:00
|
|
|
"Access-Control-Allow-Origin" => "http://rubyonrails.org",
|
|
|
|
"Cache-Control" => "public, max-age=60",
|
2015-05-01 22:12:45 -04:00
|
|
|
"X-Custom-Header" => "I'm a teapot"
|
|
|
|
}
|
|
|
|
|
|
|
|
app = ActionDispatch::Static.new(DummyApp, @root, headers: headers)
|
|
|
|
response = Rack::MockRequest.new(app).request("GET", "/foo/bar.html")
|
|
|
|
|
2016-08-06 12:54:50 -04:00
|
|
|
assert_equal "http://rubyonrails.org", response.headers["Access-Control-Allow-Origin"]
|
|
|
|
assert_equal "public, max-age=60", response.headers["Cache-Control"]
|
2015-05-01 22:12:45 -04:00
|
|
|
assert_equal "I'm a teapot", response.headers["X-Custom-Header"]
|
|
|
|
end
|
|
|
|
|
Don't raise ActionController::UnknownHttpMethod from ActionDispatch::Static
The `ActionDispatch::Static` middleware is used low down in the stack to serve
static assets before doing much processing. Since it's called from so low in
the stack, we don't have access to the request ID at this point, and generally
won't have any exception handling defined (by default `ShowExceptions` is added
to the stack quite a bit higher and relies on logging and request ID).
Before https://github.com/rails/rails/commit/8f27d6036a2ddc3cb7a7ad98afa2666ec163c2c3
this middleware would ignore unknown HTTP methods, and an exception about these
would be raised higher in the stack. After that commit, however, that exception
will be raised here.
If we want to keep `ActionDispatch::Static` so low in the stack (I think we do)
we should suppress the `ActionController::UnknownHttpMethod` exception here,
and instead let it be raised higher up the stack, once we've had a chance to
define exception handling behaviour.
This PR updates `ActionDispatch::Static` so it passes `Rack::Request` objects to
`ActionDispatch::FileHandler`, which won't raise an
`ActionController::UnknownHttpMethod` error. If an unknown method is
passed, it should exception higher in the stack instead, once we've had a
chance to define exception handling behaviour.`
2016-07-12 07:34:21 -04:00
|
|
|
def test_ignores_unknown_http_methods
|
|
|
|
app = ActionDispatch::Static.new(DummyApp, @root)
|
|
|
|
|
|
|
|
assert_nothing_raised { Rack::MockRequest.new(app).request("BAD_METHOD", "/foo/bar.html") }
|
|
|
|
end
|
|
|
|
|
2012-02-19 23:46:45 -05:00
|
|
|
# Windows doesn't allow \ / : * ? " < > | in filenames
|
2016-11-30 06:57:50 -05:00
|
|
|
unless Gem.win_platform?
|
2012-02-19 23:46:45 -05:00
|
|
|
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
|
2012-02-16 13:50:07 -05:00
|
|
|
end
|
|
|
|
|
2009-03-10 00:31:04 -04:00
|
|
|
private
|
2010-10-04 12:06:04 -04:00
|
|
|
|
2014-08-11 14:29:25 -04:00
|
|
|
def assert_gzip(file_name, response)
|
|
|
|
expected = File.read("#{FIXTURE_LOAD_PATH}/#{public_path}" + file_name)
|
|
|
|
actual = Zlib::GzipReader.new(StringIO.new(response.body)).read
|
|
|
|
assert_equal expected, actual
|
|
|
|
end
|
|
|
|
|
2010-10-04 12:06:04 -04:00
|
|
|
def assert_html(body, response)
|
|
|
|
assert_equal body, response.body
|
|
|
|
assert_equal "text/html", response.headers["Content-Type"]
|
2016-01-19 17:22:40 -05:00
|
|
|
assert_nil response.headers["Vary"]
|
2010-10-04 12:06:04 -04:00
|
|
|
end
|
|
|
|
|
2014-08-11 14:29:25 -04:00
|
|
|
def get(path, headers = {})
|
|
|
|
Rack::MockRequest.new(@app).request("GET", path, headers)
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
2012-02-19 23:46:45 -05:00
|
|
|
|
|
|
|
def with_static_file(file)
|
2013-12-29 13:24:02 -05:00
|
|
|
path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
|
2014-02-08 19:02:44 -05:00
|
|
|
begin
|
|
|
|
File.open(path, "wb+") { |f| f.write(file) }
|
|
|
|
rescue Errno::EPROTO
|
|
|
|
skip "Couldn't create a file #{path}"
|
|
|
|
end
|
|
|
|
|
2012-02-19 23:46:45 -05:00
|
|
|
yield file
|
|
|
|
ensure
|
2014-02-09 03:39:55 -05:00
|
|
|
File.delete(path) if File.exist? path
|
2012-02-19 23:46:45 -05:00
|
|
|
end
|
2009-03-10 00:31:04 -04:00
|
|
|
end
|
2010-07-29 08:12:25 -04:00
|
|
|
|
|
|
|
class StaticTest < ActiveSupport::TestCase
|
|
|
|
def setup
|
2015-03-26 14:46:19 -04:00
|
|
|
super
|
2014-10-10 19:00:03 -04:00
|
|
|
@root = "#{FIXTURE_LOAD_PATH}/public"
|
2016-08-16 03:30:11 -04:00
|
|
|
@app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" })
|
2013-12-29 13:24:02 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def public_path
|
|
|
|
"public"
|
2010-07-29 08:12:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
include StaticTests
|
2014-10-10 19:00:03 -04:00
|
|
|
|
|
|
|
def test_custom_handler_called_when_file_is_outside_root
|
2016-08-06 12:54:50 -04:00
|
|
|
filename = "shared.html.erb"
|
|
|
|
assert File.exist?(File.join(@root, "..", filename))
|
2014-10-10 19:00:03 -04:00
|
|
|
env = {
|
2016-10-28 23:05:58 -04:00
|
|
|
"REQUEST_METHOD" => "GET",
|
|
|
|
"REQUEST_PATH" => "/..%2F#{filename}",
|
|
|
|
"PATH_INFO" => "/..%2F#{filename}",
|
|
|
|
"REQUEST_URI" => "/..%2F#{filename}",
|
|
|
|
"HTTP_VERSION" => "HTTP/1.1",
|
|
|
|
"SERVER_NAME" => "localhost",
|
|
|
|
"SERVER_PORT" => "8080",
|
|
|
|
"QUERY_STRING" => ""
|
2014-10-10 19:00:03 -04:00
|
|
|
}
|
|
|
|
assert_equal(DummyApp.call(nil), @app.call(env))
|
|
|
|
end
|
2015-05-04 15:55:23 -04:00
|
|
|
|
|
|
|
def test_non_default_static_index
|
2015-05-01 22:12:45 -04:00
|
|
|
@app = ActionDispatch::Static.new(DummyApp, @root, index: "other-index")
|
2015-05-04 15:55:23 -04:00
|
|
|
assert_html "/other-index.html", get("/other-index.html")
|
|
|
|
assert_html "/other-index.html", get("/other-index")
|
|
|
|
assert_html "/other-index.html", get("/")
|
|
|
|
assert_html "/other-index.html", get("")
|
|
|
|
assert_html "/foo/other-index.html", get("/foo/other-index.html")
|
|
|
|
assert_html "/foo/other-index.html", get("/foo/other-index")
|
|
|
|
assert_html "/foo/other-index.html", get("/foo/")
|
|
|
|
assert_html "/foo/other-index.html", get("/foo")
|
|
|
|
end
|
2012-02-05 08:34:29 -05:00
|
|
|
end
|
2013-12-29 13:24:02 -05:00
|
|
|
|
|
|
|
class StaticEncodingTest < StaticTest
|
|
|
|
def setup
|
2015-03-26 14:46:19 -04:00
|
|
|
super
|
2014-10-10 19:00:03 -04:00
|
|
|
@root = "#{FIXTURE_LOAD_PATH}/公共"
|
2016-08-16 03:30:11 -04:00
|
|
|
@app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" })
|
2013-12-29 13:24:02 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def public_path
|
|
|
|
"公共"
|
|
|
|
end
|
|
|
|
end
|