Skip to content

Commit 1bf364a

Browse files
author
Dan Kang
committed
Don't serve up assets without digests in development
If config.assets.digest = true and config.assets.raise_runtime_errors = true, only serve an asset if the request has a digest. Here are the different cases: - If the asset request has a digest and it matches the asset's digest, we serve the asset. - If the asset request has a digest and it does not match the asset's digest, we redirect to the asset. - If the asset request does not have a digest, we raise a NoDigestError. Fixes rails#117
1 parent 94a0332 commit 1bf364a

File tree

7 files changed

+170
-5
lines changed

7 files changed

+170
-5
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
### Unreleased
22

3+
* Don't serve up assets without digests in development.
4+
5+
If `config.assets.digest = true` and `config.assets.raise_runtime_errors = true`,
6+
serve an asset only if the request has a digest.
7+
8+
*Dan Kang*
9+
310
* Fix issues related `config.assets.manifest` option, including issues with `assets:precompile` Rake task.
411

512
*Johnny Shields*

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ Defines the full path to be used for the asset precompiler's manifest file. Defa
8989

9090
**`config.assets.digest`**
9191

92-
Link to undigest asset filenames. This option will eventually go away. Unless when `compile` is disabled.
92+
Defaults to `false`. If enabled, fingerprint will be added to asset filenames.
93+
If `config.assets.raise_runtime_errors` is also enabled, requests for assets
94+
will require a fingerprint.
9395

9496
**`config.assets.debug`**
9597

lib/sprockets/rails/environment.rb

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'sprockets'
2+
require 'sprockets/rails/helper'
3+
4+
module Sprockets
5+
module Rails
6+
class Environment < Sprockets::Environment
7+
class NoDigestError < StandardError
8+
def initialize(asset)
9+
msg = "Assets should not be requested directly without their digests: " <<
10+
"Use the helpers in ActionView::Helpers to request #{asset}"
11+
super(msg)
12+
end
13+
end
14+
15+
def call(env)
16+
if Sprockets::Rails::Helper.raise_runtime_errors && context_class.digest_assets
17+
path = unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
18+
19+
if fingerprint = path_fingerprint(path)
20+
path = path.sub("-#{fingerprint}", '')
21+
else
22+
raise NoDigestError.new(path)
23+
end
24+
25+
asset = find_asset(path)
26+
if asset && asset.digest != fingerprint
27+
asset_path = File.join(context_class.assets_prefix || "/", asset.digest_path)
28+
asset_path += '?' + env['QUERY_STRING'] if env['QUERY_STRING']
29+
[302, {"Location" => asset_path}, []]
30+
else
31+
super(env)
32+
end
33+
else
34+
super(env)
35+
end
36+
end
37+
end
38+
end
39+
end

lib/sprockets/railtie.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'action_controller/railtie'
44
require 'active_support/core_ext/module/remove_method'
55
require 'sprockets'
6+
require 'sprockets/rails/environment'
67
require 'sprockets/rails/helper'
78
require 'sprockets/rails/version'
89

@@ -18,9 +19,9 @@ class Configuration
1819
remove_possible_method :assets
1920
remove_possible_method :assets=
2021

21-
# Returns Sprockets::Environment for app config.
22+
# Returns Sprockets::Rails::Environment for app config.
2223
def assets
23-
@assets ||= Sprockets::Environment.new(root.to_s) do |env|
24+
@assets ||= Sprockets::Rails::Environment.new(root.to_s) do |env|
2425
env.version = ::Rails.env
2526

2627
path = "#{config.root}/tmp/cache/assets/#{::Rails.env}"

