Procházet zdrojové kódy

LibWeb: Implement the document.cookie setter/getter according to spec

This ensures we cannot set or get cookies on non-HTTP(S) origins. Since
this would prevent our ability to test cookies during LibWeb tests, this
also adds an internals API to allow cookie access on file:// URLs.
Timothy Flynn před 9 měsíci
rodič
revize
527218da19

+ 1 - 0
Tests/LibWeb/Text/expected/cookie.txt

@@ -1,3 +1,4 @@
+Cookie averse test: ""
 Basic test: "cookie=value"
 Multiple cookies: "cookie1=value1; cookie2=value2; cookie3=value3"
 Nameless cookie: "value"

+ 9 - 0
Tests/LibWeb/Text/input/cookie.html

@@ -12,6 +12,11 @@
         document.cookie = `${name}=""; max-age=-9999`;
     };
 
+    const cookieAverseTest = () => {
+        document.cookie = "cookie=value";
+        printCookies("Cookie averse test");
+    };
+
     const basicTest = () => {
         document.cookie = "cookie=value";
         printCookies("Basic test");
@@ -178,6 +183,10 @@
     };
 
     test(() => {
+        cookieAverseTest();
+
+        internals.enableCookiesOnFileDomains();
+
         basicTest();
         multipleCookiesTest();
 

+ 46 - 6
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -2492,18 +2492,58 @@ void Document::completely_finish_loading()
     }
 }
 
-String Document::cookie(Cookie::Source source)
+// https://html.spec.whatwg.org/#dom-document-cookie
+WebIDL::ExceptionOr<String> Document::cookie(Cookie::Source source)
 {
+    // On getting, if the document is a cookie-averse Document object, then the user agent must return the empty string.
+    if (is_cookie_averse())
+        return String {};
+
+    // Otherwise, if the Document's origin is an opaque origin, the user agent must throw a "SecurityError" DOMException.
+    if (origin().is_opaque())
+        return WebIDL::SecurityError::create(realm(), "Document origin is opaque"_string);
+
+    // Otherwise, the user agent must return the cookie-string for the document's URL for a "non-HTTP" API, decoded using
+    // UTF-8 decode without BOM.
     return page().client().page_did_request_cookie(m_url, source);
 }
 
-void Document::set_cookie(StringView cookie_string, Cookie::Source source)
+// https://html.spec.whatwg.org/#dom-document-cookie
+WebIDL::ExceptionOr<void> Document::set_cookie(StringView cookie_string, Cookie::Source source)
 {
-    auto cookie = Cookie::parse_cookie(url(), cookie_string);
-    if (!cookie.has_value())
-        return;
+    // On setting, if the document is a cookie-averse Document object, then the user agent must do nothing.
+    if (is_cookie_averse())
+        return {};
+
+    // Otherwise, if the Document's origin is an opaque origin, the user agent must throw a "SecurityError" DOMException.
+    if (origin().is_opaque())
+        return WebIDL::SecurityError::create(realm(), "Document origin is opaque"_string);
+
+    // Otherwise, the user agent must act as it would when receiving a set-cookie-string for the document's URL via a
+    // "non-HTTP" API, consisting of the new value encoded as UTF-8.
+    if (auto cookie = Cookie::parse_cookie(url(), cookie_string); cookie.has_value())
+        page().client().page_did_set_cookie(m_url, cookie.value(), source);
+
+    return {};
+}
 
-    page().client().page_did_set_cookie(m_url, cookie.value(), source);
+// https://html.spec.whatwg.org/#cookie-averse-document-object
+bool Document::is_cookie_averse() const
+{
+    // A Document object that falls into one of the following conditions is a cookie-averse Document object:
+
+    // * A Document object whose browsing context is null.
+    if (!browsing_context())
+        return true;
+
+    // * A Document whose URL's scheme is not an HTTP(S) scheme.
+    if (!url().scheme().is_one_of("http"sv, "https"sv)) {
+        // AD-HOC: This allows us to write cookie integration tests.
+        if (!m_enable_cookies_on_file_domains || url().scheme() != "file"sv)
+            return true;
+    }
+
+    return false;
 }
 
 String Document::fg_color() const

+ 6 - 2
Userland/Libraries/LibWeb/DOM/Document.h

@@ -116,8 +116,10 @@ public:
 
     JS::GCPtr<Selection::Selection> get_selection() const;
 
-    String cookie(Cookie::Source = Cookie::Source::NonHttp);
-    void set_cookie(StringView, Cookie::Source = Cookie::Source::NonHttp);
+    WebIDL::ExceptionOr<String> cookie(Cookie::Source = Cookie::Source::NonHttp);
+    WebIDL::ExceptionOr<void> set_cookie(StringView, Cookie::Source = Cookie::Source::NonHttp);
+    bool is_cookie_averse() const;
+    void enable_cookies_on_file_domains(Badge<Internals::Internals>) { m_enable_cookies_on_file_domains = true; }
 
     String fg_color() const;
     void set_fg_color(String const&);
@@ -1020,6 +1022,8 @@ private:
 
     bool m_needs_repaint { false };
 
+    bool m_enable_cookies_on_file_domains { false };
+
     Optional<PaintConfig> m_cached_display_list_paint_config;
     RefPtr<Painting::DisplayList> m_cached_display_list;
 

+ 5 - 0
Userland/Libraries/LibWeb/Internals/Internals.cpp

@@ -183,6 +183,11 @@ void Internals::simulate_drop(double x, double y)
     page.handle_drag_and_drop_event(DragEvent::Type::Drop, position, position, UIEvents::MouseButton::Primary, 0, 0, {});
 }
 
+void Internals::enable_cookies_on_file_domains()
+{
+    internals_window().associated_document().enable_cookies_on_file_domains({});
+}
+
 void Internals::expire_cookies_with_time_offset(WebIDL::LongLong seconds)
 {
     internals_page().client().page_did_expire_cookies_with_time_offset(AK::Duration::from_seconds(seconds));

+ 1 - 0
Userland/Libraries/LibWeb/Internals/Internals.h

@@ -44,6 +44,7 @@ public:
     void simulate_drag_move(double x, double y);
     void simulate_drop(double x, double y);
 
+    void enable_cookies_on_file_domains();
     void expire_cookies_with_time_offset(WebIDL::LongLong seconds);
 
 private:

+ 1 - 0
Userland/Libraries/LibWeb/Internals/Internals.idl

@@ -34,5 +34,6 @@ interface Internals {
     undefined simulateDragMove(double x, double y);
     undefined simulateDrop(double x, double y);
 
+    undefined enableCookiesOnFileDomains();
     undefined expireCookiesWithTimeOffset(long long seconds);
 };