diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 7fd655991059..d5e3fce13245 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -5573,6 +5573,18 @@ void Document::reset_cursor_blink_cycle() m_cursor_blink_timer->restart(); } +// https://html.spec.whatwg.org/multipage/document-sequences.html#doc-container-document +GC::Ptr Document::container_document() const +{ + // 1. If document's node navigable is null, then return null. + auto node_navigable = navigable(); + if (!node_navigable) + return nullptr; + + // 2. Return document's node navigable's container document. + return node_navigable->container_document(); +} + GC::Ptr Document::cached_navigable() { return m_cached_navigable.ptr(); diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 909be6e0b7b7..9c5661cf37ed 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -757,6 +757,8 @@ class Document bool css_styling_flag() const { return m_css_styling_flag; } void set_css_styling_flag(bool flag) { m_css_styling_flag = flag; } + GC::Ptr container_document() const; + protected: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index 5045fa7ac9a4..3dfc0c415339 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -1165,10 +1165,16 @@ WebIDL::ExceptionOr Navigable::populate_session_history_entry_document( // - navigationParams is null; // - FIXME: the result of should navigation response to navigation request of type in target be blocked by Content Security Policy? given navigationParams's request, navigationParams's response, navigationParams's policy container's CSP list, cspNavigationType, and navigable is "Blocked"; // - FIXME: navigationParams's reserved environment is non-null and the result of checking a navigation response's adherence to its embedder policy given navigationParams's response, navigable, and navigationParams's policy container's embedder policy is false; or - // - FIXME: the result of checking a navigation response's adherence to `X-Frame-Options` given navigationParams's response, navigable, navigationParams's policy container's CSP list, and navigationParams's origin is false, - if (navigation_params.has()) { + // - the result of checking a navigation response's adherence to `X-Frame-Options` given navigationParams's response, navigable, navigationParams's policy container's CSP list, and navigationParams's origin is false, + if (navigation_params.visit( + [](NullOrError) { return true; }, + [this](GC::Ref navigation_params) { + // FIXME: Pass in navigationParams's policy container's CSP list + return !check_a_navigation_responses_adherence_to_x_frame_options(navigation_params->response, this, navigation_params->origin); + }, + [](GC::Ref) { return false; })) { // 1. Set entry's document state's document to the result of creating a document for inline content that doesn't have a DOM, given navigable, null, and navTimingType. The inline content should indicate to the user the sort of error that occurred. - auto error_message = navigation_params.get().value_or("Unknown error"sv); + auto error_message = navigation_params.has() ? navigation_params.get().value_or("Unknown error"sv) : "The request was denied."sv; auto error_html = load_error_page(entry->url(), error_message).release_value_but_fixme_should_propagate_errors(); entry->document_state()->set_document(create_document_for_inline_content(this, navigation_id, [this, error_html](auto& document) { diff --git a/Libraries/LibWeb/HTML/NavigationParams.cpp b/Libraries/LibWeb/HTML/NavigationParams.cpp index 6c535031bade..89dee2c07d07 100644 --- a/Libraries/LibWeb/HTML/NavigationParams.cpp +++ b/Libraries/LibWeb/HTML/NavigationParams.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -30,4 +31,67 @@ void NonFetchSchemeNavigationParams::visit_edges(Visitor& visitor) visitor.visit(navigable); } +// https://html.spec.whatwg.org/multipage/document-lifecycle.html#check-a-navigation-response's-adherence-to-x-frame-options +// FIXME: Add the cspList parameter +bool check_a_navigation_responses_adherence_to_x_frame_options(GC::Ptr response, Navigable* navigable, URL::Origin destination_origin) +{ + // 1. If navigable is not a child navigable, then return true. + if (!navigable->parent()) { + return true; + } + + // FIXME: 2. For each policy of cspList: + // 1. If policy's disposition is not "enforce", then continue. + // 2. If policy's directive set contains a frame-ancestors directive, then return true. + + // 3. Let rawXFrameOptions be the result of getting, decoding, and splitting `X-Frame-Options` from response's header list. + auto raw_x_frame_options = response->header_list()->get_decode_and_split("X-Frame-Options"sv.bytes()); + + // 4. Let xFrameOptions be a new set. + auto x_frame_options = AK::OrderedHashTable(); + + // 5. For each value of rawXFrameOptions, append value, converted to ASCII lowercase, to xFrameOptions. + if (raw_x_frame_options.has_value()) { + for (auto const& value : raw_x_frame_options.value()) { + x_frame_options.set(value.to_ascii_lowercase()); + } + } + + // 6. If xFrameOptions's size is greater than 1, and xFrameOptions contains any of "deny", "allowall", or "sameorigin", then return false. + if (x_frame_options.size() > 1 && (x_frame_options.contains("deny"sv) || x_frame_options.contains("allowall"sv) || x_frame_options.contains("sameorigin"sv))) { + return false; + } + + // 7. If xFrameOptions's size is greater than 1, then return true. + if (x_frame_options.size() > 1) { + return true; + } + + // 8. If xFrameOptions[0] is "deny", then return false. + auto first_x_frame_option = x_frame_options.begin(); + if (!x_frame_options.is_empty() && *first_x_frame_option == "deny"sv) { + return false; + } + + // 9. If xFrameOptions[0] is "sameorigin", then: + if (!x_frame_options.is_empty() && *first_x_frame_option == "sameorigin"sv) { + // 1. Let containerDocument be navigable's container document. + auto container_document = navigable->container_document(); + + // 2. While containerDocument is not null: + while (container_document) { + // 1. If containerDocument's origin is not same origin with destinationOrigin, then return false. + if (!container_document->origin().is_same_origin(destination_origin)) { + return false; + } + + // 2. Set containerDocument to containerDocument's container document. + container_document = container_document->container_document(); + } + } + + // 10. Return true. + return true; +} + } diff --git a/Libraries/LibWeb/HTML/NavigationParams.h b/Libraries/LibWeb/HTML/NavigationParams.h index 0151bba9f2b1..086261eb7c3c 100644 --- a/Libraries/LibWeb/HTML/NavigationParams.h +++ b/Libraries/LibWeb/HTML/NavigationParams.h @@ -97,4 +97,5 @@ struct NonFetchSchemeNavigationParams : JS::Cell { void visit_edges(Visitor& visitor) override; }; +bool check_a_navigation_responses_adherence_to_x_frame_options(GC::Ptr response, Navigable* navigable, URL::Origin destination_origin); }