test/test_environment.rb

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
require 'minitest/autorun'
2+
3+
require 'rack/test'
4+
require 'sprockets/rails/environment'
5+
require 'sprockets/rails/helper'
6+
7+
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
8+
9+
class EnvironmentTest < Minitest::Test
10+
include Rack::Test::Methods
11+
12+
FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
13+
14+
def setup
15+
@assets = Sprockets::Rails::Environment.new
16+
@assets.append_path FIXTURES_PATH
17+
@assets.context_class.class_eval do
18+
include ::Sprockets::Rails::Helper
19+
end
20+
@assets.context_class.assets_prefix = "/assets"
21+
@assets.context_class.digest_assets = true
22+
23+
@foo_js_digest = @assets['foo.js'].digest
24+
25+
Sprockets::Rails::Helper.raise_runtime_errors = true
26+
end
27+
28+
def default_app
29+
env = @assets
30+
31+
Rack::Builder.new do
32+
map "/assets" do
33+
run env
34+
end
35+
end
36+
end
37+
38+
def app
39+
@app ||= default_app
40+
end
41+
end
42+
43+
class DigestTest < EnvironmentTest
44+
def setup
45+
super
46+
@assets.context_class.digest_assets = true
47+
end
48+
49+
def test_assets_with_digest
50+
get "/assets/foo-#{@foo_js_digest}.js"
51+
assert_equal 200, last_response.status
52+
end
53+
54+
def test_assets_with_no_digest
55+
assert_raises(Sprockets::Rails::Environment::NoDigestError) do
56+
get "/assets/foo.js"
57+
end
58+
end
59+
60+
def test_assets_with_wrong_digest
61+
wrong_digest = "0" * 32
62+
get "/assets/foo-#{wrong_digest}.js"
63+
assert_equal 302, last_response.status
64+
65+
follow_redirect!
66+
assert_equal "/assets/foo-#{@foo_js_digest}.js", last_request.path
67+
assert_equal 200, last_response.status
68+
end
69+
70+
def test_assets_with_wrong_digest_with_query_parameters
71+
wrong_digest = "0" * 32
72+
get "/assets/foo-#{wrong_digest}.js?body=1"
73+
assert_equal 302, last_response.status
74+
75+
follow_redirect!
76+
assert_equal "/assets/foo-#{@foo_js_digest}.js", last_request.path
77+
assert_equal "body=1", last_request.query_string
78+
assert_equal 200, last_response.status
79+
end
80+
end
81+
82+
class NoDigestTest < EnvironmentTest
83+
def setup
84+
super
85+
@assets.context_class.digest_assets = false
86+
end
87+
88+
def test_assets_with_digest
89+
get "/assets/foo-#{@foo_js_digest}.js"
90+
assert_equal 200, last_response.status
91+
end
92+
93+
def test_assets_with_no_digest
94+
get "/assets/foo.js"
95+
assert_equal 200, last_response.status
96+
end
97+
end
98+
99+
class NoRuntimeErrorTest < EnvironmentTest
100+
def setup
101+
super
102+
Sprockets::Rails::Helper.raise_runtime_errors = false
103+
end
104+
105+
def test_assets_with_digest
106+
get "/assets/foo-#{@foo_js_digest}.js"
107+
assert_equal 200, last_response.status
108+
end
109+
110+
def test_assets_with_no_digest
111+
get "/assets/foo.js"
112+
assert_equal 200, last_response.status
113+
end
114+
end

test/test_helper.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'action_view'
44
require 'sprockets'
5+
require 'sprockets/rails/environment'
56
require 'sprockets/rails/helper'
67

78
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
@@ -10,7 +11,7 @@ class HelperTest < Minitest::Test
1011
FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
1112

1213
def setup
13-
assets = @assets = Sprockets::Environment.new
14+
assets = @assets = Sprockets::Rails::Environment.new
1415
@assets.append_path FIXTURES_PATH
1516
@assets.context_class.class_eval do
1617
include ::Sprockets::Rails::Helper

test/test_task.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'tmpdir'
33

44
require 'sprockets'
5+
require 'sprockets/rails/environment'
56
require 'sprockets/rails/task'
67

78
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
@@ -13,7 +14,7 @@ def setup
1314
@rake = Rake::Application.new
1415
Rake.application = @rake
1516

16-
@assets = Sprockets::Environment.new
17+
@assets = Sprockets::Rails::Environment.new
1718
@assets.append_path FIXTURES_PATH
1819

1920
@dir = File.join(Dir::tmpdir, 'rails', 'task')

0 commit comments

Comments
 (0)