From 163e8e5b44029a4d38b3b7bb4c7bf80afd4a27cf Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 9 Oct 2025 14:24:47 -0400 Subject: [PATCH] LibWebView+RequestServer: Support clearing the HTTP disk cache This is a bit of a blunt hammer, but this hooks an action to clear the HTTP disk cache into the existing Clear Cache action. Upon invocation, it stops all existing cache entries from making further progress, and then deletes the entire cache index and all cache files. In the future, we will of course want more fine-grained control over cache deletion, e.g. via an about:history page. --- Libraries/LibWebView/Application.cpp | 5 ++- Services/RequestServer/Cache/CacheEntry.cpp | 40 ++++++++++++++----- Services/RequestServer/Cache/CacheEntry.h | 5 +++ Services/RequestServer/Cache/CacheIndex.cpp | 7 ++++ Services/RequestServer/Cache/CacheIndex.h | 2 + Services/RequestServer/Cache/DiskCache.cpp | 24 +++++++++++ Services/RequestServer/Cache/DiskCache.h | 1 + .../RequestServer/ConnectionFromClient.cpp | 6 +++ Services/RequestServer/ConnectionFromClient.h | 2 + Services/RequestServer/RequestServer.ipc | 2 + 10 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 040e97bc374..525efcb9247 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -824,7 +824,10 @@ void Application::initialize_actions() m_debug_menu->add_separator(); m_debug_menu->add_action(Action::create("Collect Garbage"sv, ActionID::CollectGarbage, debug_request("collect-garbage"sv))); - m_debug_menu->add_action(Action::create("Clear Cache"sv, ActionID::ClearCache, debug_request("clear-cache"sv))); + m_debug_menu->add_action(Action::create("Clear Cache"sv, ActionID::ClearCache, [this, clear_memory_cache = debug_request("clear_cache")]() { + m_request_server_client->async_clear_cache(); + clear_memory_cache(); + })); m_debug_menu->add_action(Action::create("Clear All Cookies"sv, ActionID::ClearCookies, [this]() { m_cookie_jar->clear_all_cookies(); })); m_debug_menu->add_separator(); diff --git a/Services/RequestServer/Cache/CacheEntry.cpp b/Services/RequestServer/Cache/CacheEntry.cpp index 98ce603fe1d..b975af67e94 100644 --- a/Services/RequestServer/Cache/CacheEntry.cpp +++ b/Services/RequestServer/Cache/CacheEntry.cpp @@ -153,6 +153,11 @@ CacheEntryWriter::CacheEntryWriter(DiskCache& disk_cache, CacheIndex& index, u64 ErrorOr CacheEntryWriter::write_data(ReadonlyBytes data) { + if (m_marked_for_deletion) { + close_and_destory_cache_entry(); + return Error::from_string_literal("Cache entry has been deleted"); + } + if (auto result = m_file->write_until_depleted(data); result.is_error()) { dbgln("\033[31;1mUnable to write to cache entry for{}\033[0m {}: {}", m_url, result.error()); @@ -174,6 +179,9 @@ ErrorOr CacheEntryWriter::flush() { ScopeGuard guard { [&]() { close_and_destory_cache_entry(); } }; + if (m_marked_for_deletion) + return Error::from_string_literal("Cache entry has been deleted"); + if (auto result = m_file->write_value(m_cache_footer); result.is_error()) { dbgln("\033[31;1mUnable to flush cache entry for{}\033[0m {}: {}", m_url, result.error()); remove(); @@ -272,6 +280,11 @@ void CacheEntryReader::pipe_to(int pipe_fd, Function on_complete, Fun m_on_pipe_complete = move(on_complete); m_on_pipe_error = move(on_error); + if (m_marked_for_deletion) { + pipe_error(Error::from_string_literal("Cache entry has been deleted")); + return; + } + m_pipe_write_notifier = Core::Notifier::construct(m_pipe_fd, Core::NotificationType::Write); m_pipe_write_notifier->set_enabled(false); @@ -285,19 +298,18 @@ void CacheEntryReader::pipe_to(int pipe_fd, Function on_complete, Fun void CacheEntryReader::pipe_without_blocking() { + if (m_marked_for_deletion) { + pipe_error(Error::from_string_literal("Cache entry has been deleted")); + return; + } + auto result = Core::System::transfer_file_through_pipe(m_fd, m_pipe_fd, m_data_offset + m_bytes_piped, m_data_size - m_bytes_piped); if (result.is_error()) { - if (result.error().code() != EAGAIN && result.error().code() != EWOULDBLOCK) { - dbgln("\033[31;1mError transferring cache to pipe for\033[0m {}: {}", m_url, result.error()); - - if (m_on_pipe_error) - m_on_pipe_error(m_bytes_piped); - - close_and_destory_cache_entry(); - } else { + if (result.error().code() != EAGAIN && result.error().code() != EWOULDBLOCK) + pipe_error(result.release_error()); + else m_pipe_write_notifier->set_enabled(true); - } return; } @@ -330,6 +342,16 @@ void CacheEntryReader::pipe_complete() close_and_destory_cache_entry(); } +void CacheEntryReader::pipe_error(Error error) +{ + dbgln("\033[31;1mError transferring cache to pipe for\033[0m {}: {}", m_url, error); + + if (m_on_pipe_error) + m_on_pipe_error(m_bytes_piped); + + close_and_destory_cache_entry(); +} + ErrorOr CacheEntryReader::read_and_validate_footer() { TRY(m_file->seek(m_data_offset + m_data_size, SeekMode::SetPosition)); diff --git a/Services/RequestServer/Cache/CacheEntry.h b/Services/RequestServer/Cache/CacheEntry.h index 62a60c7f14c..1c8ab8f8323 100644 --- a/Services/RequestServer/Cache/CacheEntry.h +++ b/Services/RequestServer/Cache/CacheEntry.h @@ -57,6 +57,8 @@ public: void remove(); + void mark_for_deletion(Badge) { m_marked_for_deletion = true; } + protected: CacheEntry(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, CacheHeader); @@ -72,6 +74,8 @@ protected: CacheHeader m_cache_header; CacheFooter m_cache_footer; + + bool m_marked_for_deletion { false }; }; class CacheEntryWriter : public CacheEntry { @@ -107,6 +111,7 @@ private: void pipe_without_blocking(); void pipe_complete(); + void pipe_error(Error); ErrorOr read_and_validate_footer(); diff --git a/Services/RequestServer/Cache/CacheIndex.cpp b/Services/RequestServer/Cache/CacheIndex.cpp index f454076ffec..9b6da3e020e 100644 --- a/Services/RequestServer/Cache/CacheIndex.cpp +++ b/Services/RequestServer/Cache/CacheIndex.cpp @@ -25,6 +25,7 @@ ErrorOr CacheIndex::create(Database::Database& database) Statements statements {}; statements.insert_entry = TRY(database.prepare_statement("INSERT OR REPLACE INTO CacheIndex VALUES (?, ?, ?, ?, ?, ?);"sv)); statements.remove_entry = TRY(database.prepare_statement("DELETE FROM CacheIndex WHERE cache_key = ?;"sv)); + statements.remove_all_entries = TRY(database.prepare_statement("DELETE FROM CacheIndex;"sv)); statements.select_entry = TRY(database.prepare_statement("SELECT * FROM CacheIndex WHERE cache_key = ?;"sv)); statements.update_last_access_time = TRY(database.prepare_statement("UPDATE CacheIndex SET last_access_time = ? WHERE cache_key = ?;"sv)); @@ -60,6 +61,12 @@ void CacheIndex::remove_entry(u64 cache_key) m_entries.remove(cache_key); } +void CacheIndex::remove_all_entries() +{ + m_database.execute_statement(m_statements.remove_all_entries, {}); + m_entries.clear(); +} + void CacheIndex::update_last_access_time(u64 cache_key) { auto entry = m_entries.get(cache_key); diff --git a/Services/RequestServer/Cache/CacheIndex.h b/Services/RequestServer/Cache/CacheIndex.h index a1b2e4174d7..47fe55a0853 100644 --- a/Services/RequestServer/Cache/CacheIndex.h +++ b/Services/RequestServer/Cache/CacheIndex.h @@ -33,6 +33,7 @@ public: void create_entry(u64 cache_key, String url, u64 data_size, UnixDateTime request_time, UnixDateTime response_time); void remove_entry(u64 cache_key); + void remove_all_entries(); Optional find_entry(u64 cache_key); @@ -42,6 +43,7 @@ private: struct Statements { Database::StatementID insert_entry { 0 }; Database::StatementID remove_entry { 0 }; + Database::StatementID remove_all_entries { 0 }; Database::StatementID select_entry { 0 }; Database::StatementID update_last_access_time { 0 }; }; diff --git a/Services/RequestServer/Cache/DiskCache.cpp b/Services/RequestServer/Cache/DiskCache.cpp index 9d4bf4953a1..f0cf3a8c093 100644 --- a/Services/RequestServer/Cache/DiskCache.cpp +++ b/Services/RequestServer/Cache/DiskCache.cpp @@ -4,7 +4,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include +#include #include #include #include @@ -90,6 +92,28 @@ Optional DiskCache::open_entry(URL::URL const& url, StringVie return static_cast(**m_open_cache_entries.get(address)); } +void DiskCache::clear_cache() +{ + for (auto& [_, cache_entry] : m_open_cache_entries) + cache_entry->mark_for_deletion({}); + + m_index.remove_all_entries(); + + Core::DirIterator it { m_cache_directory.string(), Core::DirIterator::SkipDots }; + size_t cache_entries { 0 }; + + while (it.has_next()) { + auto entry = it.next_full_path(); + if (LexicalPath { entry }.title() == INDEX_DATABASE) + continue; + + (void)FileSystem::remove(entry, FileSystem::RecursionMode::Disallowed); + ++cache_entries; + } + + dbgln("Cleared {} disk cache entries", cache_entries); +} + void DiskCache::cache_entry_closed(Badge, CacheEntry const& cache_entry) { auto address = reinterpret_cast(&cache_entry); diff --git a/Services/RequestServer/Cache/DiskCache.h b/Services/RequestServer/Cache/DiskCache.h index 09cdc42e48d..9f21dd1ae8b 100644 --- a/Services/RequestServer/Cache/DiskCache.h +++ b/Services/RequestServer/Cache/DiskCache.h @@ -26,6 +26,7 @@ public: Optional create_entry(URL::URL const&, StringView method, u32 status_code, Optional reason_phrase, HTTP::HeaderMap const&, UnixDateTime request_time); Optional open_entry(URL::URL const&, StringView method); + void clear_cache(); LexicalPath const& cache_directory() { return m_cache_directory; } diff --git a/Services/RequestServer/ConnectionFromClient.cpp b/Services/RequestServer/ConnectionFromClient.cpp index 6461ce88954..95583e84509 100644 --- a/Services/RequestServer/ConnectionFromClient.cpp +++ b/Services/RequestServer/ConnectionFromClient.cpp @@ -861,6 +861,12 @@ void ConnectionFromClient::ensure_connection(URL::URL url, ::RequestServer::Cach } } +void ConnectionFromClient::clear_cache() +{ + if (g_disk_cache.has_value()) + g_disk_cache->clear_cache(); +} + void ConnectionFromClient::websocket_connect(i64 websocket_id, URL::URL url, ByteString origin, Vector protocols, Vector extensions, HTTP::HeaderMap additional_request_headers) { auto host = url.serialized_host().to_byte_string(); diff --git a/Services/RequestServer/ConnectionFromClient.h b/Services/RequestServer/ConnectionFromClient.h index 0a6baaa1618..7eafa4f1b0f 100644 --- a/Services/RequestServer/ConnectionFromClient.h +++ b/Services/RequestServer/ConnectionFromClient.h @@ -49,6 +49,8 @@ private: virtual Messages::RequestServer::SetCertificateResponse set_certificate(i32, ByteString, ByteString) override; virtual void ensure_connection(URL::URL url, ::RequestServer::CacheLevel cache_level) override; + virtual void clear_cache() override; + virtual void websocket_connect(i64 websocket_id, URL::URL, ByteString, Vector, Vector, HTTP::HeaderMap) override; virtual void websocket_send(i64 websocket_id, bool, ByteBuffer) override; virtual void websocket_close(i64 websocket_id, u16, ByteString) override; diff --git a/Services/RequestServer/RequestServer.ipc b/Services/RequestServer/RequestServer.ipc index ea4979d66b4..848efc91a97 100644 --- a/Services/RequestServer/RequestServer.ipc +++ b/Services/RequestServer/RequestServer.ipc @@ -22,6 +22,8 @@ endpoint RequestServer ensure_connection(URL::URL url, ::RequestServer::CacheLevel cache_level) =| + clear_cache() =| + // Websocket Connection API websocket_connect(i64 websocket_id, URL::URL url, ByteString origin, Vector protocols, Vector extensions, HTTP::HeaderMap additional_request_headers) =| websocket_send(i64 websocket_id, bool is_text, ByteBuffer data) =|