From 95da08e035389eb3f316ea281c6b0240fb381732 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Wed, 22 Mar 2023 21:32:56 +0000 Subject: [PATCH] LibWeb: Add workaround to restore cross-origin stylesheet functionality CORS cross-origin responses in the No CORS request mode provide an opaque filtered response, which is the original response with certain attributes removed/changed. The relevant effect it has is setting the body to `null`, which means `body_bytes` has `Empty` in the process_response_consume_body callback. This effectively disables cross-origin linked resources (e.g. stylesheets). However, the web actually depends on this, especially for stylesheets retrieved from a cross-origin CDN. For example, Shopify websites request stylesheets from `cdn.shopify.com` and Substack websites request stylesheets from `substackcdn.com`. This makes this a specification bug, as this code was written from it. The workaround is to read the actual body from the unfiltered response and then call `process_linked_resource` from there. This _should_ be safe to do, as linked resource fetches do not include credentials (i.e. cookies and the Authorization header), so it cannot provide personalized responses. --- .../Libraries/LibWeb/HTML/HTMLLinkElement.cpp | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index 217ebdbde38..58a17ab15f1 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -294,9 +294,49 @@ void HTMLLinkElement::default_fetch_and_process_linked_resource() // 2. If either of the following conditions are met: // - bodyBytes is null or failure; or // - response's status is not an ok status, + // then set success to false. // NOTE: content-specific errors, e.g., CSS parse errors or PNG decoding errors, do not affect success. - if (body_bytes.template has() || body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { - // then set success to false. + if (body_bytes.template has()) { + // CORS cross-origin responses in the No CORS request mode provide an opaque filtered response, which is the original response + // with certain attributes removed/changed. + + // The relevant effect it has is setting the body to `null`, which means `body_bytes` has `Empty` here. This effectively + // disables cross-origin linked resources (e.g. stylesheets). + + // However, the web actually depends on this, especially for stylesheets retrieved from a cross-origin CDN. For example, + // Shopify websites request stylesheets from `cdn.shopify.com` and Substack websites request stylesheets from `substackcdn.com`. + + // This makes this a specification bug, as this code was written from it. + + // The workaround is to read the actual body from the unfiltered response and then call `process_linked_resource` from there. + + // This _should_ be safe to do, as linked resource fetches do not include credentials (i.e. cookies and the Authorization + // header), so it cannot provide personalized responses. + + // FIXME: Replace this workaround with a proper fix that has landed in the specification. + // See: https://github.com/whatwg/html/issues/9066 + if (is(response.ptr())) { + auto unsafe_response = static_cast(*response).internal_response(); + if (unsafe_response->body().has_value()) { + // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction. + auto process_body = [this, unsafe_response](ByteBuffer bytes) { + process_linked_resource(true, unsafe_response, bytes); + }; + + // NOTE: `this` and `unsafe_response` are protected by `fully_read` using JS::SafeFunction. + auto process_body_error = [this, unsafe_response](auto&) { + process_linked_resource(false, unsafe_response, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag {}); + }; + + unsafe_response->body()->fully_read(realm(), move(process_body), move(process_body_error), JS::NonnullGCPtr { realm().global_object() }).release_value_but_fixme_should_propagate_errors(); + return; + } else { + success = false; + } + } else { + success = false; + } + } else if (body_bytes.template has() || !Fetch::Infrastructure::is_ok_status(response->status())) { success = false; } // FIXME: 3. Otherwise, wait for the link resource's critical subresources to finish loading.