Skip to content

Commit 86fa5dc

Browse files
committed
Add experimental support for SRI
1 parent c56e8fa commit 86fa5dc

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ The following plugins provide some extras for the Sprockets Asset Pipeline.
128128
* `config.assets.manifest` (if used) must now include the manifest filename, e.g. `Rails.root.join('config/manifest.json')`. It cannot be a directory.
129129
* Two cleanup tasks. `rake assets:clean` is now a safe cleanup that only removes older assets that are no longer used. While `rake assets:clobber` nukes the entire `public/assets` directory and clears your filesystem cache. The clean task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
130130

131+
## Experimental
132+
133+
### [SRI](http://www.w3.org/TR/SRI/) support
134+
135+
Sprockets 3.x adds experimental support for subresource integrity checks. The spec is still evolving and the API may change in backwards incompatible ways.
136+
137+
``` ruby
138+
javascript_include_tag :application, integrity: true
139+
# => "<script src="/assets/application.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>"
140+
```
141+
131142

132143
## Contributing
133144

lib/sprockets/rails/helper.rb

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,45 @@ def asset_digest_path(path, options = {})
8989
end
9090
end
9191

92+
# Experimental: Get integrity for asset path.
93+
#
94+
# path - String path
95+
# options - Hash options
96+
#
97+
# Returns String integrity attribute or nil if no asset was found.
98+
def asset_integrity(path, options = {})
99+
path = path.to_s
100+
if extname = compute_asset_extname(path, options)
101+
path = "#{path}#{extname}"
102+
end
103+
104+
if manifest = assets_manifest
105+
if digest_path = manifest.assets[path]
106+
if metadata = manifest.files[digest_path]
107+
return metadata["integrity"]
108+
end
109+
end
110+
end
111+
112+
if environment = assets_environment
113+
if asset = environment[path]
114+
return asset.integrity
115+
end
116+
end
117+
118+
nil
119+
end
120+
92121
# Override javascript tag helper to provide debugging support.
93122
#
94123
# Eventually will be deprecated and replaced by source maps.
95124
def javascript_include_tag(*sources)
96125
options = sources.extract_options!.stringify_keys
97126

127+
if options["integrity"] == true
128+
compute_integrity = options.delete("integrity")
129+
end
130+
98131
if options["debug"] != false && request_debug_assets?
99132
sources.map { |source|
100133
if asset = lookup_asset_for_path(source, :type => :javascript)
@@ -106,8 +139,11 @@ def javascript_include_tag(*sources)
106139
end
107140
}.flatten.uniq.join("\n").html_safe
108141
else
109-
sources.push(options)
110-
super(*sources)
142+
sources.map { |source|
143+
super(source, compute_integrity ?
144+
options.merge("integrity" => asset_integrity(source, :type => :javascript)) :
145+
options)
146+
}.join("\n").html_safe
111147
end
112148
end
113149

@@ -116,6 +152,11 @@ def javascript_include_tag(*sources)
116152
# Eventually will be deprecated and replaced by source maps.
117153
def stylesheet_link_tag(*sources)
118154
options = sources.extract_options!.stringify_keys
155+
156+
if options["integrity"] == true
157+
compute_integrity = options.delete("integrity")
158+
end
159+
119160
if options["debug"] != false && request_debug_assets?
120161
sources.map { |source|
121162
if asset = lookup_asset_for_path(source, :type => :stylesheet)
@@ -127,8 +168,11 @@ def stylesheet_link_tag(*sources)
127168
end
128169
}.flatten.uniq.join("\n").html_safe
129170
else
130-
sources.push(options)
131-
super(*sources)
171+
sources.map { |source|
172+
super(source, compute_integrity ?
173+
options.merge("integrity" => asset_integrity(source, :type => :stylesheet)) :
174+
options)
175+
}.join("\n").html_safe
132176
end
133177
end
134178

test/fixtures/bar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
//= require foo
2+
var Bar;

test/test_helper.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ def setup
4949
def test_truth
5050
end
5151

52+
def test_foo_and_bar_different_digests
53+
refute_equal @foo_js_digest, @bar_js_digest
54+
refute_equal @foo_css_digest, @bar_css_digest
55+
end
56+
5257
def assert_servable_asset_url(url)
5358
path, query = url.split("?", 2)
5459
path = path.sub(@view.assets_prefix, "")
@@ -115,6 +120,22 @@ def test_stylesheet_link_tag
115120
@view.stylesheet_link_tag("print", :media => "<hax>")
116121
end
117122

