mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-18 15:52:21 +00:00
Write cache entries to a temporary file and rename them over the final path only after the entry has been fully flushed. This keeps previously mapped cache bodies tied to their original inode instead of exposing them to truncation when the same cache key is replaced. Clean up only the temporary file for incomplete writers so a failed replacement does not remove the still-valid existing cache entry. Add coverage that replaces an entry while an old body mapping remains live and verifies that it still exposes the original bytes.
177 lines
5.6 KiB
C++
177 lines
5.6 KiB
C++
/*
|
|
* Copyright (c) 2025-2026, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Error.h>
|
|
#include <AK/LexicalPath.h>
|
|
#include <AK/Optional.h>
|
|
#include <AK/String.h>
|
|
#include <AK/Time.h>
|
|
#include <AK/Types.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/Notifier.h>
|
|
#include <LibHTTP/Cache/Version.h>
|
|
#include <LibHTTP/Forward.h>
|
|
#include <LibHTTP/HeaderList.h>
|
|
|
|
namespace HTTP {
|
|
|
|
struct CacheHeader {
|
|
static ErrorOr<CacheHeader> read_from_stream(Stream&);
|
|
ErrorOr<void> write_to_stream(Stream&) const;
|
|
|
|
u32 hash() const;
|
|
|
|
static constexpr auto CACHE_MAGIC = 0xcafef00du;
|
|
|
|
u32 magic { CACHE_MAGIC };
|
|
u32 version { CACHE_VERSION };
|
|
|
|
u32 key_hash { 0 };
|
|
|
|
u32 url_size { 0 };
|
|
u32 url_hash { 0 };
|
|
|
|
u32 status_code { 0 };
|
|
u32 reason_phrase_size { 0 };
|
|
u32 reason_phrase_hash { 0 };
|
|
};
|
|
|
|
struct CacheFooter {
|
|
static ErrorOr<CacheFooter> read_from_stream(Stream&);
|
|
ErrorOr<void> write_to_stream(Stream&) const;
|
|
|
|
u64 data_size { 0 };
|
|
u32 header_hash { 0 };
|
|
};
|
|
|
|
struct CacheEntryBodyFile {
|
|
int fd { -1 };
|
|
u64 offset { 0 };
|
|
u64 size { 0 };
|
|
};
|
|
|
|
// A cache entry is an amalgamation of all information needed to reconstruct HTTP responses. It is created once we have
|
|
// received the response headers for a request. The body is streamed into the entry as it is received. The cache format
|
|
// on disk is:
|
|
//
|
|
// [CacheHeader][URL][ReasonPhrase][Data][CacheFooter]
|
|
class CacheEntry {
|
|
public:
|
|
virtual ~CacheEntry() = default;
|
|
|
|
u64 cache_key() const { return m_cache_key; }
|
|
u64 vary_key() const { return m_vary_key; }
|
|
u64 body_size() const { return m_cache_footer.data_size; }
|
|
|
|
void remove();
|
|
|
|
void mark_for_deletion(Badge<DiskCache>) { m_marked_for_deletion = true; }
|
|
|
|
protected:
|
|
CacheEntry(DiskCache&, CacheIndex&, u64 cache_key, u64 vary_key, String url, Optional<LexicalPath>, CacheHeader);
|
|
|
|
void close_and_destroy_cache_entry();
|
|
|
|
DiskCache& m_disk_cache;
|
|
CacheIndex& m_index;
|
|
|
|
u64 m_cache_key { 0 };
|
|
u64 m_vary_key { 0 };
|
|
|
|
String m_url;
|
|
Optional<LexicalPath> m_path;
|
|
|
|
CacheHeader m_cache_header;
|
|
CacheFooter m_cache_footer;
|
|
|
|
bool m_marked_for_deletion { false };
|
|
};
|
|
|
|
class CacheEntryWriter final : public CacheEntry {
|
|
public:
|
|
static ErrorOr<NonnullOwnPtr<CacheEntryWriter>> create(DiskCache&, CacheIndex&, u64 cache_key, String url, UnixDateTime request_time, AK::Duration current_time_offset_for_testing);
|
|
virtual ~CacheEntryWriter() override = default;
|
|
|
|
ErrorOr<void> write_status_and_reason(u32 status_code, Optional<String> reason_phrase, HeaderList const& request_headers, HeaderList const& response_headers);
|
|
ErrorOr<void> write_data(ReadonlyBytes);
|
|
ErrorOr<void> flush(NonnullRefPtr<HeaderList> request_headers, NonnullRefPtr<HeaderList> response_headers);
|
|
ErrorOr<CacheEntryBodyFile> flush_and_take_body_file(NonnullRefPtr<HeaderList> request_headers, NonnullRefPtr<HeaderList> response_headers);
|
|
void remove_incomplete_entry();
|
|
|
|
private:
|
|
CacheEntryWriter(DiskCache&, CacheIndex&, u64 cache_key, String url, CacheHeader, UnixDateTime request_time, AK::Duration current_time_offset_for_testing);
|
|
|
|
ErrorOr<void> flush_impl(NonnullRefPtr<HeaderList> request_headers, NonnullRefPtr<HeaderList> response_headers, CacheEntryBodyFile*);
|
|
void remove_incomplete_temporary_file();
|
|
|
|
OwnPtr<Core::OutputBufferedFile> m_file;
|
|
Optional<LexicalPath> m_temporary_path;
|
|
u64 m_data_offset { 0 };
|
|
|
|
UnixDateTime m_request_time;
|
|
UnixDateTime m_response_time;
|
|
|
|
AK::Duration m_current_time_offset_for_testing;
|
|
};
|
|
|
|
class CacheEntryReader final : public CacheEntry {
|
|
public:
|
|
static ErrorOr<NonnullOwnPtr<CacheEntryReader>> create(DiskCache&, CacheIndex&, u64 cache_key, u64 vary_key, NonnullRefPtr<HeaderList>, u64 data_size);
|
|
virtual ~CacheEntryReader() override = default;
|
|
|
|
enum class RevalidationType {
|
|
None,
|
|
MustRevalidate,
|
|
StaleWhileRevalidate,
|
|
};
|
|
RevalidationType revalidation_type() const { return m_revalidation_type; }
|
|
void set_revalidation_type(RevalidationType revalidation_type) { m_revalidation_type = revalidation_type; }
|
|
|
|
void revalidation_succeeded(HeaderList const&);
|
|
void revalidation_failed();
|
|
|
|
void send_to(int socket_fd, Function<void(u64 bytes_sent)> on_complete, Function<void(u64 bytes_sent)> on_error);
|
|
|
|
ErrorOr<CacheEntryBodyFile> take_body_file();
|
|
|
|
u64 body_size() const { return m_data_size; }
|
|
|
|
u32 status_code() const { return m_cache_header.status_code; }
|
|
Optional<String> const& reason_phrase() const { return m_reason_phrase; }
|
|
HeaderList& response_headers() { return m_response_headers; }
|
|
HeaderList const& response_headers() const { return m_response_headers; }
|
|
|
|
private:
|
|
CacheEntryReader(DiskCache&, CacheIndex&, u64 cache_key, u64 vary_key, String url, LexicalPath, NonnullOwnPtr<Core::File>, int fd, CacheHeader, Optional<String> reason_phrase, NonnullRefPtr<HeaderList>, u64 data_offset, u64 data_size);
|
|
|
|
void send_without_blocking();
|
|
void send_complete();
|
|
void send_error(Error);
|
|
|
|
ErrorOr<void> read_and_validate_footer();
|
|
|
|
NonnullOwnPtr<Core::File> m_file;
|
|
int m_fd { -1 };
|
|
|
|
RefPtr<Core::Notifier> m_socket_write_notifier;
|
|
int m_socket_fd { -1 };
|
|
|
|
Function<void(u64)> m_on_send_complete;
|
|
Function<void(u64)> m_on_send_error;
|
|
u64 m_bytes_sent { 0 };
|
|
|
|
Optional<String> m_reason_phrase;
|
|
NonnullRefPtr<HeaderList> m_response_headers;
|
|
|
|
RevalidationType m_revalidation_type { RevalidationType::None };
|
|
|
|
u64 const m_data_offset { 0 };
|
|
u64 const m_data_size { 0 };
|
|
};
|
|
|
|
}
|