script/dom/security/
xframeoptions.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use content_security_policy::{CspList, PolicyDisposition};
6use http::header::HeaderMap;
7use hyper_serde::Serde;
8use net_traits::fetch::headers::get_decode_and_split_header_name;
9use rustc_hash::FxHashSet;
10use servo_url::MutableOrigin;
11
12use crate::dom::node::NodeTraits;
13use crate::dom::window::Window;
14
15/// <https://html.spec.whatwg.org/multipage/#check-a-navigation-response%027s-adherence-to-x-frame-options>
16pub(crate) fn check_a_navigation_response_adherence_to_x_frame_options(
17    window: &Window,
18    csp_list: Option<&CspList>,
19    destination_origin: &MutableOrigin,
20    headers: Option<&Serde<HeaderMap>>,
21) -> bool {
22    // Step 1. If navigable is not a child navigable, then return true.
23    if window.window_proxy().parent().is_none() {
24        return true;
25    }
26    // Step 2. For each policy of cspList:
27    if let Some(csp_list) = csp_list {
28        for policy in csp_list.0.iter() {
29            // Step 2.1. If policy's disposition is not "enforce", then continue.
30            if policy.disposition != PolicyDisposition::Enforce {
31                continue;
32            }
33            // Step 2.2. If policy's directive set contains a frame-ancestors directive, then return true.
34            if policy.contains_a_directive_whose_name_is("frame-ancestors") {
35                return true;
36            }
37        }
38    }
39    // Step 3. Let rawXFrameOptions be the result of getting, decoding,
40    // and splitting `X-Frame-Options` from response's header list.
41    let Some(headers) = headers else {
42        return true;
43    };
44    let Some(raw_xframe_options) = get_decode_and_split_header_name("X-Frame-Options", headers)
45    else {
46        return true;
47    };
48    // Step 4. Let xFrameOptions be a new set.
49    // Step 5. For each value of rawXFrameOptions, append value, converted to ASCII lowercase, to xFrameOptions.
50    let x_frame_options =
51        FxHashSet::from_iter(raw_xframe_options.iter().map(|value| value.to_lowercase()));
52    // Step 6. If xFrameOptions's size is greater than 1,
53    // and xFrameOptions contains any of "deny", "allowall", or "sameorigin", then return false.
54    if x_frame_options.len() > 1 &&
55        x_frame_options
56            .iter()
57            .any(|value| value == "deny" || value == "allowall" || value == "sameorigin")
58    {
59        return false;
60    }
61    // Step 7. If xFrameOptions's size is greater than 1, then return true.
62    if x_frame_options.len() > 1 {
63        return true;
64    }
65    let Some(first_item) = x_frame_options.iter().next() else {
66        return true;
67    };
68    // Step 8. If xFrameOptions[0] is "deny", then return false.
69    if first_item == "deny" {
70        return false;
71    }
72    // Step 9. If xFrameOptions[0] is "sameorigin", then:
73    if first_item == "sameorigin" {
74        let mut window_proxy = window.window_proxy();
75        // Step 9.2. While containerDocument is not null:
76        while let Some(container_element) = window_proxy.frame_element() {
77            // Step 9.1. Let containerDocument be navigable's container document.
78            let container_document = container_element.owner_document();
79            // Step 9.2.1. If containerDocument's origin is not same origin with destinationOrigin, then return false.
80            if !container_document.origin().same_origin(destination_origin) {
81                return false;
82            }
83            // Step 9.2.2. Set containerDocument to containerDocument's container document.
84            window_proxy = container_document.window().window_proxy()
85        }
86        // If the `frame_element` is None, it could be two options:
87        // 1. There is no parent, in which case we are top-level. We can stop the loop
88        // 2. There is a parent, but it isn't same origin. Therefore, we need to double
89        //    check here that we actually cover this case.
90        if window_proxy.parent().is_some() {
91            return false;
92        }
93    }
94    // Step 10. Return true.
95    true
96}