Skip to content

Commit 56493da

Browse files
committed
Fix fingerprint fallback when filename collision
This is a fix for: rails#749 The bug is when you have two assets in the same path where one looks like the fingerprint-stripped version of the other, e.g. “landing.png” and “landing-splashimage.png”. In this case, if landing-splashimage.png is requested, a 404 is returned rather than the asset. The old behavior: The code had logic to first strip anything that looked like a fingerprint from the filename. Then, if the asset was not found with the stripped path, it would attempt to find it with the full_path. This “fallback” was to handle the case where the code mistakenly thought a filename with a dash actually had a fingerprint. But, if the stripped path had already matched a different asset, then a 404 would be returned since the matched asset’s etag wouldn’t match the fingerprint. The new behavior: This PR first looks up the asset using the original path. Only in the case where the asset is not found does the code strip what might be a fingerprint from the path and re-lookup. This new behavior fixes the bug and I think is more true to the spirit of what this code should be doing. Other changes / side effects: In the case where both the original asset and the fingerprinted asset exist, the new code returns the fingerprinted-asset while the old code returns the original asset. This should never result in any actual difference of bytes served. I think this is worth fixing because if the bug is encountered, it can be deeply confusing to the uninitiated and could waste hours of time.
1 parent 5b040f3 commit 56493da

File tree

1 file changed

+8
-22
lines changed

1 file changed

+8
-22
lines changed

lib/sprockets/server.rb

+8-22
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,20 @@ def call(env)
4848
full_path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
4949
path = full_path
5050

51-
unless path.valid_encoding?
52-
return bad_request_response(env)
53-
end
51+
return bad_request_response(env) unless path.valid_encoding?
52+
return forbidden_response(env) if forbidden_request?(path)
53+
54+
asset = find_asset(path)
5455

5556
# Strip fingerprint
56-
if fingerprint = path_fingerprint(path)
57+
if asset.nil? && fingerprint = path_fingerprint(path)
5758
path = path.sub("-#{fingerprint}", '')
58-
end
59-
60-
# URLs containing a `".."` are rejected for security reasons.
61-
if forbidden_request?(path)
62-
return forbidden_response(env)
59+
return forbidden_response(env) if forbidden_request?(path)
60+
asset = find_asset(path)
6361
end
6462

6563
if fingerprint
66-
if_match = fingerprint
64+
if_match = asset&.etag
6765
elsif env['HTTP_IF_MATCH']
6866
if_match = env['HTTP_IF_MATCH'][/"(\w+)"$/, 1]
6967
end
@@ -72,18 +70,6 @@ def call(env)
7270
if_none_match = env['HTTP_IF_NONE_MATCH'][/"(\w+)"$/, 1]
7371
end
7472

75-
# Look up the asset.
76-
asset = find_asset(path)
77-
78-
# Fallback to looking up the asset with the full path.
79-
# This will make assets that are hashed with webpack or
80-
# other js bundlers work consistently between production
81-
# and development pipelines.
82-
if asset.nil? && (asset = find_asset(full_path))
83-
if_match = asset.etag if fingerprint
84-
fingerprint = asset.etag
85-
end
86-
8773
if asset.nil?
8874
status = :not_found
8975
elsif fingerprint && asset.etag != fingerprint

0 commit comments

Comments
 (0)