123+
def test_javascript_include_tag_integrity
124+
assert_dom_equal %(<script src="/javascripts/static.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
125+
@view.javascript_include_tag("static", integrity: "ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript")
126+
127+
assert_dom_equal %(<script src="/javascripts/static.js"></script>),
128+
@view.javascript_include_tag("static", integrity: true)
129+
end
130+
131+
def test_stylesheet_link_tag_integrity
132+
assert_dom_equal %(<link href="/stylesheets/static.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
133+
@view.stylesheet_link_tag("static", integrity: "ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css")
134+
135+
assert_dom_equal %(<link href="/stylesheets/static.css" media="screen" rel="stylesheet" />),
136+
@view.stylesheet_link_tag("static", integrity: true)
137+
end
138+
118139
def test_javascript_path
119140
assert_equal "/javascripts/xmlhr.js", @view.javascript_path("xmlhr")
120141
assert_equal "/javascripts/xmlhr.js", @view.javascript_path("xmlhr.js")
@@ -266,6 +287,9 @@ def test_javascript_include_tag
266287
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js"></script>),
267288
@view.javascript_include_tag(:foo)
268289

290+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js"></script>\n<script src="/assets/bar-#{@bar_js_digest}.js"></script>),
291+
@view.javascript_include_tag(:foo, :bar)
292+
269293
assert_servable_asset_url "/assets/foo-#{@foo_js_digest}.js"
270294
end
271295

@@ -279,9 +303,40 @@ def test_stylesheet_link_tag
279303
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" />),
280304
@view.stylesheet_link_tag(:foo)
281305

306+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" />\n<link href="/assets/bar-#{@bar_css_digest}.css" media="screen" rel="stylesheet" />),
307+
@view.stylesheet_link_tag(:foo, :bar)
308+
282309
assert_servable_asset_url "/assets/foo-#{@foo_css_digest}.css"
283310
end
284311

312+
def test_javascript_include_tag_integrity
313+
super
314+
315+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
316+
@view.javascript_include_tag("foo", integrity: true)
317+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
318+
@view.javascript_include_tag("foo.js", integrity: true)
319+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
320+
@view.javascript_include_tag(:foo, integrity: true)
321+
322+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>\n<script src="/assets/bar-#{@bar_js_digest}.js" integrity="ni:///sha-256;g0JYFeYSYGXe376R0JrRzS6CpYpC1HiqtwBsVt_XAWU?ct=application/javascript"></script>),
323+
@view.javascript_include_tag(:foo, :bar, integrity: true)
324+
end
325+
326+
def test_stylesheet_link_tag_integrity
327+
super
328+
329+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
330+
@view.stylesheet_link_tag("foo", integrity: true)
331+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
332+
@view.stylesheet_link_tag("foo.css", integrity: true)
333+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
334+
@view.stylesheet_link_tag(:foo, integrity: true)
335+
336+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />\n<link href="/assets/bar-#{@bar_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;Vd370-VAW4D96CVpZcjFLXyeHoagI0VHwofmzRXetuE?ct=text/css" />),
337+
@view.stylesheet_link_tag(:foo, :bar, integrity: true)
338+
end
339+
285340
def test_javascript_path
286341
super
287342

@@ -437,6 +492,13 @@ def setup
437492
@manifest.assets["foo.js"] = "foo-#{@foo_js_digest}.js"
438493
@manifest.assets["foo.css"] = "foo-#{@foo_css_digest}.css"
439494

495+
@manifest.files["foo-#{@foo_js_digest}.js"] = {
496+
"integrity" => "ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"
497+
}
498+
@manifest.files["foo-#{@foo_css_digest}.css"] = {
499+
"integrity" => "ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css"
500+
}
501+
440502
@view.digest_assets = true
441503
@view.assets_environment = nil
442504
@view.assets_manifest = @manifest
@@ -464,6 +526,28 @@ def test_stylesheet_link_tag
464526
@view.stylesheet_link_tag(:foo)
465527
end
466528

529+
def test_javascript_include_tag_integrity
530+
super
531+
532+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
533+
@view.javascript_include_tag("foo", integrity: true)
534+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
535+
@view.javascript_include_tag("foo.js", integrity: true)
536+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="ni:///sha-256;TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs?ct=application/javascript"></script>),
537+
@view.javascript_include_tag(:foo, integrity: true)
538+
end
539+
540+
def test_stylesheet_link_tag_integrity
541+
super
542+
543+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
544+
@view.stylesheet_link_tag("foo", integrity: true)
545+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
546+
@view.stylesheet_link_tag("foo.css", integrity: true)
547+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="ni:///sha-256;5YzTQPuOJz_EpeXfN_-v1sxsjAj_dw8q26abiHZM3A4?ct=text/css" />),
548+
@view.stylesheet_link_tag(:foo, integrity: true)
549+
end
550+
467551
def test_javascript_path
468552
super
469553

@@ -492,6 +576,12 @@ def setup
492576

493577
@view.debug_assets = true
494578
end
579+
580+
def test_javascript_include_tag_integrity
581+
end
582+
583+
def test_stylesheet_link_tag_integrity
584+
end
495585
end
496586

497587
class AssetUrlHelperLinksTarget < HelperTest

0 commit comments

Comments
 (0)