Compare commits

...

32 commits

Author SHA1 Message Date
Daniel La Rocque
9dda07a421
Merge 84f0b4af2b into 63a5717bc7 2024-11-21 00:19:26 +01:00
Ali Mohammad Pur
63a5717bc7 LibDNS: Immediately resolve IPv4/IPv6 "hostnames" if A/AAAA is queried
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
This makes e.g. lookup(192.168.1.1, A) resolve to the IP instead of
querying DNS for the IP.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
c5afe58540 LibDNS: Add a default entry for localhost
In the future, we may want to parse /etc/hosts (or equivalent) into the
cache; this commit only adds localhost to make the normal workflow work.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
3bcd91b109 LibDNS: Hide some debug logs behind DNS_DEBUG 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7d1291b9f0 RequestServer: Implement the ResolveOnly EnsureConnection level 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
6911c45bab LibDNS: Respect records' TTL in the resolver cache 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
879ae94183 LibRequests: Don't crash on requests without a read stream finishing
This can now happen due to the hostname not existing, as RS explicitly
performs DNS resolution before setting up the response pipe.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7e20f4726f LibDNS+LibWeb+Ladybird+RequestServer: Let there be DNS over TLS
This commit adds our own DNS resolver, with the aim of implementing DoT
(and eventually DoH, maybe even DNSSEC etc.)
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7f72c28e78 LibHTTP: Make HeaderMap movable and copyable 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
d704b61066 LibCore+LibTLS: Add an API for connect()'ing 'with hostname
This just unifies the API for all three sockets (UDP, TCP and TLS)
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
b93d8ef875 AK: Disable implicit conversion from char* -> ipv4 -> ipv6
This is a footgun with some massive bullets.
2024-11-20 21:37:58 +01:00
Pavel Shliak
8a07131229 LibGfx: Clean up #include directives
We actually include what we use where we use it.
This change aims to improve the speed of incremental builds.
2024-11-20 21:13:23 +01:00
Andreas Kling
063cd68bf4 LibWeb: Mark image elements for layout before firing their load event
This removes a long-standing source of flakiness seen for example in
WPT's /referrer-policy/ tests.
2024-11-20 19:04:37 +01:00
Luke Wilde
f638f84185 LibWeb: Make default document readiness be "complete"
This is required by mini Cloudflare invisible challenges, as it will
only run if the readyState is not "loading". If it is "loading", then
it waits for readystatechange to check that it's not "loading" anymore.

Initial about:blank iframes do not go through the full navigation and
thus don't go through HTMLParser::the_end, which sets the ready state
to something other than "loading". Therefore, the challenge would never
run, as readyState would never change.

Seen on https://discord.com/login
2024-11-20 16:20:28 +01:00
Andreas Kling
4203b7823f LibWeb: Fix incorrect exception on replaceChild() with doctypes
We were checking for presence of the wrong child in the parent.
2024-11-20 16:10:57 +01:00
Andreas Kling
cd446e5e9c LibWeb: Set doctype node immediately while parsing XML document
Instead of deferring it to the end of parsing, where scripts that
were expecting to look at the doctype may have already run.
2024-11-20 16:10:57 +01:00
Andreas Kling
ab0dc83d28 LibWeb: Make Node.normalize() ignore CDATASection nodes
We hadn't modeled the "exclusive text node" concept correctly.
2024-11-20 16:10:57 +01:00
Nico Weber
6fc06f45c2 LibWeb: Plumbing for svg stroke-dashoffset 2024-11-20 15:57:37 +01:00
Nicolas Ramz
e98e9b8e81 Documentation: Fix typo in Testing markdown 2024-11-20 15:48:13 +01:00
Gingeh
4b1deb6fe1 LibWeb: Don't skip filtering when CSS contains null or surrogates 2024-11-20 15:47:19 +01:00
Jonne Ransijn
356507284e LibWeb: Compare font keys by reference
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
`StyleComputer::font_matching_algorithm` was creating a copy of a
`FlyString` every time a `MatchingFontCandidate` was constructed or
copied, causing millions of unnecessairy reference updates when a
lot of fonts are loaded.

While a more permanent solution would be to not load so many unused
fonts, let's do the right thing and remove the unnecessairy copies of
`FlyString`.
2024-11-20 15:38:03 +01:00
Jonne Ransijn
ec5ea0d686 LibGfx: Return family names by reference to avoid unnecessairy cloning 2024-11-20 15:38:03 +01:00
Pavel Shliak
b3c253e50f LibFileSystem: Clean up #include directives
This change aims to improve the speed of incremental builds.
2024-11-20 15:17:53 +01:00
Pavel Shliak
d0c0db5bb3 LibCompress: Clean up #include directives
This change aims to improve the speed of incremental builds.
2024-11-20 15:17:31 +01:00
Timothy Flynn
b99a3ec2df LibWeb: Clone CDATASection nodes with the correct node type
We were cloning these as plain Text nodes, but the clone must also be a
CDATASection node.
2024-11-20 15:15:56 +01:00
rmg-x
74b27d620d LibJS: Parse dates like "Wed Nov 20 2024" 2024-11-20 09:20:48 +00:00
stasoid
dabf3da7e5 LibCore: Fix bug in CreateFileMapping call
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
size >> 31 >> 1 is used instead of size >> 32 to support 32-bit Windows
(size_t is 32 bit there, and you cannot shift 32-bit value by 32 bits
on x86)
This is equivalent to sizeof(size) == 4 ? 0 : size >> 32
2024-11-19 22:07:01 -07:00
stasoid
11460b3daa LibCore: Fix ConfigFile.cpp compilation on Windows 2024-11-19 22:07:01 -07:00
stasoid
43056a8684 LibCore: Port Directory to Windows 2024-11-19 22:07:01 -07:00
stasoid
a423493dd8 AK: Add LexicalPath::is_root() 2024-11-19 22:07:01 -07:00
stasoid
77d205571d LibCore/System: Add mkdir, openat (stub), fstatat (stub) for Windows
Also support directories in open().
2024-11-19 22:07:01 -07:00
Daniel La Rocque
84f0b4af2b LibWeb: Add NullOrError to NavigationParamsVariant
To check whether a NavigationParams is null, we have to check whether
it's `Empty` or `NullWithError`. Instead, we can merge both of these
possible variants into an optional error. If `NullOrError` has no
value it's null, otherwise it contains an error message.
2024-11-16 16:48:35 -05:00
125 changed files with 3909 additions and 406 deletions

View file

@ -9,10 +9,8 @@
#include <AK/BitmapView.h>
#include <AK/Error.h>
#include <AK/Noncopyable.h>
#include <AK/Optional.h>
#include <AK/Platform.h>
#include <AK/StdLibExtras.h>
#include <AK/Try.h>
#include <AK/Types.h>
#include <AK/kmalloc.h>

View file

@ -50,6 +50,10 @@
# cmakedefine01 CSS_TRANSITIONS_DEBUG
#endif
#ifndef DNS_DEBUG
# cmakedefine01 DNS_DEBUG
#endif
#ifndef EDITOR_DEBUG
# cmakedefine01 EDITOR_DEBUG
#endif

View file

