From 2cb25ab35def9b3b9b605dee7ac9201627bec9c2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 23 Apr 2021 08:50:00 +0200 Subject: [PATCH] Make sure not to generate Link headers longer than 8kiB --- .../action_view/helpers/asset_tag_helper.rb | 31 +++++++++++++++++-- .../test/template/asset_tag_helper_test.rb | 11 +++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index b1efedccf6..4906654df2 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -521,13 +521,40 @@ module ActionView end end - def send_preload_links_header(preload_links) + MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit + def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE) if respond_to?(:request) && request request.send_early_hints("Link" => preload_links.join("\n")) end if respond_to?(:response) && response - response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",") + header = response.headers["Link"] + header = header ? header.dup : +"" + + # rindex count characters not bytes, but we assume non-ascii characters + # are rare in urls, and we have a 192 bytes margin. + last_line_offset = header.rindex("\n") + last_line_size = if last_line_offset + header.bytesize - last_line_offset + else + header.bytesize + end + + preload_links.each do |link| + if link.bytesize + last_line_size + 1 < max_header_size + unless header.empty? + header << "," + last_line_size += 1 + end + else + header << "\n" + last_line_size = 0 + end + header << link + last_line_size += link.bytesize + end + + response.headers["Link"] = header end end end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index a46074f3e6..7ac7b6c9d5 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -529,6 +529,17 @@ class AssetTagHelperTest < ActionView::TestCase end end + def test_should_generate_links_under_the_max_size + with_preload_links_header do + 100.times do |i| + stylesheet_link_tag("http://example.com/style.css?#{i}") + javascript_include_tag("http://example.com/all.js?#{i}") + end + lines = @response.headers["Link"].split("\n") + assert_equal 2, lines.size + end + end + def test_should_not_preload_links_with_defer with_preload_links_header do javascript_include_tag("http://example.com/all.js", defer: true)