Skip to content

Commit

Permalink
LibWeb: Support the X-Frame-Options header
Browse files Browse the repository at this point in the history
Navigation responses are now checked for adherence to the
`X-Frame-Options` header and an error is shown accordingly.
  • Loading branch information
skyz1 authored and tcl3 committed Dec 7, 2024
1 parent 88884c3 commit 156f9ff
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 3 deletions.
12 changes: 12 additions & 0 deletions Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DOM::Document> 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<HTML::Navigable> Document::cached_navigable()
{
return m_cached_navigable.ptr();
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/DOM/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<DOM::Document> container_document() const;

protected:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
Expand Down
12 changes: 9 additions & 3 deletions Libraries/LibWeb/HTML/Navigable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1165,10 +1165,16 @@ WebIDL::ExceptionOr<void> 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<NullOrError>()) {
// - 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<NavigationParams> 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<NonFetchSchemeNavigationParams>) { 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<NullOrError>().value_or("Unknown error"sv);
auto error_message = navigation_params.has<NullOrError>() ? navigation_params.get<NullOrError>().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) {
Expand Down
64 changes: 64 additions & 0 deletions Libraries/LibWeb/HTML/NavigationParams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibWeb/DOM/Document.h>
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/HTML/NavigationParams.h>
Expand All @@ -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<Fetch::Infrastructure::Response> 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<String>();

// 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;
}

}
1 change: 1 addition & 0 deletions Libraries/LibWeb/HTML/NavigationParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fetch::Infrastructure::Response> response, Navigable* navigable, URL::Origin destination_origin);
}

0 comments on commit 156f9ff

Please sign in to comment.