@ -29,6 +29,15 @@ public:
m_data[i] = data[i];
}
constexpr IPv6Address(Array<u8, 16> const& data)
{
for (size_t i = 0; i < 16; i++)
m_data[i] = data[i];
}
template<SameAs<char const*> T>
constexpr IPv6Address(T const&) = delete; // Disable implicit conversion of char const* -> ipv4 -> ipv6
constexpr IPv6Address(IPv4Address const& ipv4_address)
{
// IPv4 mapped IPv6 address

View file

@ -63,6 +63,11 @@ bool LexicalPath::is_absolute_path(StringView path)
return path.starts_with('/');
}
bool LexicalPath::is_root() const
{
return m_string == "/";
}
Vector<ByteString> LexicalPath::parts() const
{
Vector<ByteString> vector;

View file

@ -28,6 +28,8 @@ public:
static bool is_absolute_path(StringView path);
bool is_absolute() const { return is_absolute_path(m_string); }
bool is_root() const;
ByteString const& string() const { return m_string; }
StringView dirname() const { return m_dirname; }

View file

@ -45,6 +45,11 @@ bool LexicalPath::is_absolute_path(StringView path)
return path.length() >= 2 && path[1] == ':';
}
bool LexicalPath::is_root() const
{
return AK::is_root(m_parts);
}
Vector<ByteString> LexicalPath::parts() const
{
Vector<ByteString> vector;
@ -81,7 +86,7 @@ ByteString LexicalPath::canonicalized_path(ByteString path)
continue;
if (part == ".." && !canonical_parts.is_empty()) {
// At the root, .. does nothing.
if (is_root(canonical_parts))
if (AK::is_root(canonical_parts))
continue;
// A .. and a previous non-.. part cancel each other.
if (canonical_parts.last() != "..") {
@ -95,7 +100,7 @@ ByteString LexicalPath::canonicalized_path(ByteString path)
StringBuilder builder;
builder.join('\\', canonical_parts);
// "X:" -> "X:\"
if (is_root(canonical_parts))
if (AK::is_root(canonical_parts))
builder.append('\\');
path = builder.to_byte_string();
return path == "" ? "." : path;

View file

@ -1,6 +1,6 @@
# Testing Ladybird
Tests are locates in `Tests/`, with a directory for each library.
Tests are located in `Tests/`, with a directory for each library.
Every feature or bug fix added to LibWeb should have a corresponding test in `Tests/LibWeb`.
The test should be either a Text, Layout, Ref, or Screenshot test depending on the feature.

View file

@ -9,8 +9,6 @@
#include <AK/Assertions.h>
#include <AK/BinarySearch.h>
#include <AK/MemoryStream.h>
#include <string.h>
#include <LibCompress/Deflate.h>
#include <LibCompress/Huffman.h>

View file

@ -11,9 +11,6 @@
#include <AK/MemoryStream.h>
#include <AK/String.h>
#include <LibCore/DateTime.h>
#include <LibCore/File.h>
#include <LibCore/MappedFile.h>
#include <LibCore/System.h>
namespace Compress {

View file

@ -28,7 +28,7 @@ AnonymousBufferImpl::~AnonymousBufferImpl()
ErrorOr<NonnullRefPtr<AnonymousBufferImpl>> AnonymousBufferImpl::create(size_t size)
{
HANDLE map_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, HIWORD(size), LOWORD(size), NULL);
HANDLE map_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, size >> 31 >> 1, size & 0xFFFFFFFF, NULL);
if (!map_handle)
return Error::from_windows_error(GetLastError());

View file

@ -12,8 +12,6 @@
#include <LibCore/Directory.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <pwd.h>
#include <sys/types.h>
namespace Core {

View file

@ -4,10 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Directory.h"
#include "DirIterator.h"
#include "System.h"
#include <dirent.h>
#include <LibCore/Directory.h>
#include <LibCore/System.h>
namespace Core {
@ -31,6 +29,7 @@ Directory::~Directory()
MUST(System::close(m_directory_fd));
}
#ifndef AK_OS_WINDOWS
ErrorOr<void> Directory::chown(uid_t uid, gid_t gid)
{
if (m_directory_fd == -1)
@ -38,6 +37,7 @@ ErrorOr<void> Directory::chown(uid_t uid, gid_t gid)
TRY(Core::System::fchown(m_directory_fd, uid, gid));
return {};
}
#endif
ErrorOr<bool> Directory::is_valid_directory(int fd)
{
@ -69,7 +69,7 @@ ErrorOr<Directory> Directory::create(LexicalPath path, CreateDirectories create_
ErrorOr<void> Directory::ensure_directory(LexicalPath const& path, mode_t creation_mode)
{
if (path.basename() == "/" || path.basename() == ".")
if (path.is_root() || path.string() == ".")
return {};
TRY(ensure_directory(path.parent(), creation_mode));

View file

@ -10,14 +10,11 @@
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Function.h>
#include <AK/IterationDecision.h>
#include <AK/LexicalPath.h>
#include <AK/Noncopyable.h>
#include <AK/Optional.h>
#include <LibCore/DirIterator.h>
#include <LibCore/DirectoryEntry.h>
#include <LibCore/File.h>
#include <dirent.h>
#include <sys/stat.h>
namespace Core {
@ -51,7 +48,9 @@ public:
static ErrorOr<void> for_each_entry(StringView path, DirIterator::Flags, ForEachEntryCallback);
ErrorOr<void> for_each_entry(DirIterator::Flags, ForEachEntryCallback);
#ifndef AK_OS_WINDOWS
ErrorOr<void> chown(uid_t, gid_t);
#endif
static ErrorOr<bool> is_valid_directory(int fd);

View file

@ -159,6 +159,7 @@ class TCPSocket final : public Socket {
public:
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(ByteString const& host, u16 port);
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(SocketAddress const& address);
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(SocketAddress const& address, ByteString const&) { return connect(address); }
static ErrorOr<NonnullOwnPtr<TCPSocket>> adopt_fd(int fd);
TCPSocket(TCPSocket&& other)
@ -220,6 +221,7 @@ class UDPSocket final : public Socket {
public:
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(ByteString const& host, u16 port, Optional<AK::Duration> timeout = {});
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(SocketAddress const& address, Optional<AK::Duration> timeout = {});
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(SocketAddress const& address, ByteString const&, Optional<AK::Duration> timeout = {}) { return connect(address, timeout); }
UDPSocket(UDPSocket&& other)
: Socket(static_cast<Socket&&>(other))

View file

@ -12,7 +12,8 @@
#include <AK/ByteString.h>
#include <AK/ScopeGuard.h>
#include <LibCore/System.h>
#include <WinSock2.h>
#include <Windows.h>
#include <direct.h>
#include <io.h>
namespace Core::System {
@ -20,9 +21,21 @@ namespace Core::System {
ErrorOr<int> open(StringView path, int options, mode_t mode)
{
ByteString string_path = path;
int rc = _open(string_path.characters(), options, mode);
if (rc < 0)
return Error::from_syscall("open"sv, -errno);
auto sz_path = string_path.characters();
int rc = _open(sz_path, options, mode);
if (rc < 0) {
int error = errno;
struct stat st = {};
if (::stat(sz_path, &st) == 0 && (st.st_mode & S_IFDIR)) {
HANDLE dir_handle = CreateFile(sz_path, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (dir_handle == INVALID_HANDLE_VALUE)
return Error::from_windows_error(GetLastError());
int dir_fd = _open_osfhandle((intptr_t)dir_handle, 0);
if (dir_fd != -1)
return dir_fd;
}
return Error::from_syscall("open"sv, -error);
}
return rc;
}
@ -133,4 +146,24 @@ ErrorOr<void> unlink(StringView path)
return {};
}
ErrorOr<void> mkdir(StringView path, mode_t)
{
ByteString str = path;
if (_mkdir(str.characters()) < 0)
return Error::from_syscall("mkdir"sv, -errno);
return {};
}
ErrorOr<int> openat(int, StringView, int, mode_t)
{
dbgln("Core::System::openat() is not implemented");
VERIFY_NOT_REACHED();
}
ErrorOr<struct stat> fstatat(int, StringView, int)
{
dbgln("Core::System::fstatat() is not implemented");
VERIFY_NOT_REACHED();
}
}

View file

@ -0,0 +1,6 @@
set(SOURCES
Message.cpp
)
serenity_lib(LibDNS dns)
target_link_libraries(LibDNS PRIVATE LibCore)

1177
Libraries/LibDNS/Message.cpp Normal file

File diff suppressed because it is too large Load diff

704
Libraries/LibDNS/Message.h Normal file
View file

@ -0,0 +1,704 @@
/*
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Base64.h>
#include <AK/MaybeOwned.h>
#include <AK/RedBlackTree.h>
#include <AK/Time.h>
#include <LibCore/Promise.h>
#include <LibCore/SocketAddress.h>
#include <LibURL/URL.h>
namespace DNS {
namespace Messages {
struct DomainName;
struct ParseContext {
CountingStream& stream;
NonnullOwnPtr<RedBlackTree<u16, DomainName>> pointers;
};
enum class OpCode : u8;
struct Options {
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ID |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |QR| Opcode |AA|TC|RD|RA| Z |AD|CD| RCODE |
constexpr inline static u16 QRMask = 0b1000000000000000;
constexpr inline static u16 OpCodeMask = 0b0111100000000000;
constexpr inline static u16 AuthoritativeAnswerMask = 0b0000010000000000;
constexpr inline static u16 TruncatedMask = 0b0000001000000000;
constexpr inline static u16 RecursionDesiredMask = 0b0000000100000000;
constexpr inline static u16 RecursionAvailableMask = 0b0000000010000000;
constexpr inline static u16 AuthenticatedDataMask = 0b0000000000100000;
constexpr inline static u16 CheckingDisabledMask = 0b0000000000010000;
constexpr inline static u16 ResponseCodeMask = 0b0000000000001111;
enum class ResponseCode : u16 {
NoError = 0,
FormatError = 1,
ServerFailure = 2,
NameError = 3,
NotImplemented = 4,
Refused = 5,
};
void set_is_question(bool value) { raw = (raw & ~QRMask) | (value ? QRMask : 0); }
void set_is_authoritative_answer(bool value) { raw = (raw & ~AuthoritativeAnswerMask) | (value ? AuthoritativeAnswerMask : 0); }
void set_is_truncated(bool value) { raw = (raw & ~TruncatedMask) | (value ? TruncatedMask : 0); }
void set_recursion_desired(bool value) { raw = (raw & ~RecursionDesiredMask) | (value ? RecursionDesiredMask : 0); }
void set_recursion_available(bool value) { raw = (raw & ~RecursionAvailableMask) | (value ? RecursionAvailableMask : 0); }
void set_response_code(ResponseCode code) { raw = (raw & ~ResponseCodeMask) | static_cast<u16>(code); }
void set_checking_disabled(bool value) { raw = (raw & ~CheckingDisabledMask) | (value ? CheckingDisabledMask : 0); }
void set_authenticated_data(bool value) { raw = (raw & ~AuthenticatedDataMask) | (value ? AuthenticatedDataMask : 0); }
void set_op_code(OpCode code) { raw = (raw & ~OpCodeMask) | (static_cast<u16>(code) << 11); }
bool is_question() const { return (raw & QRMask) == 0; }
bool is_authoritative_answer() const { return (raw & AuthoritativeAnswerMask) != 0; }
bool is_truncated() const { return (raw & TruncatedMask) != 0; }
bool recursion_desired() const { return (raw & RecursionDesiredMask) != 0; }
bool recursion_available() const { return (raw & RecursionAvailableMask) != 0; }
bool checking_disabled() const { return (raw & CheckingDisabledMask) != 0; }
bool authenticated_data() const { return (raw & AuthenticatedDataMask) != 0; }
ResponseCode response_code() const { return static_cast<ResponseCode>(raw & ResponseCodeMask); }
OpCode op_code() const { return static_cast<OpCode>((raw & OpCodeMask) >> 11); }
String to_string() const;
NetworkOrdered<u16> raw { 0 };
};
StringView to_string(Options::ResponseCode);
struct Header {
NetworkOrdered<u16> id;
Options options;
NetworkOrdered<u16> question_count;
NetworkOrdered<u16> answer_count;
NetworkOrdered<u16> authority_count;
NetworkOrdered<u16> additional_count;
};
struct DomainName {
Vector<ByteString> labels;
static DomainName from_string(StringView);
static ErrorOr<DomainName> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
String to_string() const;
};
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4.
enum class ResourceType : u16 {
Reserved = 0, // [RFC6895]
A = 1, // a host address [RFC1035]
NS = 2, // an authoritative name server [RFC1035]
MD = 3, // a mail destination (OBSOLETE - use MX) [RFC1035]
MF = 4, // a mail forwarder (OBSOLETE - use MX) [RFC1035]
CNAME = 5, // the canonical name for an alias [RFC1035]
SOA = 6, // marks the start of a zone of authority [RFC1035]
MB = 7, // a mailbox domain name (EXPERIMENTAL) [RFC1035]
MG = 8, // a mail group member (EXPERIMENTAL) [RFC1035]
MR = 9, // a mail rename domain name (EXPERIMENTAL) [RFC1035]
NULL_ = 10, // a null RR (EXPERIMENTAL) [RFC1035]
WKS = 11, // a well known service description [RFC1035]
PTR = 12, // a domain name pointer [RFC1035]
HINFO = 13, // host information [RFC1035]
MINFO = 14, // mailbox or mail list information [RFC1035]
MX = 15, // mail exchange [RFC1035]
TXT = 16, // text strings [RFC1035]
RP = 17, // for Responsible Person [RFC1183]
AFSDB = 18, // for AFS Data Base location [RFC1183][RFC5864]
X25 = 19, // for X.25 PSDN address [RFC1183]
ISDN = 20, // for ISDN address [RFC1183]
RT = 21, // for Route Through [RFC1183]
NSAP = 22, // for NSAP address, NSAP style A record (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
NSAP_PTR = 23, // for domain name pointer, NSAP style (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
SIG = 24, // for security signature [RFC2536][RFC2931][RFC3110][RFC4034]
KEY = 25, // for security key [RFC2536][RFC2539][RFC3110][RFC4034]
PX = 26, // X.400 mail mapping information [RFC2163]
GPOS = 27, // Geographical Position [RFC1712]
AAAA = 28, // IP6 Address [RFC3596]
LOC = 29, // Location Information [RFC1876]
NXT = 30, // Next Domain (OBSOLETE) [RFC2535][RFC3755]
EID = 31, // Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
NIMLOC = 32, // Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
SRV = 33, // Server Selection [1][RFC2782]
ATMA = 34, // ATM Address "[ ATM Forum Technical Committee, "ATM Name System, V2.0", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]"
NAPTR = 35, // Naming Authority Pointer [RFC3403]
KX = 36, // Key Exchanger [RFC2230]
CERT = 37, // CERT [RFC4398]
A6 = 38, // A6 (OBSOLETE - use AAAA) [RFC2874][RFC3226][RFC6563]
DNAME = 39, // DNAME [RFC6672]
SINK = 40, // SINK [Donald_E_Eastlake][draft-eastlake-kitchen-sink]
OPT = 41, // OPT [RFC3225][RFC6891]
APL = 42, // APL [RFC3123]
DS = 43, // Delegation Signer [RFC4034]
SSHFP = 44, // SSH Key Fingerprint [RFC4255]
IPSECKEY = 45, // IPSECKEY [RFC4025]
RRSIG = 46, // RRSIG [RFC4034]
NSEC = 47, // NSEC [RFC4034][RFC9077]
DNSKEY = 48, // DNSKEY [RFC4034]
DHCID = 49, // DHCID [RFC4701]
NSEC3 = 50, // NSEC3 [RFC5155][RFC9077]
NSEC3PARAM = 51, // NSEC3PARAM [RFC5155]
TLSA = 52, // TLSA [RFC6698]
SMIMEA = 53, // S/MIME cert association [RFC8162]
HIP = 55, // Host Identity Protocol [RFC8005]
NINFO = 56, // NINFO [Jim_Reid]
RKEY = 57, // RKEY [Jim_Reid]
TALINK = 58, // Trust Anchor LINK [Wouter_Wijngaards]
CDS = 59, // Child DS [RFC7344]
CDNSKEY = 60, // DNSKEY(s) the Child wants reflected in DS [RFC7344]
OPENPGPKEY = 61, // OpenPGP Key [RFC7929]
CSYNC = 62, // Child-To-Parent Synchronization [RFC7477]
ZONEMD = 63, // Message Digest Over Zone Data [RFC8976]
SVCB = 64, // General-purpose service binding [RFC9460]
HTTPS = 65, // SVCB-compatible type for use with HTTP [RFC9460]
SPF = 99, // [RFC7208]
UINFO = 100, // [IANA-Reserved]
UID = 101, // [IANA-Reserved]
GID = 102, // [IANA-Reserved]
UNSPEC = 103, // [IANA-Reserved]
NID = 104, // [RFC6742]
L32 = 105, // [RFC6742]
L64 = 106, // [RFC6742]
LP = 107, // [RFC6742]
EUI48 = 108, // an EUI-48 address [RFC7043]
EUI64 = 109, // an EUI-64 address [RFC7043]
NXNAME = 128, // NXDOMAIN indicator for Compact Denial of Existence [draft-ietf-dnsop-compact-denial-of-existence-04]
TKEY = 249, // Transaction Key [RFC2930]
TSIG = 250, // Transaction Signature [RFC8945]
IXFR = 251, // incremental transfer [RFC1995]
AXFR = 252, // transfer of an entire zone [RFC1035][RFC5936]
MAILB = 253, // mailbox-related RRs (MB, MG or MR) [RFC1035]
MAILA = 254, // mail agent RRs (OBSOLETE - see MX) [RFC1035]
ANY = 255, // A request for some or all records the server has available [RFC1035][RFC6895][RFC8482]
URI = 256, // URI [RFC7553]
CAA = 257, // Certification Authority Restriction [RFC8659]
AVC = 258, // Application Visibility and Control [Wolfgang_Riedel]
DOA = 259, // Digital Object Architecture [draft-durand-doa-over-dns]
AMTRELAY = 260, // Automatic Multicast Tunneling Relay [RFC8777]
RESINFO = 261, // Resolver Information as Key/Value Pairs [RFC9606]
WALLET = 262, // Public wallet address [Paul_Hoffman]
CLA = 263, // BP Convergence Layer Adapter [draft-johnson-dns-ipn-cla-07]
IPN = 264, // BP Node Number [draft-johnson-dns-ipn-cla-07]
TA = 32768, // DNSSEC Trust Authorities "[Sam_Weiler][Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.]"
DLV = 32769, // DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431]
};
StringView to_string(ResourceType);
Optional<ResourceType> resource_type_from_string(StringView);
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2.
enum class Class : u16 {
IN = 1, // the Internet [RFC1035]
CH = 3, // the CHAOS class [Moon1981]
HS = 4, // Hesiod [Dyer1987]
};
StringView to_string(Class);
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-3.
enum class OpCode : u8 {
Query = 0, // a standard query (QUERY)
IQuery = 1, // an inverse query (IQUERY)
Status = 2, // a server status request (STATUS)
Notify = 4, // NOTIFY
Update = 5, // dynamic update (RFC 2136)
DSO = 6, // DNS Stateful Operations (DSO) [RFC8490]
Reserved = 7, // [RFC6895]
ReservedMask = 15 // [RFC6895]
};
StringView to_string(OpCode);
namespace TLSA {
// Listings from IANA https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml.
enum class CertUsage : u8 {
CAConstraint = 0,
ServiceCertificateConstraint = 1,
TrustAnchorAssertion = 2,
DomainIssuedCertificate = 3,
Private = 255
};
enum class Selector : u8 {
FullCertificate = 0,
SubjectPublicKeyInfo = 1,
Private = 255
};
enum class MatchingType : u8 {
Full = 0,
SHA256 = 1,
SHA512 = 2,
Private = 255
};
}
namespace DNSSEC {
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class Algorithm : u8 {
RSAMD5 = 1, // RSA/MD5 [RFC4034][RFC3110]
DSA = 3, // DSA/SHA-1 [RFC3755][RFC2536]
RSASHA1 = 5, // RSA/SHA-1 [RFC3110]
RSASHA1NSEC3SHA1 = 7, // [RFC5155]
RSASHA256 = 8, // RSA/SHA-256 [RFC5702]
RSASHA512 = 10, // RSA/SHA-512 [RFC5702]
ECDSAP256SHA256 = 13, // ECDSA Curve P-256 with SHA-256 [RFC6605]
ECDSAP384SHA384 = 14, // ECDSA Curve P-384 with SHA-384 [RFC6605]
ED25519 = 15, // Ed25519 [RFC8080]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(Algorithm algorithm)
{
switch (algorithm) {
case Algorithm::RSAMD5:
return "RSAMD5"sv;
case Algorithm::DSA:
return "DSA"sv;
case Algorithm::RSASHA1:
return "RSASHA1"sv;
case Algorithm::RSASHA1NSEC3SHA1:
return "RSASHA1NSEC3SHA1"sv;
case Algorithm::RSASHA256:
return "RSASHA256"sv;
case Algorithm::RSASHA512:
return "RSASHA512"sv;
case Algorithm::ECDSAP256SHA256:
return "ECDSAP256SHA256"sv;
case Algorithm::ECDSAP384SHA384:
return "ECDSAP384SHA384"sv;
case Algorithm::ED25519:
return "ED25519"sv;
case Algorithm::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class DigestType : u8 {
SHA1 = 1, // SHA-1 [RFC3658]
SHA256 = 2, // SHA-256 [RFC4509]
GOST3411 = 3, // GOST R 34.11-94 [RFC5933]
SHA384 = 4, // SHA-384 [RFC6605]
SHA512 = 5, // SHA-512 [RFC6605]
SHA224 = 6, // SHA-224 [RFC6605]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(DigestType digest_type)
{
switch (digest_type) {
case DigestType::SHA1:
return "SHA1"sv;
case DigestType::SHA256:
return "SHA256"sv;
case DigestType::GOST3411:
return "GOST3411"sv;
case DigestType::SHA384:
return "SHA384"sv;
case DigestType::SHA512:
return "SHA512"sv;
case DigestType::SHA224:
return "SHA224"sv;
case DigestType::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class NSEC3HashAlgorithm : u8 {
SHA1 = 1, // [RFC5155]
SHA256 = 2, // [RFC6605]
GOST3411 = 3, // [RFC5933]
SHA384 = 4, // [RFC6605]
SHA512 = 5, // [RFC6605]
SHA224 = 6, // [RFC6605]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(NSEC3HashAlgorithm hash_algorithm)
{
switch (hash_algorithm) {
case NSEC3HashAlgorithm::SHA1:
return "SHA1"sv;
case NSEC3HashAlgorithm::SHA256:
return "SHA256"sv;
case NSEC3HashAlgorithm::GOST3411:
return "GOST3411"sv;
case NSEC3HashAlgorithm::SHA384:
return "SHA384"sv;
case NSEC3HashAlgorithm::SHA512:
return "SHA512"sv;
case NSEC3HashAlgorithm::SHA224:
return "SHA224"sv;
case NSEC3HashAlgorithm::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
}
struct Question {
DomainName name;
ResourceType type;
Class class_;
static ErrorOr<Question> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
};
namespace Records {
struct A {
IPv4Address address;
static constexpr ResourceType type = ResourceType::A;
static ErrorOr<A> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return address.to_string(); }
};
struct AAAA {
IPv6Address address;
static constexpr ResourceType type = ResourceType::AAAA;
static ErrorOr<AAAA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return address.to_string(); }
};
struct TXT {
ByteString content;
static constexpr ResourceType type = ResourceType::TXT;
static ErrorOr<TXT> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("Text: '{}'", StringView { content }); }
};
struct CNAME {
DomainName names;
static constexpr ResourceType type = ResourceType::CNAME;
static ErrorOr<CNAME> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return names.to_string(); }
};
struct NS {
DomainName name;
static constexpr ResourceType type = ResourceType::NS;
static ErrorOr<NS> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return name.to_string(); }
};
struct SOA {
DomainName mname;
DomainName rname;
u32 serial;
u32 refresh;
u32 retry;
u32 expire;
u32 minimum;
static constexpr ResourceType type = ResourceType::SOA;
static ErrorOr<SOA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const
{
return String::formatted("SOA MName: '{}', RName: '{}', Serial: {}, Refresh: {}, Retry: {}, Expire: {}, Minimum: {}", mname.to_string(), rname.to_string(), serial, refresh, retry, expire, minimum);
}
};
struct MX {
u16 preference;
DomainName exchange;
static constexpr ResourceType type = ResourceType::MX;
static ErrorOr<MX> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("MX Preference: {}, Exchange: '{}'", preference, exchange.to_string()); }
};
struct PTR {
DomainName name;
static constexpr ResourceType type = ResourceType::PTR;
static ErrorOr<PTR> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return name.to_string(); }
};
struct SRV {
u16 priority;
u16 weight;
u16 port;
DomainName target;
static constexpr ResourceType type = ResourceType::SRV;
static ErrorOr<SRV> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("SRV Priority: {}, Weight: {}, Port: {}, Target: '{}'", priority, weight, port, target.to_string()); }
};
struct DNSKEY {
u16 flags;
u8 protocol;
DNSSEC::Algorithm algorithm;
ByteBuffer public_key;
constexpr static inline u16 FlagSecureEntryPoint = 0b1000000000000000;
constexpr static inline u16 FlagZoneKey = 0b0100000000000000;
constexpr static inline u16 FlagRevoked = 0b0010000000000000;
constexpr bool is_secure_entry_point() const { return flags & FlagSecureEntryPoint; }
constexpr bool is_zone_key() const { return flags & FlagZoneKey; }
constexpr bool is_revoked() const { return flags & FlagRevoked; }
constexpr bool is_key_signing_key() const { return is_secure_entry_point() && is_zone_key() && !is_revoked(); }
static constexpr ResourceType type = ResourceType::DNSKEY;
static ErrorOr<DNSKEY> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const
{
return String::formatted("DNSKEY Flags: {}{}{}{}({}), Protocol: {}, Algorithm: {}, Public Key: {}",
is_secure_entry_point() ? "sep "sv : ""sv,
is_zone_key() ? "zone "sv : ""sv,
is_revoked() ? "revoked "sv : ""sv,
is_key_signing_key() ? "ksk "sv : ""sv,
flags,
protocol,
DNSSEC::to_string(algorithm),
TRY(encode_base64(public_key)));
}
};
struct CDNSKEY : public DNSKEY {
template<typename... Ts>
CDNSKEY(Ts&&... args)
: DNSKEY(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::CDNSKEY;
static ErrorOr<CDNSKEY> from_raw(ParseContext& raw) { return DNSKEY::from_raw(raw); }
};
struct DS {
u16 key_tag;
DNSSEC::Algorithm algorithm;
DNSSEC::DigestType digest_type;
ByteBuffer digest;
static constexpr ResourceType type = ResourceType::DS;
static ErrorOr<DS> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "DS"_string; }
};
struct CDS : public DS {
template<typename... Ts>
CDS(Ts&&... args)
: DS(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::CDS;
static ErrorOr<CDS> from_raw(ParseContext& raw) { return DS::from_raw(raw); }
};
struct SIG {
ResourceType type_covered;
DNSSEC::Algorithm algorithm;
u8 label_count;
u32 original_ttl;
UnixDateTime expiration;
UnixDateTime inception;
u16 key_tag;
DomainName signers_name;
ByteBuffer signature;
static constexpr ResourceType type = ResourceType::SIG;
static ErrorOr<SIG> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const;
};
struct RRSIG : public SIG {
template<typename... Ts>
RRSIG(Ts&&... args)
: SIG(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::RRSIG;
static ErrorOr<RRSIG> from_raw(ParseContext& raw) { return SIG::from_raw(raw); }
};
struct NSEC {
DomainName next_domain_name;
Vector<ResourceType> types;
static constexpr ResourceType type = ResourceType::NSEC;
static ErrorOr<NSEC> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC"_string; }
};
struct NSEC3 {
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
u8 flags;
u16 iterations;
ByteBuffer salt;
DomainName next_hashed_owner_name;
Vector<ResourceType> types;
static constexpr ResourceType type = ResourceType::NSEC3;
static ErrorOr<NSEC3> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC3"_string; }
};
struct NSEC3PARAM {
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
u8 flags;
u16 iterations;
ByteBuffer salt;
constexpr static inline u8 FlagOptOut = 0b10000000;
constexpr bool is_opt_out() const { return flags & FlagOptOut; }
static constexpr ResourceType type = ResourceType::NSEC3PARAM;
static ErrorOr<NSEC3PARAM> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC3PARAM"_string; }
};
struct TLSA {
Messages::TLSA::CertUsage cert_usage;
Messages::TLSA::Selector selector;
Messages::TLSA::MatchingType matching_type;
ByteBuffer certificate_association_data;
static ErrorOr<TLSA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "TLSA"_string; }
};
struct HINFO {
ByteString cpu;
ByteString os;
static constexpr ResourceType type = ResourceType::HINFO;
static ErrorOr<HINFO> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("HINFO CPU: '{}', OS: '{}'", StringView { cpu }, StringView { os }); }
};
struct OPT {
struct Option {
u16 code;
ByteBuffer data;
};
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | UDP Payload Size |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | Extended RCode | VER | ZZ |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |DO| Z |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | OPT-LEN / OPT-DATA...
NetworkOrdered<u16> udp_payload_size { 0 };
NetworkOrdered<u32> extended_rcode_and_flags { 0 };
Vector<Option> options;
static constexpr u32 MaskExtendedRCode = 0b11111111000000000000000000000000;
static constexpr u32 MaskVersion = 0b00000000111100000000000000000000;
static constexpr u32 MaskDO = 0b00000000000000001000000000000000;
static constexpr ResourceType type = ResourceType::OPT;
constexpr u8 extended_rcode() const { return (extended_rcode_and_flags & MaskExtendedRCode) >> 24; }
constexpr u8 version() const { return (extended_rcode_and_flags & MaskVersion) >> 20; }
constexpr bool dnssec_ok() const { return extended_rcode_and_flags & MaskDO; }
void set_extended_rcode(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskExtendedRCode) | (value << 24); }
void set_version(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskVersion) | (value << 20); }
void set_dnssec_ok(bool value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskDO) | (value ? MaskDO : 0); }
static ErrorOr<OPT> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
ErrorOr<String> to_string() const
{
StringBuilder builder;
builder.appendff("OPT UDP Payload Size: {}, Extended RCode: {}, Version: {}, DNSSEC OK: {}", udp_payload_size, extended_rcode(), version(), dnssec_ok());
for (auto& option : options)
builder.appendff(", opt[{} = '{:hex-dump}']", option.code, option.data.bytes());
return builder.to_string();
}
};
}
using Record = Variant<
Records::A,
Records::AAAA,
Records::TXT,
Records::CNAME,
Records::NS,
Records::SOA,
Records::MX,
Records::PTR,
Records::SRV,
Records::DNSKEY,
Records::CDNSKEY,
Records::DS,
Records::CDS,
Records::RRSIG,
Records::NSEC,
Records::NSEC3,
Records::NSEC3PARAM,
Records::TLSA,
Records::HINFO,
Records::OPT,
// TODO: Add more records.
ByteBuffer>; // Fallback for unknown records.
struct ResourceRecord {
DomainName name;
ResourceType type;
Class class_;
u32 ttl;
Record record;
Optional<ByteBuffer> raw;
static ErrorOr<ResourceRecord> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
ErrorOr<String> to_string() const;
};
struct ZoneAuthority {
DomainName name;
ByteString admin_mailbox;
u32 serial;
u32 refresh;
u32 retry;
u32 expire;
u32 minimum_ttl;
};
struct Message {
Header header;
Vector<Question> questions;
Vector<ResourceRecord> answers;
Vector<ResourceRecord> authorities;
Vector<ResourceRecord> additional_records;
static ErrorOr<Message> from_raw(ParseContext&);
static ErrorOr<Message> from_raw(Stream&);
ErrorOr<size_t> to_raw(ByteBuffer&) const;
ErrorOr<String> format_for_log() const;
};
}
}

488
Libraries/LibDNS/Resolver.h Normal file
View file

@ -0,0 +1,488 @@
/*
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/AtomicRefCounted.h>
#include <AK/HashTable.h>
#include <AK/MemoryStream.h>
#include <AK/Random.h>
#include <AK/StringView.h>
#include <AK/TemporaryChange.h>
#include <LibCore/DateTime.h>
#include <LibCore/Promise.h>
#include <LibCore/SocketAddress.h>
#include <LibDNS/Message.h>
#include <LibThreading/MutexProtected.h>
#include <LibThreading/RWLockProtected.h>
namespace DNS {
class Resolver;
class LookupResult : public AtomicRefCounted<LookupResult>
, public Weakable<LookupResult> {
public:
explicit LookupResult(Messages::DomainName name)
: m_name(move(name))
{
}
Vector<Variant<IPv4Address, IPv6Address>> cached_addresses() const
{
Vector<Variant<IPv4Address, IPv6Address>> result;
for (auto& re : m_cached_records) {
re.record.record.visit(
[&](Messages::Records::A const& a) { result.append(a.address); },
[&](Messages::Records::AAAA const& aaaa) { result.append(aaaa.address); },
[](auto&) {});
}
return result;
}
void check_expiration()
{
if (!m_valid)
return;
auto now = Core::DateTime::now();
for (size_t i = 0; i < m_cached_records.size();) {
auto& record = m_cached_records[i];
if (record.expiration.has_value() && record.expiration.value() < now) {
dbgln_if(DNS_DEBUG, "DNS: Removing expired record for {}", m_name.to_string());
m_cached_records.remove(i);
} else {
dbgln_if(DNS_DEBUG, "DNS: Keeping record for {} (expires in {})", m_name.to_string(), record.expiration.has_value() ? record.expiration.value().to_string() : "never"_string);
++i;
}
}
if (m_cached_records.is_empty())
m_valid = false;
}
void add_record(Messages::ResourceRecord record)
{
m_valid = true;
auto expiration = record.ttl > 0 ? Optional<Core::DateTime>(Core::DateTime::from_timestamp(Core::DateTime::now().timestamp() + record.ttl)) : OptionalNone();
m_cached_records.append({ move(record), move(expiration) });
}
Vector<Messages::ResourceRecord> records() const
{
Vector<Messages::ResourceRecord> result;
for (auto& re : m_cached_records)
result.append(re.record);
return result;
}
bool has_record_of_type(Messages::ResourceType type, bool later = false) const
{
if (later && m_desired_types.contains(type))
return true;
for (auto const& re : m_cached_records) {
if (re.record.type == type)
return true;
}
return false;
}
void will_add_record_of_type(Messages::ResourceType type) { m_desired_types.set(type); }
void set_id(u16 id) { m_id = id; }
u16 id() { return m_id; }
bool is_valid() const { return m_valid; }
Messages::DomainName const& name() const { return m_name; }
private:
bool m_valid { false };
Messages::DomainName m_name;
struct RecordWithExpiration {
Messages::ResourceRecord record;
Optional<Core::DateTime> expiration;
};
Vector<RecordWithExpiration> m_cached_records;
HashTable<Messages::ResourceType> m_desired_types;
u16 m_id { 0 };
};
class Resolver {
public:
enum class ConnectionMode {
TCP,
UDP,
};
struct SocketResult {
MaybeOwned<Core::Socket> socket;
ConnectionMode mode;
};
Resolver(Function<ErrorOr<SocketResult>()> create_socket)
: m_pending_lookups(make<RedBlackTree<u16, PendingLookup>>())
, m_create_socket(move(create_socket))
{
m_cache.with_write_locked([&](auto& cache) {
auto add_v4v6_entry = [&cache](StringView name_string, IPv4Address v4, IPv6Address v6) {
auto name = Messages::DomainName::from_string(name_string);
auto ptr = make_ref_counted<LookupResult>(name);
ptr->will_add_record_of_type(Messages::ResourceType::A);
ptr->will_add_record_of_type(Messages::ResourceType::AAAA);
cache.set(name_string, ptr);
ptr->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { v4 }, .raw = {} });
ptr->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { v6 }, .raw = {} });
};
add_v4v6_entry("localhost"sv, { 127, 0, 0, 1 }, IPv6Address::loopback());
});
}
NonnullRefPtr<Core::Promise<Empty>> when_socket_ready()
{
auto promise = Core::Promise<Empty>::construct();
m_socket_ready_promises.append(promise);
if (has_connection(false)) {
promise->resolve({});
return promise;
}
if (!has_connection())
promise->reject(Error::from_string_literal("Failed to create socket"));
return promise;
}
void reset_connection()
{
m_socket.with_write_locked([&](auto& socket) { socket = {}; });
}
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_ = Messages::Class::IN)
{
return expect_cached(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
{
auto result = lookup_in_cache(name, class_, desired_types);
VERIFY(!result.is_null());
dbgln_if(DNS_DEBUG, "DNS::expect({}) -> OK", name);
return *result;
}
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class class_ = Messages::Class::IN)
{
return lookup_in_cache(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class, Span<Messages::ResourceType const> desired_types)
{
return m_cache.with_read_locked([&](auto& cache) -> RefPtr<LookupResult const> {
auto it = cache.find(name);
if (it == cache.end())
return {};
auto& result = *it->value;
for (auto const& type : desired_types) {
if (!result.has_record_of_type(type))
return {};
}
return result;
});
}
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_ = Messages::Class::IN)
{
return lookup(move(name), class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
{
flush_cache();
auto promise = Core::Promise<NonnullRefPtr<LookupResult const>>::construct();
if (auto maybe_ipv4 = IPv4Address::from_string(name); maybe_ipv4.has_value()) {
if (desired_types.contains_slow(Messages::ResourceType::A)) {
auto result = make_ref_counted<LookupResult>(Messages::DomainName {});
result->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { maybe_ipv4.release_value() }, .raw = {} });
promise->resolve(move(result));
return promise;
}
}
if (auto maybe_ipv6 = IPv6Address::from_string(name); maybe_ipv6.has_value()) {
if (desired_types.contains_slow(Messages::ResourceType::AAAA)) {
auto result = make_ref_counted<LookupResult>(Messages::DomainName {});
result->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { maybe_ipv6.release_value() }, .raw = {} });
promise->resolve(move(result));
return promise;
}
}
if (auto result = lookup_in_cache(name, class_, desired_types)) {
promise->resolve(result.release_nonnull());
return promise;
}
auto domain_name = Messages::DomainName::from_string(name);
if (!has_connection()) {
// Use system resolver
// FIXME: Use an underlying resolver instead.
dbgln_if(DNS_DEBUG, "Not ready to resolve, using system resolver and skipping cache for {}", name);
auto record_or_error = Core::Socket::resolve_host(name, Core::Socket::SocketType::Stream);
if (record_or_error.is_error()) {
promise->reject(record_or_error.release_error());
return promise;
}
auto result = make_ref_counted<LookupResult>(domain_name);
auto record = record_or_error.release_value();
record.visit(
[&](IPv4Address const& address) {
result->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { address }, .raw = {} });
},
[&](IPv6Address const& address) {
result->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { address }, .raw = {} });
});
promise->resolve(result);
return promise;
}
auto already_in_cache = false;
auto result = m_cache.with_write_locked([&](auto& cache) -> NonnullRefPtr<LookupResult> {
auto existing = [&] -> RefPtr<LookupResult> {
if (cache.contains(name)) {
auto ptr = *cache.get(name);
already_in_cache = true;
for (auto const& type : desired_types) {
if (!ptr->has_record_of_type(type, true)) {
already_in_cache = false;
break;
}
}
return ptr;
}
return nullptr;
}();
if (existing)
return *existing;
auto ptr = make_ref_counted<LookupResult>(domain_name);
for (auto const& type : desired_types)
ptr->will_add_record_of_type(type);
cache.set(name, ptr);
return ptr;
});
Optional<u16> cached_result_id;
if (already_in_cache) {
auto id = result->id();
cached_result_id = id;
auto existing_promise = m_pending_lookups.with_write_locked([&](auto& lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
if (auto* lookup = lookups->find(id))
return lookup->promise;
return nullptr;
});
if (existing_promise)
return existing_promise.release_nonnull();
promise->resolve(*result);
return promise;
}
Messages::Message query;
m_pending_lookups.with_read_locked([&](auto& lookups) {
do
fill_with_random({ &query.header.id, sizeof(query.header.id) });
while (lookups->find(query.header.id) != nullptr);
});
query.header.question_count = max(1u, desired_types.size());
query.header.options.set_response_code(Messages::Options::ResponseCode::NoError);
query.header.options.set_recursion_desired(true);
query.header.options.set_op_code(Messages::OpCode::Query);
for (auto const& type : desired_types) {
query.questions.append(Messages::Question {
.name = domain_name,
.type = type,
.class_ = class_,
});
}
if (query.questions.is_empty()) {
query.questions.append(Messages::Question {
.name = Messages::DomainName::from_string(name),
.type = Messages::ResourceType::A,
.class_ = class_,
});
}
auto cached_entry = m_pending_lookups.with_write_locked([&](auto& pending_lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
// One more try to make sure we're not overwriting an existing lookup
if (cached_result_id.has_value()) {
if (auto* lookup = pending_lookups->find(*cached_result_id))
return lookup->promise;
}
pending_lookups->insert(query.header.id, { query.header.id, name, result->make_weak_ptr(), promise });
return nullptr;
});
if (cached_entry) {
dbgln_if(DNS_DEBUG, "DNS::lookup({}) -> Already in cache", name);
return cached_entry.release_nonnull();
}
ByteBuffer query_bytes;
MUST(query.to_raw(query_bytes));
if (m_mode == ConnectionMode::TCP) {
auto original_query_bytes = query_bytes;
query_bytes = MUST(ByteBuffer::create_uninitialized(query_bytes.size() + sizeof(u16)));
NetworkOrdered<u16> size = original_query_bytes.size();
query_bytes.overwrite(0, &size, sizeof(size));
query_bytes.overwrite(sizeof(size), original_query_bytes.data(), original_query_bytes.size());
}
auto write_result = m_socket.with_write_locked([&](auto& socket) {
return (*socket)->write_until_depleted(query_bytes.bytes());
});
if (write_result.is_error()) {
promise->reject(write_result.release_error());
return promise;
}
return promise;
}
private:
struct PendingLookup {
u16 id { 0 };
ByteString name;
WeakPtr<LookupResult> result;
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> promise;
};
ErrorOr<Messages::Message> parse_one_message()
{
if (m_mode == ConnectionMode::UDP)
return m_socket.with_write_locked([&](auto& socket) { return Messages::Message::from_raw(**socket); });
return m_socket.with_write_locked([&](auto& socket) -> ErrorOr<Messages::Message> {
if (!TRY((*socket)->can_read_without_blocking()))
return Error::from_errno(EAGAIN);
auto size = TRY((*socket)->template read_value<NetworkOrdered<u16>>());
auto buffer = TRY(ByteBuffer::create_uninitialized(size));
TRY((*socket)->read_until_filled(buffer));
FixedMemoryStream stream { static_cast<ReadonlyBytes>(buffer) };
return Messages::Message::from_raw(stream);
});
}
void process_incoming_messages()
{
while (true) {
if (auto result = m_socket.with_read_locked([](auto& socket) { return (*socket)->can_read_without_blocking(); }); result.is_error() || !result.value())
break;
auto message_or_err = parse_one_message();
if (message_or_err.is_error()) {
if (!message_or_err.error().is_errno() || message_or_err.error().code() != EAGAIN)
dbgln("DNS: Failed to receive message: {}", message_or_err.error());
break;
}
auto message = message_or_err.release_value();
auto result = m_pending_lookups.with_write_locked([&](auto& lookups) -> ErrorOr<void> {
auto* lookup = lookups->find(message.header.id);
if (!lookup)
return Error::from_string_literal("No pending lookup found for this message");
if (lookup->result.is_null())
return {}; // Message is a response to a lookup that's been purged from the cache, ignore it
auto result = lookup->result.strong_ref();
for (auto& record : message.answers)
result->add_record(move(record));
lookup->promise->resolve(*result);
lookups->remove(message.header.id);
return {};
});
if (result.is_error()) {
dbgln_if(DNS_DEBUG, "DNS: Received a message with no pending lookup: {}", result.error());
continue;
}
}
}
bool has_connection(bool attempt_restart = true)
{
auto result = m_socket.with_read_locked(
[&](auto& socket) { return socket.has_value() && (*socket)->is_open(); });
if (attempt_restart && !result && !m_attempting_restart) {
TemporaryChange change(m_attempting_restart, true);
auto create_result = m_create_socket();
if (create_result.is_error()) {
dbgln_if(DNS_DEBUG, "DNS: Failed to create socket: {}", create_result.error());
return false;
}
auto [socket, mode] = MUST(move(create_result));
set_socket(move(socket), mode);
result = true;
}
return result;
}
void set_socket(MaybeOwned<Core::Socket> socket, ConnectionMode mode = ConnectionMode::UDP)
{
m_mode = mode;
m_socket.with_write_locked([&](auto& s) {
s = move(socket);
(*s)->on_ready_to_read = [this] {
process_incoming_messages();
};
(*s)->set_notifications_enabled(true);
});
for (auto& promise : m_socket_ready_promises)
promise->resolve({});
m_socket_ready_promises.clear();
}
void flush_cache()
{
m_cache.with_write_locked([&](auto& cache) {
HashTable<ByteString> to_remove;
for (auto& entry : cache) {
entry.value->check_expiration();
if (!entry.value->is_valid())
to_remove.set(entry.key);
}
for (auto const& key : to_remove)
cache.remove(key);
});
}
Threading::RWLockProtected<HashMap<ByteString, NonnullRefPtr<LookupResult>>> m_cache;
Threading::RWLockProtected<NonnullOwnPtr<RedBlackTree<u16, PendingLookup>>> m_pending_lookups;
Threading::RWLockProtected<Optional<MaybeOwned<Core::Socket>>> m_socket;
Function<ErrorOr<SocketResult>()> m_create_socket;
bool m_attempting_restart { false };
ConnectionMode m_mode { ConnectionMode::UDP };
Vector<NonnullRefPtr<Core::Promise<Empty>>> m_socket_ready_promises;
};
}

View file

@ -10,8 +10,6 @@
#include <LibCore/DirIterator.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include <dirent.h>
#include <limits.h>
#if !defined(AK_OS_IOS) && defined(AK_OS_BSD_GENERIC)
# include <sys/disk.h>

View file

@ -11,7 +11,6 @@
#include <AK/Error.h>
#include <AK/StringView.h>
#include <LibCore/File.h>
#include <sys/stat.h>
namespace FileSystem {

View file

@ -10,11 +10,7 @@
# pragma GCC optimize("O3")
#endif
#include <AK/Function.h>
#include <AK/NumericLimits.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/Line.h>
namespace Gfx {

View file

@ -9,9 +9,7 @@
#include <LibGfx/Color.h>
#include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Forward.h>
#include <LibGfx/LineStyle.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/Quad.h>
#include <LibGfx/WindingRule.h>
namespace Gfx {

View file

@ -5,7 +5,6 @@
*/
#include <AK/Checked.h>
#include <AK/Forward.h>
#include <LibCore/AnonymousBuffer.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/BitmapSequence.h>

View file

@ -13,7 +13,6 @@
#include <AK/Swift.h>
#include <AK/Vector.h>
#include <LibGfx/Color.h>
#include <LibGfx/SystemTheme.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <ctype.h>

View file

@ -12,7 +12,6 @@
#include <AK/Format.h>
#include <AK/Forward.h>
#include <AK/Math.h>
#include <AK/SIMD.h>
#include <AK/StdLibExtras.h>
#include <LibIPC/Forward.h>

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/Math.h>
#include <LibGfx/DeltaE.h>
#include <math.h>

View file

@ -12,18 +12,13 @@
#include "DeprecatedPainter.h"
#include "Bitmap.h"
#include "Font/Font.h"
#include <AK/Assertions.h>
#include <AK/Function.h>
#include <AK/Math.h>
#include <AK/Memory.h>
#include <AK/Stack.h>
#include <AK/StdLibExtras.h>
#include <AK/Utf8View.h>
#include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Palette.h>
#include <LibGfx/Quad.h>
#include <LibGfx/TextLayout.h>
#include <LibGfx/ScalingMode.h>
#include <stdio.h>
#if defined(AK_COMPILER_GCC)

View file

@ -7,7 +7,6 @@
#pragma once
#include <AK/Forward.h>
#include <AK/Memory.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Vector.h>
#include <LibGfx/Color.h>
@ -16,8 +15,6 @@
#include <LibGfx/PaintStyle.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/Size.h>
#include <LibGfx/WindingRule.h>
namespace Gfx {

View file

@ -6,12 +6,9 @@
#include <AK/Math.h>
#include <AK/StringBuilder.h>
#include <AK/TypeCasts.h>
#include <LibGfx/BoundingBox.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/TextLayout.h>
namespace Gfx {

View file

@ -6,7 +6,6 @@
#pragma once
#include <AK/ByteString.h>
#include <AK/Optional.h>
#include <AK/Vector.h>
#include <LibGfx/Forward.h>

View file

@ -4,9 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/Debug.h>
#include <AK/IntegralMath.h>
#include <AK/Memory.h>
#include <AK/Types.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>

View file

@ -7,15 +7,10 @@
#pragma once
#include <AK/Bitmap.h>
#include <AK/ByteReader.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <LibCore/MappedFile.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h>
struct hb_font_t;
@ -85,7 +80,7 @@ public:
virtual float width(StringView) const = 0;
virtual float width(Utf8View const&) const = 0;
virtual FlyString family() const = 0;
virtual FlyString const& family() const = 0;
virtual NonnullRefPtr<Font> with_size(float point_size) const = 0;

View file

@ -6,9 +6,6 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <LibCore/Resource.h>
#include <LibGfx/Forward.h>

View file

@ -8,7 +8,6 @@
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Forward.h>

View file

@ -9,7 +9,6 @@
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h>

View file

@ -8,8 +8,6 @@
#pragma once
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/Typeface.h>
@ -37,7 +35,7 @@ public:
virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font
virtual float width(StringView) const override;
virtual float width(Utf8View const&) const override;
virtual FlyString family() const override { return m_typeface->family(); }
virtual FlyString const& family() const override { return m_typeface->family(); }
virtual NonnullRefPtr<ScaledFont> scaled_with_size(float point_size) const;
virtual NonnullRefPtr<Font> with_size(float point_size) const override;

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <core/SkTypeface.h>
#include <harfbuzz/hb.h>
#include <LibGfx/Font/ScaledFont.h>

View file

@ -7,9 +7,7 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontData.h>
#include <LibGfx/Forward.h>
@ -47,7 +45,7 @@ public:
virtual u32 glyph_count() const = 0;
virtual u16 units_per_em() const = 0;
virtual u32 glyph_id_for_code_point(u32 code_point) const = 0;
virtual FlyString family() const = 0;
virtual FlyString const& family() const = 0;
virtual u16 weight() const = 0;
virtual u16 width() const = 0;
virtual u8 slope() const = 0;

View file

@ -6,12 +6,10 @@
#include <AK/LsanSuppressions.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/TypefaceSkia.h>
#include <core/SkData.h>
#include <core/SkFontMgr.h>
#include <core/SkRefCnt.h>
#include <core/SkTypeface.h>
#ifndef AK_OS_ANDROID
# include <ports/SkFontMgr_fontconfig.h>
@ -114,7 +112,7 @@ void TypefaceSkia::populate_glyph_page(GlyphPage& glyph_page, size_t page_index)
}
}
FlyString TypefaceSkia::family() const
FlyString const& TypefaceSkia::family() const
{
if (!m_family.has_value()) {
SkString family_name;

View file

@ -19,7 +19,7 @@ public:
virtual u32 glyph_count() const override;
virtual u16 units_per_em() const override;
virtual u32 glyph_id_for_code_point(u32 code_point) const override;
virtual FlyString family() const override;
virtual FlyString const& family() const override;
virtual u16 weight() const override;
virtual u16 width() const override;
virtual u8 slope() const override;

View file

@ -6,8 +6,6 @@
#pragma once
#include <AK/ByteBuffer.h>
namespace Gfx::ICC {
class Profile;

View file

@ -7,7 +7,6 @@
#include <LibGfx/ICC/Profile.h>
#include <LibGfx/ICC/Tags.h>
#include <LibGfx/ICC/WellKnownProfiles.h>
#include <time.h>
namespace Gfx::ICC {

View file

@ -7,9 +7,7 @@
#pragma once
#include <AK/BitStream.h>
#include <AK/Error.h>
#include <AK/Optional.h>
#include <AK/Types.h>
namespace Gfx {

View file

@ -17,7 +17,6 @@
#include <LibCompress/Lzw.h>
#include <LibGfx/ImageFormats/GIFLoader.h>
#include <LibGfx/Painter.h>
#include <string.h>
namespace Gfx {

View file

@ -7,7 +7,6 @@
#pragma once
#include <AK/MemoryStream.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
namespace Gfx {

View file

@ -4,14 +4,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/Debug.h>
#include <AK/MemoryStream.h>
#include <AK/Types.h>
#include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/ICOLoader.h>
#include <LibGfx/ImageFormats/PNGLoader.h>
#include <string.h>
namespace Gfx {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <LibGfx/ImageFormats/AVIFLoader.h>
#include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/GIFLoader.h>

View file

@ -6,7 +6,6 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Error.h>
#include <LibGfx/ImageFormats/JPEGXLLoader.h>
#include <jxl/decode.h>

View file

@ -6,7 +6,6 @@
#pragma once
#include <AK/MemoryStream.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
namespace Gfx {

View file

@ -10,7 +10,6 @@
#include <AK/LEB128.h>
#include <AK/MemoryStream.h>
#include <AK/Variant.h>
#include <LibCore/File.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/ImageFormats/TinyVGLoader.h>

View file

@ -10,7 +10,6 @@
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
#include <LibGfx/ImageFormats/WebPWriterLossless.h>
#include <LibGfx/Point.h>
namespace Gfx {

View file

@ -8,11 +8,9 @@
#include <AK/BitStream.h>
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/HashTable.h>
#include <AK/MemoryStream.h>
#include <AK/QuickSort.h>
#include <LibCompress/DeflateTables.h>
#include <LibCompress/Huffman.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/WebPSharedLossless.h>

View file

@ -13,20 +13,9 @@
#include <LibGfx/PathSkia.h>
#include <AK/TypeCasts.h>
#include <core/SkBitmap.h>
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h>
#include <core/SkPathBuilder.h>
#include <core/SkRRect.h>
#include <core/SkSurface.h>
#include <effects/SkGradientShader.h>
#include <effects/SkImageFilters.h>
#include <gpu/GrDirectContext.h>
#include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h>
namespace Gfx {

View file

@ -5,7 +5,6 @@
*/
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/PaintingSurface.h>
#include <LibGfx/SkiaUtils.h>
@ -16,9 +15,7 @@
#include <gpu/ganesh/SkSurfaceGanesh.h>
#ifdef AK_OS_MACOS
# include <gpu/ganesh/mtl/GrMtlBackendContext.h>
# include <gpu/ganesh/mtl/GrMtlBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlDirectContext.h>
#endif
namespace Gfx {

View file

@ -6,9 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Badge.h>
#include <LibGfx/Palette.h>
#include <string.h>
namespace Gfx {

View file

@ -9,9 +9,10 @@
#include <AK/Forward.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Utf8View.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Forward.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/WindingRule.h>
namespace Gfx {

View file

@ -9,7 +9,7 @@
#include <AK/TypeCasts.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/PathSkia.h>
#include <core/SkContourMeasure.h>
#include <LibGfx/Rect.h>
#include <core/SkFont.h>
#include <core/SkPath.h>
#include <core/SkPathMeasure.h>

View file

@ -5,8 +5,6 @@
*/
#include <AK/ByteString.h>
#include <AK/Vector.h>
#include <LibGfx/Line.h>
#include <LibGfx/Rect.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>

View file

@ -8,7 +8,6 @@
#include <AK/RefPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h>
#include <LibIPC/Forward.h>
namespace Gfx {

View file

@ -11,7 +11,6 @@
#include <LibCore/ConfigFile.h>
#include <LibCore/DirIterator.h>
#include <LibGfx/SystemTheme.h>
#include <string.h>
namespace Gfx {

View file

@ -10,7 +10,6 @@
#include <AK/ByteString.h>
#include <AK/Forward.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibCore/AnonymousBuffer.h>
#include <LibCore/ConfigFile.h>

View file

@ -7,7 +7,7 @@
#include "TextLayout.h"
#include <AK/TypeCasts.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Point.h>
#include <harfbuzz/hb.h>
namespace Gfx {

View file

@ -7,16 +7,12 @@
#pragma once
#include <AK/ByteString.h>
#include <AK/CharacterTypes.h>
#include <AK/Forward.h>
#include <AK/Utf32View.h>
#include <AK/Utf8View.h>
#include <AK/Vector.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/FontCascadeList.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Point.h>
namespace Gfx {

View file

@ -15,6 +15,19 @@ public:
HeaderMap() = default;
~HeaderMap() = default;
HeaderMap(Vector<Header> headers)
: m_headers(move(headers))
{
for (auto& header : m_headers)
m_map.set(header.name, header.value);
}
HeaderMap(HeaderMap const&) = default;
HeaderMap(HeaderMap&&) = default;
HeaderMap& operator=(HeaderMap const&) = default;
HeaderMap& operator=(HeaderMap&&) = default;
void set(ByteString name, ByteString value)
{
m_map.set(name, value);
@ -56,10 +69,7 @@ template<>
inline ErrorOr<HTTP::HeaderMap> decode(Decoder& decoder)
{
auto headers = TRY(decoder.decode<Vector<HTTP::Header>>());
HTTP::HeaderMap header_map;
for (auto& header : headers)
header_map.set(move(header.name), move(header.value));
return header_map;
return HTTP::HeaderMap { move(headers) };
}
}

View file

@ -192,6 +192,7 @@ static double parse_date_string(VM& vm, ByteString const& date_string)
"%d%t%b%t%Y%t%R"sv, // "01 Jan 2000 08:00"
"%A,%t%B%t%e,%t%Y,%t%R%t%Z"sv, // "Tuesday, October 29, 2024, 18:00 UTC"
"%B%t%d%t%Y%t%T%t%z"sv, // "November 19 2024 00:00:00 +0900"
"%a%t%b%t%e%t%Y"sv // "Wed Nov 20 2024"
};
for (auto const& format : extra_formats) {

View file

@ -41,6 +41,7 @@ test("basic functionality", () => {
expect(Date.parse("01 February 2013")).toBe(1359698400000);
expect(Date.parse("Tuesday, October 29, 2024, 18:00 UTC")).toBe(1730224800000);
expect(Date.parse("November 19 2024 00:00:00 +0900")).toBe(1731942000000);
expect(Date.parse("Wed Nov 20 2024")).toBe(1732082400000);
// FIXME: Create a scoped time zone helper when bytecode supports the `using` declaration.
setTimeZone(originalTimeZone);

View file

@ -123,7 +123,7 @@ void Request::set_up_internal_stream_data(DataReceived on_data_available)
};
m_internal_stream_data->on_finish = [this, user_on_finish = move(user_on_finish)]() {
if (!m_internal_stream_data->user_finish_called && m_internal_stream_data->read_stream->is_eof()) {
if (!m_internal_stream_data->user_finish_called && (!m_internal_stream_data->read_stream || m_internal_stream_data->read_stream->is_eof())) {
m_internal_stream_data->user_finish_called = true;
user_on_finish(m_internal_stream_data->total_size, m_internal_stream_data->network_error);
}

View file

@ -73,6 +73,29 @@ ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, u16 port,
return tls_socket;
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(Core::SocketAddress address, ByteString const& host, Options options)
{
auto promise = Core::Promise<Empty>::construct();
OwnPtr<Core::Socket> tcp_socket = TRY(Core::TCPSocket::connect(address));
TRY(tcp_socket->set_blocking(false));
auto tls_socket = make<TLSv12>(move(tcp_socket), move(options));
tls_socket->set_sni(host);
tls_socket->on_connected = [&] {
promise->resolve({});
};
tls_socket->on_tls_error = [&](auto alert) {
tls_socket->try_disambiguate_error();
promise->reject(AK::Error::from_string_view(enum_to_string(alert)));
};
TRY(promise->await());
tls_socket->on_tls_error = nullptr;
tls_socket->on_connected = nullptr;
tls_socket->m_context.should_expect_successful_read = true;
return tls_socket;
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, Core::Socket& underlying_stream, Options options)
{
auto promise = Core::Promise<Empty>::construct();
@ -271,7 +294,8 @@ bool TLSv12::check_connection_state(bool read)
ErrorOr<bool> TLSv12::flush()
{
auto out_bytes = m_context.tls_buffer.bytes();
ByteBuffer out = move(m_context.tls_buffer);
auto out_bytes = out.bytes();
if (out_bytes.is_empty())
return true;
@ -298,17 +322,11 @@ ErrorOr<bool> TLSv12::flush()
out_bytes = out_bytes.slice(written);
} while (!out_bytes.is_empty());
if (out_bytes.is_empty() && !error.has_value()) {
m_context.tls_buffer.clear();
if (out_bytes.is_empty() && !error.has_value())
return true;
}
if (m_context.send_retries++ == 10) {
// drop the records, we can't send
dbgln_if(TLS_DEBUG, "Dropping {} bytes worth of TLS records as max retries has been reached", m_context.tls_buffer.size());
m_context.tls_buffer.clear();
m_context.send_retries = 0;
}
if (!out_bytes.is_empty())
dbgln("Dropping {} bytes worth of TLS records on the floor", out_bytes.size());
return false;
}

View file

@ -357,6 +357,7 @@ public:
virtual void set_notifications_enabled(bool enabled) override { underlying_stream().set_notifications_enabled(enabled); }
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(Core::SocketAddress, ByteString const& host, Options = {});
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, u16 port, Options = {});
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, Core::Socket& underlying_stream, Options = {});

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Bitmap.h>
#include <AK/QuickSort.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibWeb/Animations/Animation.h>

View file

@ -150,10 +150,12 @@ public:
static float fill_opacity() { return 1.0f; }
static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
static CSS::ClipRule clip_rule() { return CSS::ClipRule::Nonzero; }
static CSS::LengthPercentage stroke_dashoffset() { return CSS::Length::make_px(0); }
static CSS::StrokeLinecap stroke_linecap() { return CSS::StrokeLinecap::Butt; }
static CSS::StrokeLinejoin stroke_linejoin() { return CSS::StrokeLinejoin::Miter; }
static float stroke_miterlimit() { return 4.0f; }
static float stroke_opacity() { return 1.0f; }
static CSS::LengthPercentage stroke_width() { return CSS::Length::make_px(1); }
static float stop_opacity() { return 1.0f; }
static CSS::TextAnchor text_anchor() { return CSS::TextAnchor::Start; }
static CSS::Length border_radius() { return Length::make_px(0); }
@ -478,6 +480,7 @@ public:
CSS::FillRule fill_rule() const { return m_inherited.fill_rule; }
Optional<SVGPaint> const& stroke() const { return m_inherited.stroke; }
float fill_opacity() const { return m_inherited.fill_opacity; }
LengthPercentage const& stroke_dashoffset() const { return m_inherited.stroke_dashoffset; }
CSS::StrokeLinecap stroke_linecap() const { return m_inherited.stroke_linecap; }
CSS::StrokeLinejoin stroke_linejoin() const { return m_inherited.stroke_linejoin; }
NumberOrCalculated stroke_miterlimit() const { return m_inherited.stroke_miterlimit; }
@ -578,11 +581,12 @@ protected:
CSS::FillRule fill_rule { InitialValues::fill_rule() };
Optional<SVGPaint> stroke;
float fill_opacity { InitialValues::fill_opacity() };
LengthPercentage stroke_dashoffset { InitialValues::stroke_dashoffset() };
CSS::StrokeLinecap stroke_linecap { InitialValues::stroke_linecap() };
CSS::StrokeLinejoin stroke_linejoin { InitialValues::stroke_linejoin() };
NumberOrCalculated stroke_miterlimit { InitialValues::stroke_miterlimit() };
float stroke_opacity { InitialValues::stroke_opacity() };
LengthPercentage stroke_width { Length::make_px(1) };
LengthPercentage stroke_width { InitialValues::stroke_width() };
CSS::TextAnchor text_anchor { InitialValues::text_anchor() };
CSS::ClipRule clip_rule { InitialValues::clip_rule() };
@ -826,6 +830,7 @@ public:
void set_stroke(SVGPaint value) { m_inherited.stroke = value; }
void set_fill_rule(CSS::FillRule value) { m_inherited.fill_rule = value; }
void set_fill_opacity(float value) { m_inherited.fill_opacity = value; }
void set_stroke_dashoffset(LengthPercentage value) { m_inherited.stroke_dashoffset = value; }
void set_stroke_linecap(CSS::StrokeLinecap value) { m_inherited.stroke_linecap = value; }
void set_stroke_linejoin(CSS::StrokeLinejoin value) { m_inherited.stroke_linejoin = value; }
void set_stroke_miterlimit(NumberOrCalculated value) { m_inherited.stroke_miterlimit = value; }

View file

@ -30,7 +30,7 @@ public:
Optional<Percentage> ascent_override() const { return m_ascent_override; }
Optional<Percentage> descent_override() const { return m_descent_override; }
FontDisplay font_display() const { return m_font_display; }
FlyString font_family() const { return m_font_family; }
FlyString const& font_family() const { return m_font_family; }
Optional<OrderedHashMap<FlyString, i64>> font_feature_settings() const { return m_font_feature_settings; }
Optional<FlyString> font_language_override() const { return m_font_language_override; }
Optional<FlyString> font_named_instance() const { return m_font_named_instance; }

View file

@ -204,15 +204,15 @@ Vector<Token> Tokenizer::tokenize(StringView input, StringView encoding)
auto decoded_input = MUST(decoder->to_utf8(input));
// OPTIMIZATION: If the input doesn't contain any CR or FF, we can skip the filtering
bool const contains_cr_or_ff = [&] {
for (auto byte : decoded_input.bytes()) {
if (byte == '\r' || byte == '\f')
// OPTIMIZATION: If the input doesn't contain any filterable characters, we can skip the filtering
bool const contains_filterable = [&] {
for (auto code_point : decoded_input.code_points()) {
if (code_point == '\r' || code_point == '\f' || code_point == 0x00 || is_unicode_surrogate(code_point))
return true;
}
return false;
}();
if (!contains_cr_or_ff) {
if (!contains_filterable) {
return decoded_input;
}
@ -242,7 +242,7 @@ Vector<Token> Tokenizer::tokenize(StringView input, StringView encoding)
} else if (code_point == '\f') {
builder.append('\n');
// Replace any U+0000 NULL or surrogate code points in input with U+FFFD REPLACEMENT CHARACTER (<28>).
} else if (code_point == 0x00 || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
} else if (code_point == 0x00 || is_unicode_surrogate(code_point)) {
builder.append_code_point(REPLACEMENT_CHARACTER);
} else {
builder.append_code_point(code_point);

View file

@ -2420,6 +2420,18 @@
"paint"
]
},
"stroke-dashoffset": {
"affects-layout": false,
"animation-type": "by-computed-value",
"inherited": true,
"initial": "0",
"valid-types": [
"length [0,∞]",
"number [0,∞]",
"percentage [0,∞]"
],
"percentages-resolve-to": "length"
},
"stroke-linecap": {
"affects-layout": false,
"animation-type": "discrete",

View file

@ -14,6 +14,7 @@
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/Math.h>
#include <AK/NonnullRawPtr.h>
#include <AK/QuickSort.h>
#include <AK/TemporaryChange.h>
#include <LibGfx/Font/Font.h>
@ -85,12 +86,33 @@
#include <math.h>
#include <stdio.h>
namespace Web::CSS {
struct FontFaceKey {
NonnullRawPtr<FlyString const> family_name;
int weight { 0 };
int slope { 0 };
};
}
namespace AK {
// traits for FontFaceKey
namespace Detail {
template<>
inline constexpr bool IsHashCompatible<Web::CSS::FontFaceKey, Web::CSS::OwnFontFaceKey> = true;
template<>
inline constexpr bool IsHashCompatible<Web::CSS::OwnFontFaceKey, Web::CSS::FontFaceKey> = true;
}
template<>
struct Traits<Web::CSS::FontFaceKey> : public DefaultTraits<Web::CSS::FontFaceKey> {
static unsigned hash(Web::CSS::FontFaceKey const& key) { return pair_int_hash(key.family_name.hash(), pair_int_hash(key.weight, key.slope)); }
static unsigned hash(Web::CSS::FontFaceKey const& key) { return pair_int_hash(key.family_name->hash(), pair_int_hash(key.weight, key.slope)); }
};
template<>
struct Traits<Web::CSS::OwnFontFaceKey> : public DefaultTraits<Web::CSS::OwnFontFaceKey> {
static unsigned hash(Web::CSS::OwnFontFaceKey const& key) { return pair_int_hash(key.family_name.hash(), pair_int_hash(key.weight, key.slope)); }
};
}
@ -124,6 +146,29 @@ FlyString const& MatchingRule::qualified_layer_name() const
VERIFY_NOT_REACHED();
}
OwnFontFaceKey::OwnFontFaceKey(FontFaceKey const& other)
: family_name(other.family_name)
, weight(other.weight)
, slope(other.slope)
{
}
OwnFontFaceKey::operator FontFaceKey() const
{
return FontFaceKey {
family_name,
weight,
slope
};
}
[[nodiscard]] bool OwnFontFaceKey::operator==(FontFaceKey const& other) const
{
return family_name == other.family_name
&& weight == other.weight
&& slope == other.slope;
}
static DOM::Element const* element_to_inherit_style_from(DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>);
StyleComputer::StyleComputer(DOM::Document& document)
@ -1738,16 +1783,16 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::find_matching_font_weight_desc
// Partial implementation of the font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
// FIXME: This should be replaced by the full CSS font selection algorithm.
RefPtr<Gfx::FontCascadeList const> StyleComputer::font_matching_algorithm(FontFaceKey const& key, float font_size_in_pt) const
RefPtr<Gfx::FontCascadeList const> StyleComputer::font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const
{
// If a font family match occurs, the user agent assembles the set of font faces in that family and then
// narrows the set to a single face using other font properties in the order given below.
Vector<MatchingFontCandidate> matching_family_fonts;
for (auto const& font_key_and_loader : m_loaded_fonts) {
if (font_key_and_loader.key.family_name.equals_ignoring_ascii_case(key.family_name))
if (font_key_and_loader.key.family_name.equals_ignoring_ascii_case(family_name))
matching_family_fonts.empend(font_key_and_loader.key, const_cast<FontLoaderList*>(&font_key_and_loader.value));
}
Gfx::FontDatabase::the().for_each_typeface_with_family_name(key.family_name, [&](Gfx::Typeface const& typeface) {
Gfx::FontDatabase::the().for_each_typeface_with_family_name(family_name, [&](Gfx::Typeface const& typeface) {
matching_family_fonts.empend(
FontFaceKey {
.family_name = typeface.family(),
@ -1764,24 +1809,24 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::font_matching_algorithm(FontFa
// We don't have complete support of italic and oblique fonts, so matching on font-style can be simplified to:
// If a matching slope is found, all faces which don't have that matching slope are excluded from the matching set.
auto style_it = find_if(matching_family_fonts.begin(), matching_family_fonts.end(),
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.slope == key.slope; });
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.slope == slope; });
if (style_it != matching_family_fonts.end()) {
matching_family_fonts.remove_all_matching([&](auto const& matching_font_candidate) {
return matching_font_candidate.key.slope != key.slope;
return matching_font_candidate.key.slope != slope;
});
}
// 3. font-weight is matched next.
// If the desired weight is inclusively between 400 and 500, weights greater than or equal to the target weight
// are checked in ascending order until 500 is hit and checked, followed by weights less than the target weight
// in descending order, followed by weights greater than 500, until a match is found.
if (key.weight >= 400 && key.weight <= 500) {
if (weight >= 400 && weight <= 500) {
auto it = find_if(matching_family_fonts.begin(), matching_family_fonts.end(),
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight >= key.weight; });
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight >= weight; });
for (; it != matching_family_fonts.end() && it->key.weight <= 500; ++it) {
if (auto found_font = it->font_with_point_size(font_size_in_pt))
return found_font;
}
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, key.weight, font_size_in_pt, false))
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, false))
return found_font;
for (; it != matching_family_fonts.end(); ++it) {
if (auto found_font = it->font_with_point_size(font_size_in_pt))
@ -1790,18 +1835,18 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::font_matching_algorithm(FontFa
}
// If the desired weight is less than 400, weights less than or equal to the desired weight are checked in descending order
// followed by weights above the desired weight in ascending order until a match is found.
if (key.weight < 400) {
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, key.weight, font_size_in_pt, true))
if (weight < 400) {
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, true))
return found_font;
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, key.weight, font_size_in_pt, false))
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, weight, font_size_in_pt, false))
return found_font;
}
// If the desired weight is greater than 500, weights greater than or equal to the desired weight are checked in ascending order
// followed by weights below the desired weight in descending order until a match is found.
if (key.weight > 500) {
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, key.weight, font_size_in_pt, true))
if (weight > 500) {
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, weight, font_size_in_pt, true))
return found_font;
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, key.weight, font_size_in_pt, false))
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, false))
return found_font;
}
return {};
@ -1963,7 +2008,6 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(
.weight = weight,
.slope = slope,
};
auto result = Gfx::FontCascadeList::create();
if (auto it = m_loaded_fonts.find(key); it != m_loaded_fonts.end()) {
auto const& loaders = it->value;
@ -1974,7 +2018,7 @@ RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(
return result;
}
if (auto found_font = font_matching_algorithm(key, font_size_in_pt); found_font && !found_font->is_empty()) {
if (auto found_font = font_matching_algorithm(family, weight, slope, font_size_in_pt); found_font && !found_font->is_empty()) {
return found_font;
}
@ -2800,7 +2844,7 @@ Optional<FontLoader&> StyleComputer::load_font_face(ParsedFontFace const& font_f
} else {
FontLoaderList loaders;
loaders.append(move(loader));
const_cast<StyleComputer&>(*this).m_loaded_fonts.set(key, move(loaders));
const_cast<StyleComputer&>(*this).m_loaded_fonts.set(OwnFontFaceKey(key), move(loaders));
}
// Actual object owned by font loader list inside m_loaded_fonts, this isn't use-after-move/free
return loader_ref;

View file

@ -101,13 +101,20 @@ struct MatchingRule {
FlyString const& qualified_layer_name() const;
};
struct FontFaceKey {
struct FontFaceKey;
struct OwnFontFaceKey {
explicit OwnFontFaceKey(FontFaceKey const& other);
operator FontFaceKey() const;
[[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); }
[[nodiscard]] bool operator==(OwnFontFaceKey const& other) const = default;
[[nodiscard]] bool operator==(FontFaceKey const& other) const;
FlyString family_name;
int weight { 0 };
int slope { 0 };
[[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); }
[[nodiscard]] bool operator==(FontFaceKey const&) const = default;
};
class FontLoader;
@ -180,7 +187,7 @@ private:
void compute_cascaded_values(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FontFaceKey const& key, float font_size_in_pt) const;
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
void compute_font(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
void compute_math_depth(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
void compute_defaulted_values(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
@ -246,7 +253,7 @@ private:
GC::Root<CSSStyleSheet> m_user_style_sheet;
using FontLoaderList = Vector<NonnullOwnPtr<FontLoader>>;
HashMap<FontFaceKey, FontLoaderList> m_loaded_fonts;
HashMap<OwnFontFaceKey, FontLoaderList> m_loaded_fonts;
Length::FontMetrics m_default_font_metrics;
Length::FontMetrics m_root_element_font_metrics;

View file

@ -823,7 +823,13 @@ private:
GC::Ptr<Document> m_associated_inert_template_document;
GC::Ptr<Document> m_appropriate_template_contents_owner_document;
HTML::DocumentReadyState m_readiness { HTML::DocumentReadyState::Loading };
// https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness
// Each Document has a current document readiness, a string, initially "complete".
// Spec Note: For Document objects created via the create and initialize a Document object algorithm, this will be
// immediately reset to "loading" before any script can observe the value of document.readyState.
// This default applies to other cases such as initial about:blank Documents or Documents without a
// browsing context.
HTML::DocumentReadyState m_readiness { HTML::DocumentReadyState::Complete };
String m_content_type { "application/xml"_string };
Optional<String> m_pragma_set_default_language;
Optional<String> m_encoding;

View file

@ -235,10 +235,7 @@ WebIDL::ExceptionOr<void> Node::normalize()
Vector<Text*> nodes;
auto* current_node = node.previous_sibling();
while (current_node) {
if (!current_node->is_text())
break;
while (current_node && current_node->is_exclusive_text()) {
nodes.append(static_cast<Text*>(current_node));
current_node = current_node->previous_sibling();
}
@ -247,10 +244,7 @@ WebIDL::ExceptionOr<void> Node::normalize()
nodes.reverse();
current_node = node.next_sibling();
while (current_node) {
if (!current_node->is_text())
break;
while (current_node && current_node->is_exclusive_text()) {
nodes.append(static_cast<Text*>(current_node));
current_node = current_node->next_sibling();
}
@ -291,7 +285,7 @@ WebIDL::ExceptionOr<void> Node::normalize()
auto* current_node = node.next_sibling();
// 6. While currentNode is an exclusive Text node:
while (current_node && is<Text>(*current_node)) {
while (current_node && current_node->is_exclusive_text()) {
// 1. For each live range whose start node is currentNode, add length to its start offset and set its start node to node.
for (auto& range : Range::live_ranges()) {
if (range->start_container() == current_node)
@ -951,7 +945,7 @@ WebIDL::ExceptionOr<GC::Ref<Node>> Node::replace_child(GC::Ref<Node> node, GC::R
} else if (is<DocumentType>(*node)) {
// DocumentType
// parent has a doctype child that is not child, or an element is preceding child.
if (first_child_of_type<DocumentType>() != node || child->has_preceding_node_of_type_in_tree_order<Element>())
if (first_child_of_type<DocumentType>() != child || child->has_preceding_node_of_type_in_tree_order<Element>())
return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
}
}
@ -1057,15 +1051,21 @@ WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node(Document* document, bool clo
// Set copys namespace, namespace prefix, local name, and value to those of node.
auto& attr = static_cast<Attr&>(*this);
copy = attr.clone(*document);
}
// NOTE: is<Text>() currently returns true only for text nodes, not for descendant types of Text.
else if (is<Text>(this) || is<CDATASection>(this)) {
} else if (is<Text>(this)) {
// Text
auto& text = static_cast<Text&>(*this);
// Set copys data to that of node.
auto text_copy = realm().create<Text>(*document, text.data());
copy = move(text_copy);
copy = [&]() -> GC::Ref<Text> {
switch (type()) {
case NodeType::TEXT_NODE:
return realm().create<Text>(*document, text.data());
case NodeType::CDATA_SECTION_NODE:
return realm().create<CDATASection>(*document, text.data());
default:
VERIFY_NOT_REACHED();
}
}();
} else if (is<Comment>(this)) {
// Comment
auto comment = verify_cast<Comment>(this);

View file

@ -111,6 +111,7 @@ public:
NodeType type() const { return m_type; }
bool is_element() const { return type() == NodeType::ELEMENT_NODE; }
bool is_text() const { return type() == NodeType::TEXT_NODE || type() == NodeType::CDATA_SECTION_NODE; }
bool is_exclusive_text() const { return type() == NodeType::TEXT_NODE; }
bool is_document() const { return type() == NodeType::DOCUMENT_NODE; }
bool is_document_type() const { return type() == NodeType::DOCUMENT_TYPE_NODE; }
bool is_comment() const { return type() == NodeType::COMMENT_NODE; }

View file

@ -703,13 +703,13 @@ void HTMLImageElement::add_callbacks_to_image_request(GC::Ref<ImageRequest> imag
// 3. Add the image to the list of available images using the key key, with the ignore higher-layer caching flag set.
document().list_of_available_images().add(key, *image_data, true);
set_needs_style_update(true);
document().set_needs_layout();
// 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
if (!maybe_omit_events || previous_url != url_string)
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
set_needs_style_update(true);
document().set_needs_layout();
if (image_data->is_animated() && image_data->frame_count() > 1) {
m_current_frame_index = 0;
m_animation_timer->set_interval(image_data->frame_duration(0));

View file

@ -895,7 +895,7 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
// If the latter condition occurs, then abort fetchController, and return. Otherwise, proceed onward.
if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id)) {
fetch_controller->abort(realm, {});
return Empty {};
return Navigable::NullOrError {};
}
// 8. If request's body is null, then set entry's document state's resource to null.
@ -1010,9 +1010,9 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
if (response_holder->response()->network_error_message().has_value() && !response_holder->response()->network_error_message().value().is_null())
return response_holder->response()->network_error_message().value();
else
return Empty {};
return Navigable::NullOrError {};
} else if (location_url.is_error() || (location_url.value().has_value() && Fetch::Infrastructure::is_fetch_scheme(location_url.value().value().scheme())))
return Empty {};
return Navigable::NullOrError {};
// 22. Assert: locationURL is null and response is not a network error.
VERIFY(!location_url.value().has_value());
@ -1079,7 +1079,7 @@ WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
// FIXME: 1. Assert: this is running in parallel.
// 2. Assert: if navigationParams is non-null, then navigationParams's response is non-null.
if (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>())
if (!navigation_params.has<NullOrError>())
VERIFY(navigation_params.has<GC::Ref<NavigationParams>>() && navigation_params.get<GC::Ref<NavigationParams>>()->response);
// 3. Let currentBrowsingContext be navigable's active browsing context.
@ -1089,7 +1089,7 @@ WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
auto document_resource = entry->document_state()->resource();
// 5. If navigationParams is null, then:
if (navigation_params.has<Empty>() || navigation_params.has<NullWithError>()) {
if (navigation_params.has<NullOrError>()) {
// 1. If documentResource is a string, then set navigationParams to the result
// of creating navigation params from a srcdoc resource given entry, navigable,
// targetSnapshotParams, navigationId, and navTimingType.
@ -1160,9 +1160,9 @@ WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
// - FIXME: the result of should navigation response to navigation request of type in target be blocked by Content Security Policy? given navigationParams's request, navigationParams's response, navigationParams's policy container's CSP list, cspNavigationType, and navigable is "Blocked";
// - FIXME: navigationParams's reserved environment is non-null and the result of checking a navigation response's adherence to its embedder policy given navigationParams's response, navigable, and navigationParams's policy container's embedder policy is false; or
// - FIXME: the result of checking a navigation response's adherence to `X-Frame-Options` given navigationParams's response, navigable, navigationParams's policy container's CSP list, and navigationParams's origin is false,
if (navigation_params.has<Empty>() || navigation_params.has<NullWithError>()) {
if (navigation_params.has<NullOrError>()) {
// 1. Set entry's document state's document to the result of creating a document for inline content that doesn't have a DOM, given navigable, null, and navTimingType. The inline content should indicate to the user the sort of error that occurred.
auto error_message = navigation_params.has<NullWithError>() ? navigation_params.get<NullWithError>() : "Unknown error"sv;
auto error_message = navigation_params.get<NullOrError>().value_or("Unknown error"sv);
auto error_html = load_error_page(entry->url(), error_message).release_value_but_fixme_should_propagate_errors();
entry->document_state()->set_document(create_document_for_inline_content(this, navigation_id, [error_html](auto& document) {
@ -1178,7 +1178,7 @@ WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
saveExtraDocumentState = false;
// 4. If navigationParams is not null, then:
if (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>()) {
if (!navigation_params.has<NullOrError>()) {
// FIXME: 1. Run the environment discarding steps for navigationParams's reserved environment.
// FIXME: 2. Invoke WebDriver BiDi navigation failed with currentBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is navigationParams's response's URL.
}
@ -1210,7 +1210,7 @@ WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
// 3. If entry's document state's request referrer is "client", and navigationParams is a navigation params (i.e., neither null nor a non-fetch scheme navigation params), then:
if (entry->document_state()->request_referrer() == Fetch::Infrastructure::Request::Referrer::Client
&& (!navigation_params.has<Empty>() && !navigation_params.has<NullWithError>() && navigation_params.has<GC::Ref<NonFetchSchemeNavigationParams>>())) {
&& (!navigation_params.has<NullOrError>() && navigation_params.has<GC::Ref<NonFetchSchemeNavigationParams>>())) {
// 1. Assert: navigationParams's request is not null.
VERIFY(navigation_params.has<GC::Ref<NavigationParams>>() && navigation_params.get<GC::Ref<NavigationParams>>()->request);
@ -1463,7 +1463,7 @@ WebIDL::ExceptionOr<void> Navigable::navigate(NavigateParams params)
history_entry->set_document_state(document_state);
// 7. Let navigationParams be null.
NavigationParamsVariant navigation_params = Empty {};
NavigationParamsVariant navigation_params = Navigable::NullOrError {};
// FIXME: 8. If response is non-null:
if (response) {

View file

@ -54,8 +54,8 @@ class Navigable : public JS::Cell {
public:
virtual ~Navigable() override;
using NullWithError = StringView;
using NavigationParamsVariant = Variant<Empty, NullWithError, GC::Ref<NavigationParams>, GC::Ref<NonFetchSchemeNavigationParams>>;
using NullOrError = Optional<StringView>;
using NavigationParamsVariant = Variant<NullOrError, GC::Ref<NavigationParams>, GC::Ref<NonFetchSchemeNavigationParams>>;
ErrorOr<void> initialize_navigable(GC::Ref<DocumentState> document_state, GC::Ptr<Navigable> parent);
@ -132,7 +132,7 @@ public:
SourceSnapshotParams const& source_snapshot_params,
TargetSnapshotParams const& target_snapshot_params,
Optional<String> navigation_id = {},
NavigationParamsVariant navigation_params = Empty {},
NavigationParamsVariant navigation_params = Navigable::NullOrError {},
CSPNavigationType csp_navigation_type = CSPNavigationType::Other,
bool allow_POST = false,
GC::Ptr<GC::Function<void()>> completion_steps = {});

View file

@ -644,7 +644,7 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
// targetSnapshotParams, with allowPOST set to allowPOST and completionSteps set to queue a global task on the navigation and traversal task source given
// navigable's active window to run afterDocumentPopulated.
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(this->heap(), [populated_target_entry, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated = GC::create_function(this->heap(), move(after_document_populated))] {
navigable->populate_session_history_entry_document(populated_target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, {}, Empty {}, CSPNavigationType::Other, allow_POST, GC::create_function(this->heap(), [this, after_document_populated, populated_target_entry]() mutable {
navigable->populate_session_history_entry_document(populated_target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, {}, Navigable::NullOrError {}, CSPNavigationType::Other, allow_POST, GC::create_function(this->heap(), [this, after_document_populated, populated_target_entry]() mutable {
VERIFY(active_window());
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(this->heap(), [after_document_populated, populated_target_entry]() mutable {
after_document_populated->function()(true, populated_target_entry);

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Bitmap.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/GridFormattingContext.h>

View file

@ -854,6 +854,17 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
computed_values.set_fill_rule(*fill_rule);
computed_values.set_fill_opacity(computed_style.fill_opacity());
auto const& stroke_dashoffset = computed_style.property(CSS::PropertyID::StrokeDashoffset);
// FIXME: Converting to pixels isn't really correct - values should be in "user units"
// https://svgwg.org/svg2-draft/coords.html#TermUserUnits
if (stroke_dashoffset.is_number())
computed_values.set_stroke_dashoffset(CSS::Length::make_px(CSSPixels::nearest_value_for(stroke_dashoffset.as_number().number())));
else if (stroke_dashoffset.is_length())
computed_values.set_stroke_dashoffset(stroke_dashoffset.as_length().length());
else if (stroke_dashoffset.is_percentage())
computed_values.set_stroke_dashoffset(CSS::LengthPercentage { stroke_dashoffset.as_percentage().percentage() });
if (auto stroke_linecap = computed_style.stroke_linecap(); stroke_linecap.has_value())
computed_values.set_stroke_linecap(stroke_linecap.value());
if (auto stroke_linejoin = computed_style.stroke_linejoin(); stroke_linejoin.has_value())

View file

@ -16,6 +16,7 @@
#include <LibGfx/Forward.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/LineStyle.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/PaintingSurface.h>
#include <LibGfx/Palette.h>

View file

@ -6,6 +6,7 @@
*/
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/Quad.h>
#include <LibWeb/Painting/SVGPathPaintable.h>
#include <LibWeb/Painting/SVGSVGPaintable.h>

View file

@ -150,6 +150,7 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
NamedPropertyID(CSS::PropertyID::Fill),
// FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now.
NamedPropertyID(CSS::PropertyID::Stroke),
NamedPropertyID(CSS::PropertyID::StrokeDashoffset),
NamedPropertyID(CSS::PropertyID::StrokeLinecap),
NamedPropertyID(CSS::PropertyID::StrokeLinejoin),
NamedPropertyID(CSS::PropertyID::StrokeMiterlimit),
@ -266,13 +267,10 @@ Optional<float> SVGGraphicsElement::stroke_opacity() const
return layout_node()->computed_values().stroke_opacity();
}
Optional<float> SVGGraphicsElement::stroke_width() const
float SVGGraphicsElement::resolve_relative_to_viewport_size(CSS::LengthPercentage const& length_percentage) const
{
if (!layout_node())
return {};
// FIXME: Converting to pixels isn't really correct - values should be in "user units"
// https://svgwg.org/svg2-draft/coords.html#TermUserUnits
auto width = layout_node()->computed_values().stroke_width();
// Resolved relative to the "Scaled viewport size": https://www.w3.org/TR/2017/WD-fill-stroke-3-20170413/#scaled-viewport-size
// FIXME: This isn't right, but it's something.
CSSPixels viewport_width = 0;
@ -284,7 +282,21 @@ Optional<float> SVGGraphicsElement::stroke_width() const
}
}
auto scaled_viewport_size = (viewport_width + viewport_height) * CSSPixels(0.5);
return width.to_px(*layout_node(), scaled_viewport_size).to_double();
return length_percentage.to_px(*layout_node(), scaled_viewport_size).to_double();
}
Optional<float> SVGGraphicsElement::stroke_dashoffset() const
{
if (!layout_node())
return {};
return resolve_relative_to_viewport_size(layout_node()->computed_values().stroke_dashoffset());
}
Optional<float> SVGGraphicsElement::stroke_width() const
{
if (!layout_node())
return {};
return resolve_relative_to_viewport_size(layout_node()->computed_values().stroke_width());
}
// https://svgwg.org/svg2-draft/types.html#__svg__SVGGraphicsElement__getBBox

View file

@ -36,6 +36,7 @@ public:
Optional<Gfx::Color> fill_color() const;
Optional<Gfx::Color> stroke_color() const;
Optional<float> stroke_dashoffset() const;
Optional<float> stroke_width() const;
Optional<float> fill_opacity() const;
Optional<CSS::StrokeLinecap> stroke_linecap() const;
@ -94,6 +95,7 @@ protected:
private:
virtual bool is_svg_graphics_element() const final { return true; }
float resolve_relative_to_viewport_size(CSS::LengthPercentage const& length_percentage) const;
};
Gfx::AffineTransform transform_from_transform_list(ReadonlySpan<Transform> transform_list);

View file

@ -62,7 +62,7 @@ void XMLDocumentBuilder::set_source(ByteString source)
m_document->set_source(MUST(String::from_byte_string(source)));
}
void XMLDocumentBuilder::set_doctype(XML::Doctype doctype)
void XMLDocumentBuilder::doctype(XML::Doctype const& doctype)
{
if (m_document->doctype()) {
return;
@ -73,13 +73,13 @@ void XMLDocumentBuilder::set_doctype(XML::Doctype doctype)
document_type->set_name(name);
if (doctype.external_id.has_value()) {
auto external_id = doctype.external_id.release_value();
auto const& external_id = *doctype.external_id;
auto system_id = MUST(AK::String::from_byte_string(external_id.system_id.system_literal));
document_type->set_system_id(system_id);
if (external_id.public_id.has_value()) {
auto public_id = MUST(AK::String::from_byte_string(external_id.public_id.release_value().public_literal));
auto public_id = MUST(AK::String::from_byte_string(external_id.public_id->public_literal));
document_type->set_public_id(public_id);
}
}

View file

@ -31,7 +31,7 @@ public:
private:
virtual void set_source(ByteString) override;
virtual void set_doctype(XML::Doctype) override;
virtual void doctype(XML::Doctype const&) override;
virtual void element_start(XML::Name const& name, HashMap<XML::Name, ByteString> const& attributes) override;
virtual void element_end(XML::Name const& name) override;
virtual void text(StringView data) override;

View file

@ -72,6 +72,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
Optional<StringView> profile_process;
Optional<StringView> webdriver_content_ipc_path;
Optional<StringView> user_agent_preset;
Optional<StringView> dns_server_address;
Optional<u16> dns_server_port;
bool use_dns_over_tls = false;
bool log_all_js_exceptions = false;
bool enable_idl_tracing = false;
bool enable_http_cache = false;
@ -101,6 +104,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
args_parser.add_option(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation", 'g');
args_parser.add_option(dns_server_address, "Set the DNS server address", "dns-server", 0, "host|address");
args_parser.add_option(dns_server_port, "Set the DNS server port", "dns-port", 0, "port (default: 53 or 853 if --dot)");
args_parser.add_option(use_dns_over_tls, "Use DNS over TLS", "dot");
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
.help_string = "Name of the User-Agent preset to use in place of the default User-Agent",
@ -120,6 +126,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
if (force_new_process)
disable_sql_database = true;
if (!dns_server_port.has_value())
dns_server_port = use_dns_over_tls ? 853 : 53;
Optional<ProcessType> debug_process_type;
Optional<ProcessType> profile_process_type;
@ -140,6 +149,11 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
.disable_sql_database = disable_sql_database ? DisableSQLDatabase::Yes : DisableSQLDatabase::No,
.debug_helper_process = move(debug_process_type),
.profile_helper_process = move(profile_process_type),
.dns_settings = (dns_server_address.has_value()
? (use_dns_over_tls
? DNSSettings(DNSOverTLS(dns_server_address.release_value(), *dns_server_port))
: DNSSettings(DNSOverUDP(dns_server_address.release_value(), *dns_server_port)))
: SystemDNS {}),
};
if (webdriver_content_ipc_path.has_value())

View file

@ -163,7 +163,19 @@ ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
arguments.append(server.value());
}
return launch_server_process<Requests::RequestClient>("RequestServer"sv, move(arguments));
auto client = TRY(launch_server_process<Requests::RequestClient>("RequestServer"sv, move(arguments)));
WebView::Application::chrome_options().dns_settings.visit(
[](WebView::SystemDNS) {},
[&](WebView::DNSOverTLS const& dns_over_tls) {
dbgln("Setting DNS server to {}:{} with TLS", dns_over_tls.server_address, dns_over_tls.port);
client->async_set_dns_server(dns_over_tls.server_address, dns_over_tls.port, true);
},
[&](WebView::DNSOverUDP const& dns_over_udp) {
dbgln("Setting DNS server to {}:{}", dns_over_udp.server_address, dns_over_udp.port);
client->async_set_dns_server(dns_over_udp.server_address, dns_over_udp.port, false);
});
return client;
}
ErrorOr<IPC::File> connect_new_request_server_client()

View file

@ -45,6 +45,18 @@ enum class EnableAutoplay {
Yes,
};
struct SystemDNS { };
struct DNSOverTLS {
ByteString server_address;
u16 port;
};
struct DNSOverUDP {
ByteString server_address;
u16 port;
};
using DNSSettings = Variant<SystemDNS, DNSOverTLS, DNSOverUDP>;
struct ChromeOptions {
Vector<URL::URL> urls;
Vector<ByteString> raw_urls;
@ -58,6 +70,7 @@ struct ChromeOptions {
Optional<ProcessType> debug_helper_process {};
Optional<ProcessType> profile_helper_process {};
Optional<ByteString> webdriver_content_ipc_path {};
DNSSettings dns_settings { SystemDNS {} };
};
enum class IsLayoutTestMode {

View file

@ -10,6 +10,7 @@
#include <AK/TypeCasts.h>
#include <LibCore/Resource.h>
#include <LibCore/StandardPaths.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibWebView/Plugins/FontPlugin.h>

View file

@ -182,9 +182,6 @@ ErrorOr<void, ParseError> Parser::parse_with_listener(Listener& listener)
if (result.is_error())
m_listener->error(result.error());
m_listener->document_end();
if (m_doctype.has_value()) {
m_listener->set_doctype(m_doctype.release_value());
}
m_root_node.clear();
return result;
}
@ -621,7 +618,8 @@ ErrorOr<void, ParseError> Parser::parse_doctype_decl()
TRY(expect(">"sv));
rollback.disarm();
m_doctype = move(doctype);
if (m_listener)
m_listener->doctype(doctype);
return {};
}

View file

@ -34,9 +34,9 @@ struct Listener {
virtual ~Listener() { }
virtual void set_source(ByteString) { }
virtual void set_doctype(XML::Doctype) { }
virtual void document_start() { }
virtual void document_end() { }
virtual void doctype(Doctype const&) { }
virtual void element_start(Name const&, HashMap<Name, ByteString> const&) { }
virtual void element_end(Name const&) { }
virtual void text(StringView) { }

View file

@ -8,6 +8,7 @@ set(CSS_LOADER_DEBUG ON)
set(CSS_PARSER_DEBUG ON)
set(CSS_TOKENIZER_DEBUG ON)
set(CSS_TRANSITIONS_DEBUG ON)
set(DNS_DEBUG ON)
set(EDITOR_DEBUG ON)
set(EMOJI_DEBUG ON)
set(FILE_WATCHER_DEBUG ON)

Some files were not shown because too many files have changed in this diff Show more