From 4aa91262f050bb3fe5241f6cb1f736cf432e8975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 14 Dec 2020 15:02:57 -0500 Subject: [PATCH] Link preloading should keep integrity hashes in the header When a stylesheet or javascript link tag (or preload link tag) is output in the view, it also gets sent as a Link header for preloading. This Link header is missing the integrity attribute even when one is set on the tag, which prevents the browser from using the preloaded resource. Co-authored-by: Adrianna Chang --- actionview/lib/action_view/helpers/asset_tag_helper.rb | 7 +++++++ actionview/test/template/asset_tag_helper_test.rb | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 488542e0e7b..7c97badc89d 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -90,12 +90,14 @@ module ActionView nopush = options["nopush"].nil? ? true : options.delete("nopush") crossorigin = options.delete("crossorigin") crossorigin = "anonymous" if crossorigin == true + integrity = options["integrity"] sources_tags = sources.uniq.map { |source| href = path_to_javascript(source, path_options) unless options["defer"] preload_link = "<#{href}>; rel=preload; as=script" preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? + preload_link += "; integrity=#{integrity}" unless integrity.nil? preload_link += "; nopush" if nopush preload_links << preload_link end @@ -149,11 +151,13 @@ module ActionView crossorigin = options.delete("crossorigin") crossorigin = "anonymous" if crossorigin == true nopush = options["nopush"].nil? ? true : options.delete("nopush") + integrity = options["integrity"] sources_tags = sources.uniq.map { |source| href = path_to_stylesheet(source, path_options) preload_link = "<#{href}>; rel=preload; as=style" preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? + preload_link += "; integrity=#{integrity}" unless integrity.nil? preload_link += "; nopush" if nopush preload_links << preload_link tag_options = { @@ -256,6 +260,7 @@ module ActionView # * :as - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type. # * :crossorigin - Specify the crossorigin attribute, required to load cross-origin resources. # * :nopush - Specify if the use of server push is not desired for the resource. Defaults to +false+. + # * :integrity - Specify the integrity attribute. # # ==== Examples # @@ -287,6 +292,7 @@ module ActionView as_type = options.delete(:as) || resolve_link_as(extname, mime_type) crossorigin = options.delete(:crossorigin) crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font") + integrity = options[:integrity] nopush = options.delete(:nopush) || false link_tag = tag.link(**{ @@ -300,6 +306,7 @@ module ActionView preload_link = "<#{href}>; rel=preload; as=#{as_type}" preload_link += "; type=#{mime_type}" if mime_type preload_link += "; crossorigin=#{crossorigin}" if crossorigin + preload_link += "; integrity=#{integrity}" if integrity preload_link += "; nopush" if nopush send_preload_links_header([preload_link]) diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 9c2ee5093d4..a419078be38 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -239,7 +239,8 @@ class AssetTagHelperTest < ActionView::TestCase %(preload_link_tag '//example.com/map?callback=initMap', as: 'fetch', type: 'application/javascript') => %(), %(preload_link_tag '//example.com/font.woff2') => %(), %(preload_link_tag '//example.com/font.woff2', crossorigin: 'use-credentials') => %(), - %(preload_link_tag '/media/audio.ogg', nopush: true) => %() + %(preload_link_tag '/media/audio.ogg', nopush: true) => %(), + %(preload_link_tag '/style.css', integrity: 'sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs') => %(), } VideoPathToTag = { @@ -535,6 +536,13 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal expected, @response.headers["Link"] end + def test_should_set_preload_links_with_integrity_hashes + stylesheet_link_tag("http://example.com/style.css", integrity: "sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs") + javascript_include_tag("http://example.com/all.js", integrity: "sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs") + expected = "; rel=preload; as=style; integrity=sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs; nopush,; rel=preload; as=script; integrity=sha256-AbpHGcgLb+kRsJGnwFEktk7uzpZOCcBY74+YBdrKVGs; nopush" + assert_equal expected, @response.headers["Link"] + end + def test_image_path ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end