diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp index 079ec1309b0060..ea525c7988b2d6 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp @@ -25,6 +25,7 @@ JS_DEFINE_ALLOCATOR(OpaqueRedirectFilteredResponse); Response::Response(JS::NonnullGCPtr header_list) : m_header_list(header_list) + , m_response_time(UnixDateTime::now()) { } @@ -207,6 +208,106 @@ bool Response::is_cors_cross_origin() const return type() == Type::Opaque || type() == Type::OpaqueRedirect; } +// https://fetch.spec.whatwg.org/#concept-fresh-response +bool Response::is_fresh() const +{ + // A fresh response is a response whose current age is within its freshness lifetime. + return current_age() < freshness_lifetime(); +} + +// https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response +bool Response::is_stale_while_revalidate() const +{ + // A stale-while-revalidate response is a response that is not a fresh response and whose current age is within the stale-while-revalidate lifetime. + return !is_fresh() && current_age() < stale_while_revalidate_lifetime(); +} + +// https://fetch.spec.whatwg.org/#concept-stale-response +bool Response::is_stale() const +{ + // A stale response is a response that is not a fresh response or a stale-while-revalidate response. + return !is_fresh() && !is_stale_while_revalidate(); +} + +// https://httpwg.org/specs/rfc9111.html#age.calculations +u64 Response::current_age() const +{ + // The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic operation; or 0, if not available. + Optional age; + if (auto const age_header = header_list()->get("Age"sv.bytes()); age_header.has_value()) { + if (auto converted_age = StringView { *age_header }.to_number(); converted_age.has_value()) + age = Duration::from_seconds(converted_age.value()); + } + + auto const age_value = age.value_or(Duration::from_seconds(0)); + + // The term "date_value" denotes the value of the Date header field, in a form appropriate for arithmetic operations. See Section 6.6.1 of [HTTP] for the definition of the Date header field and for requirements regarding responses without it. + // FIXME: Do we have a parser for HTTP-date? + auto const date_value = UnixDateTime::now() - Duration::from_seconds(5); + + // The term "now" means the current value of this implementation's clock (Section 5.6.7 of [HTTP]). + auto const now = UnixDateTime::now(); + + // The value of the clock at the time of the request that resulted in the stored response. + // FIXME: Let's get the correct time. + auto const request_time = UnixDateTime::now() - Duration::from_seconds(5); + + // The value of the clock at the time the response was received. + auto const response_time = m_response_time; + + auto const apparent_age = max(0, (response_time - date_value).to_seconds()); + + auto const response_delay = response_time - request_time; + auto const corrected_age_value = age_value + response_delay; + + auto const corrected_initial_age = max(apparent_age, corrected_age_value.to_seconds()); + + auto const resident_time = (now - response_time).to_seconds(); + return corrected_initial_age + resident_time; +} + +// https://httpwg.org/specs/rfc9111.html#calculating.freshness.lifetime +u64 Response::freshness_lifetime() const +{ + auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes()); + if (!elem.has_value()) + return 0; + + // FIXME: If the cache is shared and the s-maxage response directive (Section 5.2.2.10) is present, use its value + + // If the max-age response directive (Section 5.2.2.1) is present, use its value, or + for (auto const& directive : *elem) { + if (directive.starts_with_bytes("max-age"sv)) { + auto equal_offset = directive.find_byte_offset('=').value(); + auto const value = directive.bytes_as_string_view().substring_view(equal_offset); + return value.to_number().value(); + } + } + + // FIXME: If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date response header field (using the time the message was received if it is not present, as per Section 6.6.1 of [HTTP]), or + // FIXME: Otherwise, no explicit expiration time is present in the response. A heuristic freshness lifetime might be applicable; see Section 4.2.2. + + return 0; +} + +// https://httpwg.org/specs/rfc5861.html#n-the-stale-while-revalidate-cache-control-extension +u64 Response::stale_while_revalidate_lifetime() const +{ + auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes()); + if (!elem.has_value()) + return 0; + + for (auto const& directive : *elem) { + if (directive.starts_with_bytes("stale-while-revalidate"sv)) { + auto equal_offset = directive.find_byte_offset('=').value(); + auto const value = directive.bytes_as_string_view().substring_view(equal_offset); + return value.to_number().value(); + } + } + + return 0; +} + // Non-standard Optional Response::network_error_message() const { diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h index f577af8af9b834..1504144ad4f271 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,10 @@ class Response : public JS::Cell { [[nodiscard]] bool is_cors_cross_origin() const; + [[nodiscard]] bool is_fresh() const; + [[nodiscard]] bool is_stale_while_revalidate() const; + [[nodiscard]] bool is_stale() const; + // Non-standard [[nodiscard]] Optional network_error_message() const; @@ -186,7 +191,14 @@ class Response : public JS::Cell { // A response has an associated has-cross-origin-redirects (a boolean), which is initially false. bool m_has_cross_origin_redirects { false }; + // FIXME is the type correct? + u64 current_age() const; + u64 freshness_lifetime() const; + u64 stale_while_revalidate_lifetime() const; + // Non-standard + UnixDateTime m_response_time; + Optional> m_network_error_message; };