Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/ReferrerPolicy/AbstractOperations.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
3
 * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibURL/URL.h>
9
#include <LibWeb/DOM/Document.h>
10
#include <LibWeb/DOMURL/DOMURL.h>
11
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
12
#include <LibWeb/Fetch/Infrastructure/URL.h>
13
#include <LibWeb/HTML/Window.h>
14
#include <LibWeb/ReferrerPolicy/AbstractOperations.h>
15
#include <LibWeb/ReferrerPolicy/ReferrerPolicy.h>
16
#include <LibWeb/SecureContexts/AbstractOperations.h>
17
18
namespace Web::ReferrerPolicy {
19
20
// https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header
21
ReferrerPolicy parse_a_referrer_policy_from_a_referrer_policy_header(Fetch::Infrastructure::Response const& response)
22
0
{
23
    // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
24
0
    auto policy_tokens_or_failure = Fetch::Infrastructure::extract_header_list_values("Referrer-Policy"sv.bytes(), response.header_list());
25
0
    auto policy_tokens = policy_tokens_or_failure.has<Vector<ByteBuffer>>() ? policy_tokens_or_failure.get<Vector<ByteBuffer>>() : Vector<ByteBuffer> {};
26
27
    // 2. Let policy be the empty string.
28
0
    auto policy = ReferrerPolicy::EmptyString;
29
30
    // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
31
0
    for (auto token : policy_tokens) {
32
0
        auto referrer_policy = from_string(token);
33
0
        if (referrer_policy.has_value() && referrer_policy.value() != ReferrerPolicy::EmptyString)
34
0
            policy = referrer_policy.release_value();
35
0
    }
36
37
    // 4. Return policy.
38
0
    return policy;
39
0
}
40
41
// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect
42
void set_request_referrer_policy_on_redirect(Fetch::Infrastructure::Request& request, Fetch::Infrastructure::Response const& response)
43
0
{
44
    // 1. Let policy be the result of executing § 8.1 Parse a referrer policy from a Referrer-Policy header on
45
    //    actualResponse.
46
0
    auto policy = parse_a_referrer_policy_from_a_referrer_policy_header(response);
47
48
    // 2. If policy is not the empty string, then set request’s referrer policy to policy.
49
0
    if (policy != ReferrerPolicy::EmptyString)
50
0
        request.set_referrer_policy(policy);
51
0
}
52
53
// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
54
Optional<URL::URL> determine_requests_referrer(Fetch::Infrastructure::Request const& request)
55
0
{
56
    // 1. Let policy be request’s referrer policy.
57
0
    auto const& policy = request.referrer_policy();
58
59
    // 2. Let environment be request’s client.
60
0
    auto environment = request.client();
61
62
    // 3. Switch on request’s referrer:
63
0
    auto referrer_source = request.referrer().visit(
64
        // "client"
65
0
        [&](Fetch::Infrastructure::Request::Referrer referrer) -> Optional<URL::URL> {
66
            // Note: If request’s referrer is "no-referrer", Fetch will not call into this algorithm.
67
0
            VERIFY(referrer == Fetch::Infrastructure::Request::Referrer::Client);
68
69
            // FIXME: Add a const global_object() getter to ESO
70
0
            auto& global_object = const_cast<HTML::EnvironmentSettingsObject&>(*environment).global_object();
71
72
            // 1. If environment’s global object is a Window object, then
73
0
            if (is<HTML::Window>(global_object)) {
74
                // 1. Let document be the associated Document of environment’s global object.
75
0
                auto const& document = static_cast<HTML::Window const&>(global_object).associated_document();
76
77
                // 2. If document’s origin is an opaque origin, return no referrer.
78
0
                if (document.origin().is_opaque())
79
0
                    return {};
80
81
                // FIXME: 3. While document is an iframe srcdoc document, let document be document’s browsing context’s
82
                //           browsing context container’s node document.
83
84
                // 4. Let referrerSource be document’s URL.
85
0
                return document.url();
86
0
            }
87
            // 2. Otherwise, let referrerSource be environment’s creation URL.
88
0
            else {
89
0
                return environment->creation_url;
90
0
            }
91
0
        },
92
        // a URL
93
0
        [&](URL::URL const& url) -> Optional<URL::URL> {
94
            // Let referrerSource be request’s referrer.
95
0
            return url;
96
0
        });
97
    // NOTE: This only happens in step 1.2. of the "client" case above.
98
0
    if (!referrer_source.has_value())
99
0
        return {};
100
101
    // 4. Let request’s referrerURL be the result of stripping referrerSource for use as a referrer.
102
0
    auto referrer_url = strip_url_for_use_as_referrer(referrer_source);
103
104
    // 5. Let referrerOrigin be the result of stripping referrerSource for use as a referrer, with the origin-only flag
105
    //    set to true.
106
0
    auto referrer_origin = strip_url_for_use_as_referrer(referrer_source, OriginOnly::Yes);
107
108
    // 6. If the result of serializing referrerURL is a string whose length is greater than 4096, set referrerURL to
109
    //    referrerOrigin.
110
0
    if (referrer_url.has_value() && referrer_url.value().serialize().length() > 4096)
111
0
        referrer_url = referrer_origin;
112
113
    // 7. The user agent MAY alter referrerURL or referrerOrigin at this point to enforce arbitrary policy
114
    //    considerations in the interests of minimizing data leakage. For example, the user agent could strip the URL
115
    //    down to an origin, modify its host, replace it with an empty string, etc.
116
117
    // 8. Execute the statements corresponding to the value of policy:
118
    // Note: If request’s referrer policy is the empty string, Fetch will not call into this algorithm.
119
0
    VERIFY(policy != ReferrerPolicy::EmptyString);
120
0
    switch (policy) {
121
    // "no-referrer"
122
0
    case ReferrerPolicy::NoReferrer:
123
        // Return no referrer
124
0
        return {};
125
    // "origin"
126
0
    case ReferrerPolicy::Origin:
127
        // Return referrerOrigin
128
0
        return referrer_origin;
129
    // "unsafe-url"
130
0
    case ReferrerPolicy::UnsafeURL:
131
        // Return referrerURL.
132
0
        return referrer_url;
133
    // "strict-origin"
134
0
    case ReferrerPolicy::StrictOrigin:
135
        // 1. If referrerURL is a potentially trustworthy URL and request’s current URL is not a potentially
136
        //    trustworthy URL, then return no referrer.
137
0
        if (referrer_url.has_value()
138
0
            && SecureContexts::is_url_potentially_trustworthy(*referrer_url) == SecureContexts::Trustworthiness::PotentiallyTrustworthy
139
0
            && SecureContexts::is_url_potentially_trustworthy(request.current_url()) == SecureContexts::Trustworthiness::NotTrustworthy) {
140
0
            return {};
141
0
        }
142
143
        // 2. Return referrerOrigin.
144
0
        return referrer_origin;
145
    // "strict-origin-when-cross-origin"
146
0
    case ReferrerPolicy::StrictOriginWhenCrossOrigin:
147
        // 1. If the origin of referrerURL and the origin of request’s current URL are the same, then return
148
        //    referrerURL.
149
0
        if (referrer_url.has_value() && referrer_url->origin().is_same_origin(request.current_url().origin()))
150
0
            return referrer_url;
151
152
        // 2. If referrerURL is a potentially trustworthy URL and request’s current URL is not a potentially
153
        //    trustworthy URL, then return no referrer.
154
0
        if (referrer_url.has_value()
155
0
            && SecureContexts::is_url_potentially_trustworthy(*referrer_url) == SecureContexts::Trustworthiness::PotentiallyTrustworthy
156
0
            && SecureContexts::is_url_potentially_trustworthy(request.current_url()) == SecureContexts::Trustworthiness::NotTrustworthy) {
157
0
            return {};
158
0
        }
159
160
        // 3. Return referrerOrigin.
161
0
        return referrer_origin;
162
    // "same-origin"
163
0
    case ReferrerPolicy::SameOrigin:
164
        // 1. If the origin of referrerURL and the origin of request’s current URL are the same, then return
165
        //    referrerURL.
166
0
        if (referrer_url.has_value()
167
0
            && referrer_url->origin().is_same_origin(request.current_url().origin())) {
168
0
            return referrer_url;
169
0
        }
170
171
        // 2. Return no referrer.
172
0
        return {};
173
    // "origin-when-cross-origin"
174
0
    case ReferrerPolicy::OriginWhenCrossOrigin:
175
        // 1. If the origin of referrerURL and the origin of request’s current URL are the same, then return
176
        //    referrerURL.
177
0
        if (referrer_url.has_value()
178
0
            && referrer_url->origin().is_same_origin(request.current_url().origin())) {
179
0
            return referrer_url;
180
0
        }
181
182
        // 2. Return referrerOrigin.
183
0
        return referrer_origin;
184
    // "no-referrer-when-downgrade"
185
0
    case ReferrerPolicy::NoReferrerWhenDowngrade:
186
        // 1. If referrerURL is a potentially trustworthy URL and request’s current URL is not a potentially
187
        //    trustworthy URL, then return no referrer.
188
0
        if (referrer_url.has_value()
189
0
            && SecureContexts::is_url_potentially_trustworthy(*referrer_url) == SecureContexts::Trustworthiness::PotentiallyTrustworthy
190
0
            && SecureContexts::is_url_potentially_trustworthy(request.current_url()) == SecureContexts::Trustworthiness::NotTrustworthy) {
191
0
            return {};
192
0
        }
193
194
        // 2. Return referrerURL.
195
0
        return referrer_url;
196
0
    default:
197
0
        VERIFY_NOT_REACHED();
198
0
    }
199
0
}
200
201
// https://w3c.github.io/webappsec-referrer-policy/#strip-url
202
Optional<URL::URL> strip_url_for_use_as_referrer(Optional<URL::URL> url, OriginOnly origin_only)
203
0
{
204
    // 1. If url is null, return no referrer.
205
0
    if (!url.has_value())
206
0
        return {};
207
208
    // 2. If url’s scheme is a local scheme, then return no referrer.
209
0
    if (Fetch::Infrastructure::LOCAL_SCHEMES.span().contains_slow(url->scheme()))
210
0
        return {};
211
212
    // 3. Set url’s username to the empty string.
213
0
    url->set_username(""sv);
214
215
    // 4. Set url’s password to the empty string.
216
0
    url->set_password(""sv);
217
218
    // 5. Set url’s fragment to null.
219
0
    url->set_fragment({});
220
221
    // 6. If the origin-only flag is true, then:
222
0
    if (origin_only == OriginOnly::Yes) {
223
        // 1. Set url’s path to « the empty string ».
224
0
        url->set_paths({ ""sv });
225
226
        // 2. Set url’s query to null.
227
0
        url->set_query({});
228
0
    }
229
230
    // 7. Return url.
231
0
    return url;
232
0
}
233
234
}