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