Browse Source

LibWeb: Decode linked style sheets before parsing them

This fixes an issue where a BOM at the head of a style sheet would be
passed verbatim to the parser, who would then interpret it as an ident
token and (after some confusion) fail to parse the first rule, but then
carry on with the rest of the sheet.
Andreas Kling 2 years ago
parent
commit
fb727332f9

+ 1 - 0
Tests/LibWeb/Text/expected/css/style-sheet-with-byte-order-mark.txt

@@ -0,0 +1 @@
+PASS 

+ 7 - 0
Tests/LibWeb/Text/input/css/style-sheet-with-byte-order-mark.css

@@ -0,0 +1,7 @@
+.pass {
+    display: block;
+}
+
+div {
+    display: none;
+}

+ 1 - 0
Tests/LibWeb/Text/input/css/style-sheet-with-byte-order-mark.html

@@ -0,0 +1 @@
+<head><link rel="stylesheet" href="style-sheet-with-byte-order-mark.css"></head><div class="pass">PASS

+ 33 - 9
Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp

@@ -10,6 +10,7 @@
 #include <AK/ByteBuffer.h>
 #include <AK/Debug.h>
 #include <AK/URL.h>
+#include <LibTextCodec/Decoder.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Event.h>
@@ -341,18 +342,41 @@ void HTMLLinkElement::process_stylesheet_resource(bool success, Fetch::Infrastru
         // The CSS environment encoding is the result of running the following steps: [CSSSYNTAX]
         //     1. If the element has a charset attribute, get an encoding from that attribute's value. If that succeeds, return the resulting encoding. [ENCODING]
         //     2. Otherwise, return the document's character encoding. [DOM]
-        m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), *response.url()), body_bytes.template get<ByteBuffer>());
 
-        if (m_loaded_style_sheet) {
-            m_loaded_style_sheet->set_owner_node(this);
-            m_loaded_style_sheet->set_media(attribute(HTML::AttributeNames::media));
-            document().style_sheets().add_sheet(*m_loaded_style_sheet);
+        DeprecatedString encoding;
+        if (auto charset = attribute(HTML::AttributeNames::charset); !charset.is_null())
+            encoding = charset;
+        else
+            encoding = document().encoding_or_default();
+
+        auto decoder = TextCodec::decoder_for(encoding);
+
+        if (!decoder.has_value()) {
+            // If we don't support the encoding yet, let's error out instead of trying to decode it as something it's most likely not.
+            dbgln("FIXME: Style sheet encoding '{}' is not supported yet", encoding);
+            dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
         } else {
-            dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Failed to parse stylesheet: {}", resource()->url());
+            auto const& encoded_string = body_bytes.get<ByteBuffer>();
+            auto maybe_decoded_string = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, encoded_string);
+            if (maybe_decoded_string.is_error()) {
+                dbgln("Style sheet {} claimed to be '{}' but decoding failed", response.url().value_or(AK::URL()), encoding);
+                dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::error).release_value_but_fixme_should_propagate_errors());
+            } else {
+                auto const decoded_string = maybe_decoded_string.release_value();
+                m_loaded_style_sheet = parse_css_stylesheet(CSS::Parser::ParsingContext(document(), *response.url()), decoded_string);
+
+                if (m_loaded_style_sheet) {
+                    m_loaded_style_sheet->set_owner_node(this);
+                    m_loaded_style_sheet->set_media(attribute(HTML::AttributeNames::media));
+                    document().style_sheets().add_sheet(*m_loaded_style_sheet);
+                } else {
+                    dbgln_if(CSS_LOADER_DEBUG, "HTMLLinkElement: Failed to parse stylesheet: {}", resource()->url());
+                }
+
+                // 2. Fire an event named load at el.
+                dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
+            }
         }
-
-        // 2. Fire an event named load at el.
-        dispatch_event(*DOM::Event::create(realm(), HTML::EventNames::load).release_value_but_fixme_should_propagate_errors());
     }
     // 5. Otherwise, fire an event named error at el.
     else {