From 9b8e3418282533bfdeadbb09496599e5cda435de Mon Sep 17 00:00:00 2001 From: Praise-Garfield <260351813+Praise-Garfield@users.noreply.github.com> Date: Sat, 14 Feb 2026 12:54:37 +0000 Subject: [PATCH] LibHTTP: Implement the must-understand cache directive This implements the must-understand response cache directive per RFC 9111 Section 5.2.2.3. When a response contains must-understand, this cache now ignores the no-store directive for status codes whose caching behavior it implements. For status codes the cache does not understand, the response is not stored. --- Libraries/LibHTTP/Cache/Utilities.cpp | 23 +++++++++++--- Tests/LibHTTP/CMakeLists.txt | 1 + Tests/LibHTTP/TestCacheUtilities.cpp | 45 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 Tests/LibHTTP/TestCacheUtilities.cpp diff --git a/Libraries/LibHTTP/Cache/Utilities.cpp b/Libraries/LibHTTP/Cache/Utilities.cpp index 498b830b06..b6c6c4ef40 100644 --- a/Libraries/LibHTTP/Cache/Utilities.cpp +++ b/Libraries/LibHTTP/Cache/Utilities.cpp @@ -195,12 +195,25 @@ bool is_cacheable(u32 status_code, HeaderList const& headers) // * if the response status code is 206 or 304, or the must-understand cache directive (see Section 5.2.2.3) is // present: the cache understands the response status code; // - // This cache implements the semantics of 206 and 304, so no check is needed here. - // FIXME: must-understand is not implemented. + // NB: This cache implements the semantics of 304 for revalidation. 206 is excluded above. + bool has_must_understand = cache_control.has_value() && contains_cache_control_directive(*cache_control, "must-understand"sv); - // * the no-store cache directive is not present in the response (see Section 5.2.2.5); - if (cache_control.has_value() && contains_cache_control_directive(*cache_control, "no-store"sv)) - return false; + if (has_must_understand) { + if (!is_heuristically_cacheable_status(status_code) && status_code != 304) + return false; + + // https://httpwg.org/specs/rfc9111.html#cache-response-directive.must-understand + // The must-understand response directive limits caching of the response to a cache that understands and conforms + // to the requirements for that response's status code. + // + // A response that contains the must-understand directive SHOULD also contain the no-store directive. When a cache + // that implements the must-understand directive receives a response that includes it, the cache SHOULD ignore the + // no-store directive if it understands and implements the status code's caching requirements. + } else { + // * the no-store cache directive is not present in the response (see Section 5.2.2.5); + if (cache_control.has_value() && contains_cache_control_directive(*cache_control, "no-store"sv)) + return false; + } // * if the cache is shared: the private response directive is either not present or allows a shared cache to store // a modified response; see Section 5.2.2.7); diff --git a/Tests/LibHTTP/CMakeLists.txt b/Tests/LibHTTP/CMakeLists.txt index 76c7cdcd43..67f4a01e88 100644 --- a/Tests/LibHTTP/CMakeLists.txt +++ b/Tests/LibHTTP/CMakeLists.txt @@ -1,4 +1,5 @@ set(TEST_SOURCES + TestCacheUtilities.cpp TestHTTPUtils.cpp ) diff --git a/Tests/LibHTTP/TestCacheUtilities.cpp b/Tests/LibHTTP/TestCacheUtilities.cpp new file mode 100644 index 0000000000..429bf02913 --- /dev/null +++ b/Tests/LibHTTP/TestCacheUtilities.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +TEST_CASE(is_cacheable_must_understand_ignores_no_store_for_understood_status) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "must-understand, no-store, max-age=3600" } }); + EXPECT(HTTP::is_cacheable(200, *headers)); +} + +TEST_CASE(is_cacheable_must_understand_rejects_unknown_status) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "must-understand, no-store, max-age=3600" } }); + EXPECT(!HTTP::is_cacheable(202, *headers)); +} + +TEST_CASE(is_cacheable_no_store_without_must_understand) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "no-store, max-age=3600" } }); + EXPECT(!HTTP::is_cacheable(200, *headers)); +} + +TEST_CASE(is_cacheable_must_understand_without_no_store_understood_status) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "must-understand, max-age=3600" } }); + EXPECT(HTTP::is_cacheable(200, *headers)); +} + +TEST_CASE(is_cacheable_must_understand_without_no_store_unknown_status) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "must-understand, max-age=3600" } }); + EXPECT(!HTTP::is_cacheable(299, *headers)); +} + +TEST_CASE(is_cacheable_must_understand_accepts_304_status) +{ + auto headers = HTTP::HeaderList::create({ { "Cache-Control", "must-understand, no-store, max-age=3600" } }); + EXPECT(HTTP::is_cacheable(304, *headers)); +}