Compare commits

...

25 commits

Author SHA1 Message Date
Lucien Fiorini
7cc71b5fa1
Merge 0ce6cd67df into 63a5717bc7 2024-11-20 22:37:47 +00: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
112 changed files with 3834 additions and 375 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

@ -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

@ -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

@ -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

@ -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

@ -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)

View file

@ -382,6 +382,7 @@ set(lagom_standard_libraries
Compress
Crypto
Diff
DNS
GC
HTTP
IPC
@ -448,6 +449,8 @@ endif()
# Lagom Utilities
lagom_utility(abench SOURCES ../../Utilities/abench.cpp LIBS LibMain LibFileSystem LibMedia)
lagom_utility(dns SOURCES ../../Utilities/dns.cpp LIBS LibDNS LibMain LibTLS LibCrypto)
if (ENABLE_GUI_TARGETS)
lagom_utility(animation SOURCES ../../Utilities/animation.cpp LIBS LibGfx LibMain)
lagom_utility(icc SOURCES ../../Utilities/icc.cpp LIBS LibGfx LibMain LibURL)

View file

@ -25,7 +25,7 @@ target_include_directories(requestserverservice PRIVATE ${CMAKE_CURRENT_BINARY_D
target_include_directories(requestserverservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)
target_link_libraries(RequestServer PRIVATE requestserverservice)
target_link_libraries(requestserverservice PUBLIC LibCore LibMain LibCrypto LibFileSystem LibIPC LibMain LibTLS LibWebView LibWebSocket LibURL LibTextCodec LibThreading CURL::libcurl)
target_link_libraries(requestserverservice PUBLIC LibCore LibDNS LibMain LibCrypto LibFileSystem LibIPC LibMain LibTLS LibWebView LibWebSocket LibURL LibTextCodec LibThreading CURL::libcurl)
if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
# Solaris has socket and networking related functions in two extra libraries

View file

@ -7,6 +7,7 @@
#include <AK/Badge.h>
#include <AK/IDAllocator.h>
#include <AK/NonnullOwnPtr.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Proxy.h>
#include <LibCore/Socket.h>
@ -24,6 +25,47 @@ ByteString g_default_certificate_path;
static HashMap<int, RefPtr<ConnectionFromClient>> s_connections;
static IDAllocator s_client_ids;
static long s_connect_timeout_seconds = 90L;
static HashMap<ByteString, ByteString> g_dns_cache; // host -> curl "resolve" string
static struct {
Optional<Core::SocketAddress> server_address;
Optional<ByteString> server_hostname;
u16 port;
bool use_dns_over_tls = true;
} g_dns_info;
static WeakPtr<Resolver> s_resolver {};
static NonnullRefPtr<Resolver> default_resolver()
{
if (auto resolver = s_resolver.strong_ref())
return *resolver;
auto resolver = make_ref_counted<Resolver>([] -> ErrorOr<DNS::Resolver::SocketResult> {
if (!g_dns_info.server_address.has_value()) {
if (!g_dns_info.server_hostname.has_value())
return Error::from_string_literal("No DNS server configured");
auto resolved = TRY(default_resolver()->dns.lookup(*g_dns_info.server_hostname)->await());
if (resolved->cached_addresses().is_empty())
return Error::from_string_literal("Failed to resolve DNS server hostname");
auto address = resolved->cached_addresses().first().visit([](auto& addr) -> Core::SocketAddress { return { addr, g_dns_info.port }; });
g_dns_info.server_address = address;
}
if (g_dns_info.use_dns_over_tls) {
return DNS::Resolver::SocketResult {
MaybeOwned<Core::Socket>(TRY(TLS::TLSv12::connect(*g_dns_info.server_address, *g_dns_info.server_hostname))),
DNS::Resolver::ConnectionMode::TCP,
};
}
return DNS::Resolver::SocketResult {
MaybeOwned<Core::Socket>(TRY(Core::BufferedSocket<Core::UDPSocket>::create(TRY(Core::UDPSocket::connect(*g_dns_info.server_address))))),
DNS::Resolver::ConnectionMode::UDP,
};
});
s_resolver = resolver;
return resolver;
}
struct ConnectionFromClient::ActiveRequest {
CURLM* multi { nullptr };
@ -199,6 +241,7 @@ int ConnectionFromClient::on_timeout_callback(void*, long timeout_ms, void* user
ConnectionFromClient::ConnectionFromClient(IPC::Transport transport)
: IPC::ConnectionFromClient<RequestClientEndpoint, RequestServerEndpoint>(*this, move(transport), s_client_ids.allocate())
, m_resolver(default_resolver())
{
s_connections.set(client_id(), *this);
@ -264,6 +307,33 @@ Messages::RequestServer::IsSupportedProtocolResponse ConnectionFromClient::is_su
return protocol == "http"sv || protocol == "https"sv;
}
void ConnectionFromClient::set_dns_server(ByteString const& host_or_address, u16 port, bool use_tls)
{
if (host_or_address == g_dns_info.server_hostname && port == g_dns_info.port && use_tls == g_dns_info.use_dns_over_tls)
return;
auto result = [&] -> ErrorOr<void> {
Core::SocketAddress addr;
if (auto v4 = IPv4Address::from_string(host_or_address); v4.has_value())
addr = { v4.value(), port };
else if (auto v6 = IPv6Address::from_string(host_or_address); v6.has_value())
addr = { v6.value(), port };
else
TRY(default_resolver()->dns.lookup(host_or_address)->await())->cached_addresses().first().visit([&](auto& address) { addr = { address, port }; });
g_dns_info.server_address = addr;
g_dns_info.server_hostname = host_or_address;
g_dns_info.port = port;
g_dns_info.use_dns_over_tls = use_tls;
return {};
}();
if (result.is_error())
dbgln("Failed to set DNS server: {}", result.error());
else
default_resolver()->dns.reset_connection();
}
void ConnectionFromClient::start_request(i32 request_id, ByteString const& method, URL::URL const& url, HTTP::HeaderMap const& request_headers, ByteBuffer const& request_body, Core::ProxyData const& proxy_data)
{
if (!url.is_valid()) {
@ -272,6 +342,22 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString const& metho
return;
}
auto host = url.serialized_host().value().to_byte_string();
auto dns_promise = m_resolver->dns.lookup(host, DNS::Messages::Class::IN, Array { DNS::Messages::ResourceType::A, DNS::Messages::ResourceType::AAAA }.span());
auto resolve_result = dns_promise->await();
if (resolve_result.is_error()) {
dbgln("StartRequest: DNS lookup failed for '{}': {}", host, resolve_result.error());
async_request_finished(request_id, 0, Requests::NetworkError::UnableToResolveHost);
return;
}
auto dns_result = resolve_result.release_value();
if (dns_result->records().is_empty()) {
dbgln("StartRequest: DNS lookup failed for '{}'", host);
async_request_finished(request_id, 0, Requests::NetworkError::UnableToResolveHost);
return;
}
auto* easy = curl_easy_init();
if (!easy) {
dbgln("StartRequest: Failed to initialize curl easy handle");
@ -349,6 +435,24 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString const& metho
set_option(CURLOPT_HEADERFUNCTION, &on_header_received);
set_option(CURLOPT_HEADERDATA, reinterpret_cast<void*>(request.ptr()));
StringBuilder resolve_opt_builder;
resolve_opt_builder.appendff("{}:{}:", host, url.port_or_default());
auto first = true;
for (auto& addr : dns_result->cached_addresses()) {
auto formatted_address = addr.visit(
[&](IPv4Address const& ipv4) { return ipv4.to_byte_string(); },
[&](IPv6Address const& ipv6) { return MUST(ipv6.to_string()).to_byte_string(); });
if (!first)
resolve_opt_builder.append(',');
first = false;
resolve_opt_builder.append(formatted_address);
}
auto formatted_address = resolve_opt_builder.to_byte_string();
g_dns_cache.set(host, formatted_address);
curl_slist* resolve_list = curl_slist_append(nullptr, formatted_address.characters());
curl_easy_setopt(easy, CURLOPT_RESOLVE, resolve_list);
auto result = curl_multi_add_handle(m_curl_multi, easy);
VERIFY(result == CURLM_OK);
@ -495,7 +599,16 @@ void ConnectionFromClient::ensure_connection(URL::URL const& url, ::RequestServe
}
if (cache_level == CacheLevel::ResolveOnly) {
dbgln("FIXME: EnsureConnection: Implement ResolveOnly cache level");
[[maybe_unused]] auto promise = m_resolver->dns.lookup(url.serialized_host().value().to_byte_string(), DNS::Messages::Class::IN, Array { DNS::Messages::ResourceType::A, DNS::Messages::ResourceType::AAAA }.span());
if constexpr (REQUESTSERVER_DEBUG) {
Core::ElapsedTimer timer;
timer.start();
promise->when_resolved([url, timer](auto const& results) -> ErrorOr<void> {
dbgln("ensure_connection::ResolveOnly({}) OK {} entrie(s) in {}ms", url, results->cached_addresses().size(), timer.elapsed());
return {};
});
promise->when_rejected([url](auto const&) { dbgln("ensure_connection::ResolveOnly({}) rejected", url); });
}
}
}

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/HashMap.h>
#include <LibDNS/Resolver.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibWebSocket/WebSocket.h>
#include <RequestServer/Forward.h>
@ -15,6 +16,16 @@
namespace RequestServer {
struct Resolver : public RefCounted<Resolver>
, Weakable<Resolver> {
Resolver(Function<ErrorOr<DNS::Resolver::SocketResult>()> create_socket)
: dns(move(create_socket))
{
}
DNS::Resolver dns;
};
class ConnectionFromClient final
: public IPC::ConnectionFromClient<RequestClientEndpoint, RequestServerEndpoint> {
C_OBJECT(ConnectionFromClient);
@ -34,6 +45,7 @@ private:
virtual Messages::RequestServer::ConnectNewClientResponse connect_new_client() override;
virtual Messages::RequestServer::IsSupportedProtocolResponse is_supported_protocol(ByteString const&) override;
virtual void set_dns_server(ByteString const& host_or_address, u16 port, bool use_tls) override;
virtual void start_request(i32 request_id, ByteString const&, URL::URL const&, HTTP::HeaderMap const&, ByteBuffer const&, Core::ProxyData const&) override;
virtual Messages::RequestServer::StopRequestResponse stop_request(i32) override;
virtual Messages::RequestServer::SetCertificateResponse set_certificate(i32, ByteString const&, ByteString const&) override;
@ -61,6 +73,7 @@ private:
RefPtr<Core::Timer> m_timer;
HashMap<int, NonnullRefPtr<Core::Notifier>> m_read_notifiers;
HashMap<int, NonnullRefPtr<Core::Notifier>> m_write_notifiers;
NonnullRefPtr<Resolver> m_resolver;
};
}

View file

@ -7,6 +7,9 @@ endpoint RequestServer
{
connect_new_client() => (IPC::File client_socket)
// use_tls: enable DNS over TLS
set_dns_server(ByteString host_or_address, u16 port, bool use_tls) =|
// Test if a specific protocol is supported, e.g "http"
is_supported_protocol(ByteString protocol) => (bool supported)

View file

@ -1 +1 @@
Cloned CDATASection node data: PASS
Cloned #cdata-section node data: PASS

View file

@ -0,0 +1,5 @@
readyState of 'new Document()' should be 'complete': 'complete'
readyState of 'document.implementation.createHTMLDocument()' should be 'complete': 'complete'
readyState of 'document.implementation.createDocument()' should be 'complete': 'complete'
FIXME: readyState of 'new DOMParser().parseFromString('', 'text/html')' should be 'complete': 'interactive'
readyState of 'iframe.contentDocument' of initial about:blank iframe should be 'complete': 'complete'

View file

@ -0,0 +1 @@
image is 400x400

View file

@ -1,6 +1,6 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': ''
'length': '202'
'length': '203'
'parentRule': 'null'
'cssFloat': 'none'
'WebkitAlignContent': 'normal'
@ -495,6 +495,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati
'stopOpacity': '1'
'stop-opacity': '1'
'stroke': 'none'
'strokeDashoffset': '0'
'stroke-dashoffset': '0'
'strokeLinecap': 'butt'
'stroke-linecap': 'butt'
'strokeLinejoin': 'miter'

View file

@ -33,175 +33,176 @@ All properties associated with getComputedStyle(document.body):
"30": "pointer-events",
"31": "quotes",
"32": "stroke",
"33": "stroke-linecap",
"34": "stroke-linejoin",
"35": "stroke-miterlimit",
"36": "stroke-opacity",
"37": "stroke-width",
"38": "tab-size",
"39": "text-align",
"40": "text-anchor",
"41": "text-decoration-line",
"42": "text-indent",
"43": "text-justify",
"44": "text-shadow",
"45": "text-transform",
"46": "visibility",
"47": "white-space",
"48": "word-break",
"49": "word-spacing",
"50": "word-wrap",
"51": "writing-mode",
"52": "align-content",
"53": "align-items",
"54": "align-self",
"55": "animation-delay",
"56": "animation-direction",
"57": "animation-duration",
"58": "animation-fill-mode",
"59": "animation-iteration-count",
"60": "animation-name",
"61": "animation-play-state",
"62": "animation-timing-function",
"63": "appearance",
"64": "aspect-ratio",
"65": "backdrop-filter",
"66": "background-attachment",
"67": "background-clip",
"68": "background-color",
"69": "background-image",
"70": "background-origin",
"71": "background-position-x",
"72": "background-position-y",
"73": "background-repeat",
"74": "background-size",
"75": "border-bottom-color",
"76": "border-bottom-left-radius",
"77": "border-bottom-right-radius",
"78": "border-bottom-style",
"79": "border-bottom-width",
"80": "border-left-color",
"81": "border-left-style",
"82": "border-left-width",
"83": "border-right-color",
"84": "border-right-style",
"85": "border-right-width",
"86": "border-top-color",
"87": "border-top-left-radius",
"88": "border-top-right-radius",
"89": "border-top-style",
"90": "border-top-width",
"91": "bottom",
"92": "box-shadow",
"93": "box-sizing",
"94": "clear",
"95": "clip",
"96": "clip-path",
"97": "column-count",
"98": "column-gap",
"99": "column-span",
"100": "column-width",
"101": "content",
"102": "content-visibility",
"103": "counter-increment",
"104": "counter-reset",
"105": "counter-set",
"106": "cx",
"107": "cy",
"108": "display",
"109": "filter",
"110": "flex-basis",
"111": "flex-direction",
"112": "flex-grow",
"113": "flex-shrink",
"114": "flex-wrap",
"115": "float",
"116": "grid-auto-columns",
"117": "grid-auto-flow",
"118": "grid-auto-rows",
"119": "grid-column-end",
"120": "grid-column-start",
"121": "grid-row-end",
"122": "grid-row-start",
"123": "grid-template-areas",
"124": "grid-template-columns",
"125": "grid-template-rows",
"126": "height",
"127": "inline-size",
"128": "inset-block-end",
"129": "inset-block-start",
"130": "inset-inline-end",
"131": "inset-inline-start",
"132": "justify-content",
"133": "justify-items",
"134": "justify-self",
"135": "left",
"136": "margin-block-end",
"137": "margin-block-start",
"138": "margin-bottom",
"139": "margin-inline-end",
"140": "margin-inline-start",
"141": "margin-left",
"142": "margin-right",
"143": "margin-top",
"144": "mask",
"145": "mask-image",
"146": "mask-type",
"147": "max-height",
"148": "max-inline-size",
"149": "max-width",
"150": "min-height",
"151": "min-inline-size",
"152": "min-width",
"153": "object-fit",
"154": "object-position",
"155": "opacity",
"156": "order",
"157": "outline-color",
"158": "outline-offset",
"159": "outline-style",
"160": "outline-width",
"161": "overflow-x",
"162": "overflow-y",
"163": "padding-block-end",
"164": "padding-block-start",
"165": "padding-bottom",
"166": "padding-inline-end",
"167": "padding-inline-start",
"168": "padding-left",
"169": "padding-right",
"170": "padding-top",
"171": "position",
"172": "r",
"173": "right",
"174": "rotate",
"175": "row-gap",
"176": "rx",
"177": "ry",
"178": "scrollbar-gutter",
"179": "scrollbar-width",
"180": "stop-color",
"181": "stop-opacity",
"182": "table-layout",
"183": "text-decoration-color",
"184": "text-decoration-style",
"185": "text-decoration-thickness",
"186": "text-overflow",
"187": "top",
"188": "transform",
"189": "transform-box",
"190": "transform-origin",
"191": "transition-delay",
"192": "transition-duration",
"193": "transition-property",
"194": "transition-timing-function",
"195": "unicode-bidi",
"196": "user-select",
"197": "vertical-align",
"198": "width",
"199": "x",
"200": "y",
"201": "z-index"
"33": "stroke-dashoffset",
"34": "stroke-linecap",
"35": "stroke-linejoin",
"36": "stroke-miterlimit",
"37": "stroke-opacity",
"38": "stroke-width",
"39": "tab-size",
"40": "text-align",
"41": "text-anchor",
"42": "text-decoration-line",
"43": "text-indent",
"44": "text-justify",
"45": "text-shadow",
"46": "text-transform",
"47": "visibility",
"48": "white-space",
"49": "word-break",
"50": "word-spacing",
"51": "word-wrap",
"52": "writing-mode",
"53": "align-content",
"54": "align-items",
"55": "align-self",
"56": "animation-delay",
"57": "animation-direction",
"58": "animation-duration",
"59": "animation-fill-mode",
"60": "animation-iteration-count",
"61": "animation-name",
"62": "animation-play-state",
"63": "animation-timing-function",
"64": "appearance",
"65": "aspect-ratio",
"66": "backdrop-filter",
"67": "background-attachment",
"68": "background-clip",
"69": "background-color",
"70": "background-image",
"71": "background-origin",
"72": "background-position-x",
"73": "background-position-y",
"74": "background-repeat",
"75": "background-size",
"76": "border-bottom-color",
"77": "border-bottom-left-radius",
"78": "border-bottom-right-radius",
"79": "border-bottom-style",
"80": "border-bottom-width",
"81": "border-left-color",
"82": "border-left-style",
"83": "border-left-width",
"84": "border-right-color",
"85": "border-right-style",
"86": "border-right-width",
"87": "border-top-color",
"88": "border-top-left-radius",
"89": "border-top-right-radius",
"90": "border-top-style",
"91": "border-top-width",
"92": "bottom",
"93": "box-shadow",
"94": "box-sizing",
"95": "clear",
"96": "clip",
"97": "clip-path",
"98": "column-count",
"99": "column-gap",
"100": "column-span",
"101": "column-width",
"102": "content",
"103": "content-visibility",
"104": "counter-increment",
"105": "counter-reset",
"106": "counter-set",
"107": "cx",
"108": "cy",
"109": "display",
"110": "filter",
"111": "flex-basis",
"112": "flex-direction",
"113": "flex-grow",
"114": "flex-shrink",
"115": "flex-wrap",
"116": "float",
"117": "grid-auto-columns",
"118": "grid-auto-flow",
"119": "grid-auto-rows",
"120": "grid-column-end",
"121": "grid-column-start",
"122": "grid-row-end",
"123": "grid-row-start",
"124": "grid-template-areas",
"125": "grid-template-columns",
"126": "grid-template-rows",
"127": "height",
"128": "inline-size",
"129": "inset-block-end",
"130": "inset-block-start",
"131": "inset-inline-end",
"132": "inset-inline-start",
"133": "justify-content",
"134": "justify-items",
"135": "justify-self",
"136": "left",
"137": "margin-block-end",
"138": "margin-block-start",
"139": "margin-bottom",
"140": "margin-inline-end",
"141": "margin-inline-start",
"142": "margin-left",
"143": "margin-right",
"144": "margin-top",
"145": "mask",
"146": "mask-image",
"147": "mask-type",
"148": "max-height",
"149": "max-inline-size",
"150": "max-width",
"151": "min-height",
"152": "min-inline-size",
"153": "min-width",
"154": "object-fit",
"155": "object-position",
"156": "opacity",
"157": "order",
"158": "outline-color",
"159": "outline-offset",
"160": "outline-style",
"161": "outline-width",
"162": "overflow-x",
"163": "overflow-y",
"164": "padding-block-end",
"165": "padding-block-start",
"166": "padding-bottom",
"167": "padding-inline-end",
"168": "padding-inline-start",
"169": "padding-left",
"170": "padding-right",
"171": "padding-top",
"172": "position",
"173": "r",
"174": "right",
"175": "rotate",
"176": "row-gap",
"177": "rx",
"178": "ry",
"179": "scrollbar-gutter",
"180": "scrollbar-width",
"181": "stop-color",
"182": "stop-opacity",
"183": "table-layout",
"184": "text-decoration-color",
"185": "text-decoration-style",
"186": "text-decoration-thickness",
"187": "text-overflow",
"188": "top",
"189": "transform",
"190": "transform-box",
"191": "transform-origin",
"192": "transition-delay",
"193": "transition-duration",
"194": "transition-property",
"195": "transition-timing-function",
"196": "unicode-bidi",
"197": "user-select",
"198": "vertical-align",
"199": "width",
"200": "x",
"201": "y",
"202": "z-index"
}
All properties associated with document.body.style by default:
{}

View file

@ -31,6 +31,7 @@ math-style: normal
pointer-events: auto
quotes: auto
stroke: none
stroke-dashoffset: 0
stroke-linecap: butt
stroke-linejoin: miter
stroke-miterlimit: 4
@ -124,7 +125,7 @@ grid-row-start: auto
grid-template-areas: none
grid-template-columns: auto
grid-template-rows: auto
height: 2142px
height: 2159px
inline-size: auto
inset-block-end: auto
inset-block-start: auto

View file

@ -0,0 +1,17 @@
Summary
Harness status: OK
Rerun
Found 7 tests
7 Pass
Details
Result Test Name MessagePass For Element nodes, nodeName should return the same as tagName.
Pass For Text nodes, nodeName should return "#text".
Pass For ProcessingInstruction nodes, nodeName should return the target.
Pass For Comment nodes, nodeName should return "#comment".
Pass For Document nodes, nodeName should return "#document".
Pass For DocumentType nodes, nodeName should return the name.
Pass For DocumentFragment nodes, nodeName should return "#document-fragment".

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