/src/serenity/Userland/Libraries/LibWeb/Cookie/ParsedCookie.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include "ParsedCookie.h" |
8 | | #include <AK/DateConstants.h> |
9 | | #include <AK/Function.h> |
10 | | #include <AK/StdLibExtras.h> |
11 | | #include <AK/Time.h> |
12 | | #include <AK/Vector.h> |
13 | | #include <LibIPC/Decoder.h> |
14 | | #include <LibIPC/Encoder.h> |
15 | | #include <LibURL/URL.h> |
16 | | #include <LibWeb/Infra/Strings.h> |
17 | | #include <ctype.h> |
18 | | |
19 | | namespace Web::Cookie { |
20 | | |
21 | | static void parse_attributes(URL::URL const&, ParsedCookie& parsed_cookie, StringView unparsed_attributes); |
22 | | static void process_attribute(URL::URL const&, ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value); |
23 | | static void on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_value); |
24 | | static void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value); |
25 | | static void on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value); |
26 | | static void on_path_attribute(URL::URL const&, ParsedCookie& parsed_cookie, StringView attribute_value); |
27 | | static void on_secure_attribute(ParsedCookie& parsed_cookie); |
28 | | static void on_http_only_attribute(ParsedCookie& parsed_cookie); |
29 | | static void on_same_site_attribute(ParsedCookie& parsed_cookie, StringView attribute_value); |
30 | | static Optional<UnixDateTime> parse_date_time(StringView date_string); |
31 | | |
32 | | bool cookie_contains_invalid_control_character(StringView cookie_string) |
33 | 0 | { |
34 | 0 | for (auto code_point : Utf8View { cookie_string }) { |
35 | 0 | if (code_point <= 0x08) |
36 | 0 | return true; |
37 | 0 | if (code_point >= 0x0a && code_point <= 0x1f) |
38 | 0 | return true; |
39 | 0 | if (code_point == 0x7f) |
40 | 0 | return true; |
41 | 0 | } |
42 | | |
43 | 0 | return false; |
44 | 0 | } |
45 | | |
46 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6-6 |
47 | | Optional<ParsedCookie> parse_cookie(URL::URL const& url, StringView cookie_string) |
48 | 0 | { |
49 | | // 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F character (CTL characters excluding HTAB): |
50 | | // Abort these steps and ignore the set-cookie-string entirely. |
51 | 0 | if (cookie_contains_invalid_control_character(cookie_string)) |
52 | 0 | return {}; |
53 | | |
54 | 0 | StringView name_value_pair; |
55 | 0 | StringView unparsed_attributes; |
56 | | |
57 | | // 2. If the set-cookie-string contains a %x3B (";") character: |
58 | 0 | if (auto position = cookie_string.find(';'); position.has_value()) { |
59 | | // 1. The name-value-pair string consists of the characters up to, but not including, the first %x3B (";"), and |
60 | | // the unparsed-attributes consist of the remainder of the set-cookie-string (including the %x3B (";") in |
61 | | // question). |
62 | 0 | name_value_pair = cookie_string.substring_view(0, position.value()); |
63 | 0 | unparsed_attributes = cookie_string.substring_view(position.value()); |
64 | 0 | } |
65 | | // Otherwise: |
66 | 0 | else { |
67 | | // 1. The name-value-pair string consists of all the characters contained in the set-cookie-string, and the |
68 | | // unparsed-attributes is the empty string. |
69 | 0 | name_value_pair = cookie_string; |
70 | 0 | } |
71 | |
|
72 | 0 | StringView name; |
73 | 0 | StringView value; |
74 | | |
75 | | // 3. If the name-value-pair string lacks a %x3D ("=") character, then the name string is empty, and the value |
76 | | // string is the value of name-value-pair. |
77 | 0 | if (auto position = name_value_pair.find('='); !position.has_value()) { |
78 | 0 | value = name_value_pair; |
79 | 0 | } else { |
80 | | // Otherwise, the name string consists of the characters up to, but not including, the first %x3D ("=") character |
81 | | // and the (possibly empty) value string consists of the characters after the first %x3D ("=") character. |
82 | 0 | name = name_value_pair.substring_view(0, position.value()); |
83 | |
|
84 | 0 | if (position.value() < name_value_pair.length() - 1) |
85 | 0 | value = name_value_pair.substring_view(position.value() + 1); |
86 | 0 | } |
87 | | |
88 | | // 4. Remove any leading or trailing WSP characters from the name string and the value string. |
89 | 0 | name = name.trim_whitespace(); |
90 | 0 | value = value.trim_whitespace(); |
91 | | |
92 | | // 5. If the sum of the lengths of the name string and the value string is more than 4096 octets, abort these steps |
93 | | // and ignore the set-cookie-string entirely. |
94 | 0 | if (name.length() + value.length() > 4096) |
95 | 0 | return {}; |
96 | | |
97 | | // 6. The cookie-name is the name string, and the cookie-value is the value string. |
98 | 0 | ParsedCookie parsed_cookie { MUST(String::from_utf8(name)), MUST(String::from_utf8(value)) }; |
99 | |
|
100 | 0 | parse_attributes(url, parsed_cookie, unparsed_attributes); |
101 | 0 | return parsed_cookie; |
102 | 0 | } |
103 | | |
104 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6-8 |
105 | | void parse_attributes(URL::URL const& url, ParsedCookie& parsed_cookie, StringView unparsed_attributes) |
106 | 0 | { |
107 | | // 1. If the unparsed-attributes string is empty, skip the rest of these steps. |
108 | 0 | if (unparsed_attributes.is_empty()) |
109 | 0 | return; |
110 | | |
111 | | // 2. Discard the first character of the unparsed-attributes (which will be a %x3B (";") character). |
112 | 0 | unparsed_attributes = unparsed_attributes.substring_view(1); |
113 | |
|
114 | 0 | StringView cookie_av; |
115 | | |
116 | | // 3. If the remaining unparsed-attributes contains a %x3B (";") character: |
117 | 0 | if (auto position = unparsed_attributes.find(';'); position.has_value()) { |
118 | | // 1. Consume the characters of the unparsed-attributes up to, but not including, the first %x3B (";") character. |
119 | 0 | cookie_av = unparsed_attributes.substring_view(0, position.value()); |
120 | 0 | unparsed_attributes = unparsed_attributes.substring_view(position.value()); |
121 | 0 | } |
122 | | // Otherwise: |
123 | 0 | else { |
124 | | // 1. Consume the remainder of the unparsed-attributes. |
125 | 0 | cookie_av = unparsed_attributes; |
126 | 0 | unparsed_attributes = {}; |
127 | 0 | } |
128 | | // Let the cookie-av string be the characters consumed in this step. |
129 | |
|
130 | 0 | StringView attribute_name; |
131 | 0 | StringView attribute_value; |
132 | | |
133 | | // 4. If the cookie-av string contains a %x3D ("=") character: |
134 | 0 | if (auto position = cookie_av.find('='); position.has_value()) { |
135 | | // 1. The (possibly empty) attribute-name string consists of the characters up to, but not including, the first |
136 | | // %x3D ("=") character, and the (possibly empty) attribute-value string consists of the characters after the |
137 | | // first %x3D ("=") character. |
138 | 0 | attribute_name = cookie_av.substring_view(0, position.value()); |
139 | |
|
140 | 0 | if (position.value() < cookie_av.length() - 1) |
141 | 0 | attribute_value = cookie_av.substring_view(position.value() + 1); |
142 | 0 | } |
143 | | // Otherwise: |
144 | 0 | else { |
145 | | // 1. The attribute-name string consists of the entire cookie-av string, and the attribute-value string is empty. |
146 | 0 | attribute_name = cookie_av; |
147 | 0 | } |
148 | | |
149 | | // 5. Remove any leading or trailing WSP characters from the attribute-name string and the attribute-value string. |
150 | 0 | attribute_name = attribute_name.trim_whitespace(); |
151 | 0 | attribute_value = attribute_value.trim_whitespace(); |
152 | | |
153 | | // 6. If the attribute-value is longer than 1024 octets, ignore the cookie-av string and return to Step 1 of this |
154 | | // algorithm. |
155 | 0 | if (attribute_value.length() > 1024) { |
156 | 0 | parse_attributes(url, parsed_cookie, unparsed_attributes); |
157 | 0 | return; |
158 | 0 | } |
159 | | |
160 | | // 7. Process the attribute-name and attribute-value according to the requirements in the following subsections. |
161 | | // (Notice that attributes with unrecognized attribute-names are ignored.) |
162 | 0 | process_attribute(url, parsed_cookie, attribute_name, attribute_value); |
163 | | |
164 | | // 8. Return to Step 1 of this algorithm. |
165 | 0 | parse_attributes(url, parsed_cookie, unparsed_attributes); |
166 | 0 | } |
167 | | |
168 | | void process_attribute(URL::URL const& url, ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value) |
169 | 0 | { |
170 | 0 | if (attribute_name.equals_ignoring_ascii_case("Expires"sv)) { |
171 | 0 | on_expires_attribute(parsed_cookie, attribute_value); |
172 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("Max-Age"sv)) { |
173 | 0 | on_max_age_attribute(parsed_cookie, attribute_value); |
174 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("Domain"sv)) { |
175 | 0 | on_domain_attribute(parsed_cookie, attribute_value); |
176 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("Path"sv)) { |
177 | 0 | on_path_attribute(url, parsed_cookie, attribute_value); |
178 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("Secure"sv)) { |
179 | 0 | on_secure_attribute(parsed_cookie); |
180 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("HttpOnly"sv)) { |
181 | 0 | on_http_only_attribute(parsed_cookie); |
182 | 0 | } else if (attribute_name.equals_ignoring_ascii_case("SameSite"sv)) { |
183 | 0 | on_same_site_attribute(parsed_cookie, attribute_value); |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | static constexpr AK::Duration maximum_cookie_age() |
188 | 0 | { |
189 | 0 | return AK::Duration::from_seconds(400LL * 24 * 60 * 60); |
190 | 0 | } |
191 | | |
192 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.1 |
193 | | void on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_value) |
194 | 0 | { |
195 | | // 1. Let the expiry-time be the result of parsing the attribute-value as cookie-date (see Section 5.1.1). |
196 | 0 | auto expiry_time = parse_date_time(attribute_value); |
197 | | |
198 | | // 2. If the attribute-value failed to parse as a cookie date, ignore the cookie-av. |
199 | 0 | if (!expiry_time.has_value()) |
200 | 0 | return; |
201 | | |
202 | | // 3. Let cookie-age-limit be the maximum age of the cookie (which SHOULD be 400 days in the future or sooner, see |
203 | | // Section 5.5). |
204 | 0 | auto cookie_age_limit = UnixDateTime::now() + maximum_cookie_age(); |
205 | | |
206 | | // 4. If the expiry-time is more than cookie-age-limit, the user agent MUST set the expiry time to cookie-age-limit |
207 | | // in seconds. |
208 | 0 | if (expiry_time->seconds_since_epoch() > cookie_age_limit.seconds_since_epoch()) |
209 | 0 | expiry_time = cookie_age_limit; |
210 | | |
211 | | // 5. If the expiry-time is earlier than the earliest date the user agent can represent, the user agent MAY replace |
212 | | // the expiry-time with the earliest representable date. |
213 | 0 | if (auto earliest = UnixDateTime::earliest(); *expiry_time < earliest) |
214 | 0 | expiry_time = earliest; |
215 | | |
216 | | // 6. Append an attribute to the cookie-attribute-list with an attribute-name of Expires and an attribute-value of |
217 | | // expiry-time. |
218 | 0 | parsed_cookie.expiry_time_from_expires_attribute = expiry_time.release_value(); |
219 | 0 | } |
220 | | |
221 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.2 |
222 | | void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value) |
223 | 0 | { |
224 | | // 1. If the attribute-value is empty, ignore the cookie-av. |
225 | 0 | if (attribute_value.is_empty()) |
226 | 0 | return; |
227 | | |
228 | | // 2. If the first character of the attribute-value is neither a DIGIT, nor a "-" character followed by a DIGIT, |
229 | | // ignore the cookie-av. |
230 | | // 3. If the remainder of attribute-value contains a non-DIGIT character, ignore the cookie-av. |
231 | 0 | auto digits = attribute_value[0] == '-' ? attribute_value.substring_view(1) : attribute_value; |
232 | |
|
233 | 0 | if (digits.is_empty() || !all_of(digits, is_ascii_digit)) |
234 | 0 | return; |
235 | | |
236 | | // 4. Let delta-seconds be the attribute-value converted to a base 10 integer. |
237 | 0 | auto delta_seconds = attribute_value.to_number<i64>(); |
238 | 0 | if (!delta_seconds.has_value()) { |
239 | | // We know the attribute value only contains digits, so if we failed to parse, it is because the result did not |
240 | | // fit in an i64. Set the value to the i64 limits in that case. The positive limit will be further capped below, |
241 | | // and the negative limit will be immediately expired in the cookie jar. |
242 | 0 | delta_seconds = attribute_value[0] == '-' ? NumericLimits<i64>::min() : NumericLimits<i64>::max(); |
243 | 0 | } |
244 | | |
245 | | // 5. Let cookie-age-limit be the maximum age of the cookie (which SHOULD be 400 days or less, see Section 5.5). |
246 | 0 | auto cookie_age_limit = maximum_cookie_age(); |
247 | | |
248 | | // 6. Set delta-seconds to the smaller of its present value and cookie-age-limit. |
249 | 0 | if (*delta_seconds > cookie_age_limit.to_seconds()) |
250 | 0 | delta_seconds = cookie_age_limit.to_seconds(); |
251 | | |
252 | | // 7. If delta-seconds is less than or equal to zero (0), let expiry-time be the earliest representable date and |
253 | | // time. Otherwise, let the expiry-time be the current date and time plus delta-seconds seconds. |
254 | 0 | auto expiry_time = *delta_seconds <= 0 |
255 | 0 | ? UnixDateTime::earliest() |
256 | 0 | : UnixDateTime::now() + AK::Duration::from_seconds(*delta_seconds); |
257 | | |
258 | | // 8. Append an attribute to the cookie-attribute-list with an attribute-name of Max-Age and an attribute-value of |
259 | | // expiry-time. |
260 | 0 | parsed_cookie.expiry_time_from_max_age_attribute = expiry_time; |
261 | 0 | } |
262 | | |
263 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.3 |
264 | | void on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value) |
265 | 0 | { |
266 | | // 1. Let cookie-domain be the attribute-value. |
267 | 0 | auto cookie_domain = attribute_value; |
268 | | |
269 | | // 2. If cookie-domain starts with %x2E ("."), let cookie-domain be cookie-domain without its leading %x2E ("."). |
270 | 0 | if (cookie_domain.starts_with('.')) |
271 | 0 | cookie_domain = cookie_domain.substring_view(1); |
272 | | |
273 | | // 3. Convert the cookie-domain to lower case. |
274 | 0 | auto lowercase_cookie_domain = MUST(Infra::to_ascii_lowercase(cookie_domain)); |
275 | | |
276 | | // 4. Append an attribute to the cookie-attribute-list with an attribute-name of Domain and an attribute-value of |
277 | | // cookie-domain. |
278 | 0 | parsed_cookie.domain = move(lowercase_cookie_domain); |
279 | 0 | } |
280 | | |
281 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.4 |
282 | | void on_path_attribute(URL::URL const& url, ParsedCookie& parsed_cookie, StringView attribute_value) |
283 | 0 | { |
284 | 0 | String cookie_path; |
285 | | |
286 | | // 1. If the attribute-value is empty or if the first character of the attribute-value is not %x2F ("/"): |
287 | 0 | if (attribute_value.is_empty() || attribute_value[0] != '/') { |
288 | | // 1. Let cookie-path be the default-path. |
289 | 0 | cookie_path = default_path(url); |
290 | 0 | } |
291 | | // Otherwise: |
292 | 0 | else { |
293 | | // 1. Let cookie-path be the attribute-value. |
294 | 0 | cookie_path = MUST(String::from_utf8(attribute_value)); |
295 | 0 | } |
296 | | |
297 | | // 2. Append an attribute to the cookie-attribute-list with an attribute-name of Path and an attribute-value of |
298 | | // cookie-path. |
299 | 0 | parsed_cookie.path = move(cookie_path); |
300 | 0 | } |
301 | | |
302 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.5 |
303 | | void on_secure_attribute(ParsedCookie& parsed_cookie) |
304 | 0 | { |
305 | 0 | parsed_cookie.secure_attribute_present = true; |
306 | 0 | } |
307 | | |
308 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.6 |
309 | | void on_http_only_attribute(ParsedCookie& parsed_cookie) |
310 | 0 | { |
311 | 0 | parsed_cookie.http_only_attribute_present = true; |
312 | 0 | } |
313 | | |
314 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.7 |
315 | | void on_same_site_attribute(ParsedCookie& parsed_cookie, StringView attribute_value) |
316 | 0 | { |
317 | | // 1. Let enforcement be "Default". |
318 | | // 2. If cookie-av's attribute-value is a case-insensitive match for "None", set enforcement to "None". |
319 | | // 3. If cookie-av's attribute-value is a case-insensitive match for "Strict", set enforcement to "Strict". |
320 | | // 4. If cookie-av's attribute-value is a case-insensitive match for "Lax", set enforcement to "Lax". |
321 | 0 | auto enforcement = same_site_from_string(attribute_value); |
322 | | |
323 | | // 5. Append an attribute to the cookie-attribute-list with an attribute-name of "SameSite" and an attribute-value |
324 | | // of enforcement. |
325 | 0 | parsed_cookie.same_site_attribute = enforcement; |
326 | 0 | } |
327 | | |
328 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.1 |
329 | | Optional<UnixDateTime> parse_date_time(StringView date_string) |
330 | 0 | { |
331 | | // https://tools.ietf.org/html/rfc6265#section-5.1.1 |
332 | 0 | unsigned hour = 0; |
333 | 0 | unsigned minute = 0; |
334 | 0 | unsigned second = 0; |
335 | 0 | unsigned day_of_month = 0; |
336 | 0 | unsigned month = 0; |
337 | 0 | unsigned year = 0; |
338 | |
|
339 | 0 | auto to_uint = [](StringView token, unsigned& result) { |
340 | 0 | if (!all_of(token, isdigit)) |
341 | 0 | return false; |
342 | | |
343 | 0 | if (auto converted = token.to_number<unsigned>(); converted.has_value()) { |
344 | 0 | result = *converted; |
345 | 0 | return true; |
346 | 0 | } |
347 | | |
348 | 0 | return false; |
349 | 0 | }; |
350 | |
|
351 | 0 | auto parse_time = [&](StringView token) { |
352 | 0 | Vector<StringView> parts = token.split_view(':'); |
353 | 0 | if (parts.size() != 3) |
354 | 0 | return false; |
355 | | |
356 | 0 | for (auto const& part : parts) { |
357 | 0 | if (part.is_empty() || part.length() > 2) |
358 | 0 | return false; |
359 | 0 | } |
360 | | |
361 | 0 | return to_uint(parts[0], hour) && to_uint(parts[1], minute) && to_uint(parts[2], second); |
362 | 0 | }; |
363 | |
|
364 | 0 | auto parse_day_of_month = [&](StringView token) { |
365 | 0 | if (token.is_empty() || token.length() > 2) |
366 | 0 | return false; |
367 | 0 | return to_uint(token, day_of_month); |
368 | 0 | }; |
369 | |
|
370 | 0 | auto parse_month = [&](StringView token) { |
371 | 0 | for (unsigned i = 0; i < 12; ++i) { |
372 | 0 | if (token.equals_ignoring_ascii_case(short_month_names[i])) { |
373 | 0 | month = i + 1; |
374 | 0 | return true; |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | 0 | return false; |
379 | 0 | }; |
380 | |
|
381 | 0 | auto parse_year = [&](StringView token) { |
382 | 0 | if (token.length() != 2 && token.length() != 4) |
383 | 0 | return false; |
384 | 0 | return to_uint(token, year); |
385 | 0 | }; |
386 | |
|
387 | 0 | Function<bool(char)> is_delimiter = [](char ch) { |
388 | 0 | return ch == 0x09 || (ch >= 0x20 && ch <= 0x2f) || (ch >= 0x3b && ch <= 0x40) || (ch >= 0x5b && ch <= 0x60) || (ch >= 0x7b && ch <= 0x7e); |
389 | 0 | }; |
390 | | |
391 | | // 1. Using the grammar below, divide the cookie-date into date-tokens. |
392 | 0 | Vector<StringView> date_tokens = date_string.split_view_if(is_delimiter); |
393 | | |
394 | | // 2. Process each date-token sequentially in the order the date-tokens appear in the cookie-date: |
395 | 0 | bool found_time = false; |
396 | 0 | bool found_day_of_month = false; |
397 | 0 | bool found_month = false; |
398 | 0 | bool found_year = false; |
399 | |
|
400 | 0 | for (auto const& date_token : date_tokens) { |
401 | | // 1. If the found-time flag is not set and the token matches the time production, set the found-time flag and |
402 | | // set the hour-value, minute-value, and second-value to the numbers denoted by the digits in the date-token, |
403 | | // respectively. Skip the remaining sub-steps and continue to the next date-token. |
404 | 0 | if (!found_time && parse_time(date_token)) { |
405 | 0 | found_time = true; |
406 | 0 | } |
407 | | |
408 | | // 2. If the found-day-of-month flag is not set and the date-token matches the day-of-month production, set the |
409 | | // found-day-of-month flag and set the day-of-month-value to the number denoted by the date-token. Skip the |
410 | | // remaining sub-steps and continue to the next date-token. |
411 | 0 | else if (!found_day_of_month && parse_day_of_month(date_token)) { |
412 | 0 | found_day_of_month = true; |
413 | 0 | } |
414 | | |
415 | | // 3. If the found-month flag is not set and the date-token matches the month production, set the found-month |
416 | | // flag and set the month-value to the month denoted by the date-token. Skip the remaining sub-steps and |
417 | | // continue to the next date-token. |
418 | 0 | else if (!found_month && parse_month(date_token)) { |
419 | 0 | found_month = true; |
420 | 0 | } |
421 | | |
422 | | // 4. If the found-year flag is not set and the date-token matches the year production, set the found-year flag |
423 | | // and set the year-value to the number denoted by the date-token. Skip the remaining sub-steps and continue |
424 | | // to the next date-token. |
425 | 0 | else if (!found_year && parse_year(date_token)) { |
426 | 0 | found_year = true; |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | // 3. If the year-value is greater than or equal to 70 and less than or equal to 99, increment the year-value by 1900. |
431 | 0 | if (year >= 70 && year <= 99) |
432 | 0 | year += 1900; |
433 | | |
434 | | // 4. If the year-value is greater than or equal to 0 and less than or equal to 69, increment the year-value by 2000. |
435 | 0 | if (year <= 69) |
436 | 0 | year += 2000; |
437 | | |
438 | | // 5. Abort these steps and fail to parse the cookie-date if: |
439 | | // * at least one of the found-day-of-month, found-month, found-year, or found-time flags is not set, |
440 | 0 | if (!found_day_of_month || !found_month || !found_year || !found_time) |
441 | 0 | return {}; |
442 | | // * the day-of-month-value is less than 1 or greater than 31, |
443 | 0 | if (day_of_month < 1 || day_of_month > 31) |
444 | 0 | return {}; |
445 | | // * the year-value is less than 1601, |
446 | 0 | if (year < 1601) |
447 | 0 | return {}; |
448 | | // * the hour-value is greater than 23, |
449 | 0 | if (hour > 23) |
450 | 0 | return {}; |
451 | | // * the minute-value is greater than 59, or |
452 | 0 | if (minute > 59) |
453 | 0 | return {}; |
454 | | // * the second-value is greater than 59. |
455 | 0 | if (second > 59) |
456 | 0 | return {}; |
457 | | |
458 | | // 6. Let the parsed-cookie-date be the date whose day-of-month, month, year, hour, minute, and second (in UTC) are the |
459 | | // day-of-month-value, the month-value, the year-value, the hour-value, the minute-value, and the second-value, respectively. |
460 | | // If no such date exists, abort these steps and fail to parse the cookie-date. |
461 | 0 | if (day_of_month > static_cast<unsigned int>(days_in_month(year, month))) |
462 | 0 | return {}; |
463 | | |
464 | | // FIXME: This currently uses UNIX time, which is not equivalent to UTC due to leap seconds. |
465 | 0 | auto parsed_cookie_date = UnixDateTime::from_unix_time_parts(year, month, day_of_month, hour, minute, second, 0); |
466 | | |
467 | | // 7. Return the parsed-cookie-date as the result of this algorithm. |
468 | 0 | return parsed_cookie_date; |
469 | 0 | } |
470 | | |
471 | | // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.4 |
472 | | String default_path(URL::URL const& url) |
473 | 0 | { |
474 | | // 1. Let uri-path be the path portion of the request-uri if such a portion exists (and empty otherwise). |
475 | 0 | auto uri_path = URL::percent_decode(url.serialize_path()); |
476 | | |
477 | | // 2. If the uri-path is empty or if the first character of the uri-path is not a %x2F ("/") character, output |
478 | | // %x2F ("/") and skip the remaining steps. |
479 | 0 | if (uri_path.is_empty() || (uri_path[0] != '/')) |
480 | 0 | return "/"_string; |
481 | | |
482 | 0 | StringView uri_path_view = uri_path; |
483 | 0 | size_t last_separator = uri_path_view.find_last('/').value(); |
484 | | |
485 | | // 3. If the uri-path contains no more than one %x2F ("/") character, output %x2F ("/") and skip the remaining step. |
486 | 0 | if (last_separator == 0) |
487 | 0 | return "/"_string; |
488 | | |
489 | | // 4. Output the characters of the uri-path from the first character up to, but not including, the right-most |
490 | | // %x2F ("/"). |
491 | | // FIXME: The path might not be valid UTF-8. |
492 | 0 | return MUST(String::from_utf8(uri_path.substring_view(0, last_separator))); |
493 | 0 | } |
494 | | |
495 | | } |
496 | | |
497 | | template<> |
498 | | ErrorOr<void> IPC::encode(Encoder& encoder, Web::Cookie::ParsedCookie const& cookie) |
499 | 0 | { |
500 | 0 | TRY(encoder.encode(cookie.name)); |
501 | 0 | TRY(encoder.encode(cookie.value)); |
502 | 0 | TRY(encoder.encode(cookie.expiry_time_from_expires_attribute)); |
503 | 0 | TRY(encoder.encode(cookie.expiry_time_from_max_age_attribute)); |
504 | 0 | TRY(encoder.encode(cookie.domain)); |
505 | 0 | TRY(encoder.encode(cookie.path)); |
506 | 0 | TRY(encoder.encode(cookie.secure_attribute_present)); |
507 | 0 | TRY(encoder.encode(cookie.http_only_attribute_present)); |
508 | 0 | TRY(encoder.encode(cookie.same_site_attribute)); |
509 | |
|
510 | 0 | return {}; |
511 | 0 | } |
512 | | |
513 | | template<> |
514 | | ErrorOr<Web::Cookie::ParsedCookie> IPC::decode(Decoder& decoder) |
515 | 0 | { |
516 | 0 | auto name = TRY(decoder.decode<String>()); |
517 | 0 | auto value = TRY(decoder.decode<String>()); |
518 | 0 | auto expiry_time_from_expires_attribute = TRY(decoder.decode<Optional<UnixDateTime>>()); |
519 | 0 | auto expiry_time_from_max_age_attribute = TRY(decoder.decode<Optional<UnixDateTime>>()); |
520 | 0 | auto domain = TRY(decoder.decode<Optional<String>>()); |
521 | 0 | auto path = TRY(decoder.decode<Optional<String>>()); |
522 | 0 | auto secure_attribute_present = TRY(decoder.decode<bool>()); |
523 | 0 | auto http_only_attribute_present = TRY(decoder.decode<bool>()); |
524 | 0 | auto same_site_attribute = TRY(decoder.decode<Web::Cookie::SameSite>()); |
525 | |
|
526 | 0 | return Web::Cookie::ParsedCookie { move(name), move(value), same_site_attribute, move(expiry_time_from_expires_attribute), move(expiry_time_from_max_age_attribute), move(domain), move(path), secure_attribute_present, http_only_attribute_present }; |
527 | 0 | } |