Commit ac6b5d30 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #555 from capnproto/network-filter

Extend kj::Network interface for easy SSRF protection
parents b3dec708 04ff4676
......@@ -198,6 +198,19 @@ kj::LowLevelAsyncIoProvider& EzRpcClient::getLowLevelIoProvider() {
// =======================================================================================
namespace {
class DummyFilter: public kj::LowLevelAsyncIoProvider::NetworkFilter {
public:
bool shouldAllow(const struct sockaddr* addr, uint addrlen) override {
return true;
}
};
static DummyFilter DUMMY_FILTER;
} // namespace
struct EzRpcServer::Impl final: public SturdyRefRestorer<AnyPointer>,
public kj::TaskSet::ErrorHandler {
Capability::Client mainInterface;
......@@ -271,7 +284,8 @@ struct EzRpcServer::Impl final: public SturdyRefRestorer<AnyPointer>,
context(EzRpcContext::getThreadLocal()),
portPromise(kj::Promise<uint>(port).fork()),
tasks(*this) {
acceptLoop(context->getLowLevelIoProvider().wrapListenSocketFd(socketFd), readerOpts);
acceptLoop(context->getLowLevelIoProvider().wrapListenSocketFd(socketFd, DUMMY_FILTER),
readerOpts);
}
void acceptLoop(kj::Own<kj::ConnectionReceiver>&& listener, ReaderOptions readerOpts) {
......
// Copyright (c) 2017 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef KJ_ASYNC_IO_INTERNAL_H_
#define KJ_ASYNC_IO_INTERNAL_H_
#include "string.h"
#include "vector.h"
#include "async-io.h"
#include <stdint.h>
struct sockaddr;
struct sockaddr_un;
namespace kj {
namespace _ { // private
// =======================================================================================
#if !_WIN32
kj::ArrayPtr<const char> safeUnixPath(const struct sockaddr_un* addr, uint addrlen);
// sockaddr_un::sun_path is not required to have a NUL terminator! Thus to be safe unix address
// paths MUST be read using this function.
#endif
class CidrRange {
public:
CidrRange(StringPtr pattern);
static CidrRange inet4(ArrayPtr<const byte> bits, uint bitCount);
static CidrRange inet6(ArrayPtr<const uint16_t> prefix, ArrayPtr<const uint16_t> suffix,
uint bitCount);
// Zeros are inserted between `prefix` and `suffix` to extend the address to 128 bits.
uint getSpecificity() const { return bitCount; }
bool matches(const struct sockaddr* addr) const;
bool matchesFamily(int family) const;
String toString() const;
private:
int family;
byte bits[16];
uint bitCount; // how many bits in `bits` need to match
CidrRange(int family, ArrayPtr<const byte> bits, uint bitCount);
void zeroIrrelevantBits();
};
class NetworkFilter: public LowLevelAsyncIoProvider::NetworkFilter {
public:
NetworkFilter();
NetworkFilter(ArrayPtr<const StringPtr> allow, ArrayPtr<const StringPtr> deny,
NetworkFilter& next);
bool shouldAllow(const struct sockaddr* addr, uint addrlen) override;
bool shouldAllowParse(const struct sockaddr* addr, uint addrlen);
private:
Vector<CidrRange> allowCidrs;
Vector<CidrRange> denyCidrs;
bool allowUnix;
bool allowAbstractUnix;
kj::Maybe<NetworkFilter&> next;
};
} // namespace _ (private)
} // namespace kj
#endif // KJ_ASYNC_IO_INTERNAL_H_
......@@ -19,17 +19,27 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if _WIN32
// Request Vista-level APIs.
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#endif
#include "async-io.h"
#include "async-io-internal.h"
#include "debug.h"
#include <kj/compat/gtest.h>
#include <sys/types.h>
#if _WIN32
#include <ws2tcpip.h>
#include "windows-sanity.h"
#define inet_pton InetPtonA
#define inet_ntop InetNtopA
#else
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#endif
namespace kj {
......@@ -77,12 +87,13 @@ String tryParse(WaitScope& waitScope, Network& network, StringPtr text, uint por
return network.parseAddress(text, portHint).wait(waitScope)->toString();
}
bool systemSupportsAddress(StringPtr addr) {
bool systemSupportsAddress(StringPtr addr, StringPtr service = nullptr) {
// Can getaddrinfo() parse this addresses? This is only true if the address family (e.g., ipv6)
// is configured on at least one interface. (The loopback interface usually has both ipv4 and
// ipv6 configured, but not always.)
struct addrinfo* list;
int status = getaddrinfo(addr.cStr(), nullptr, nullptr, &list);
int status = getaddrinfo(
addr.cStr(), service == nullptr ? nullptr : service.cStr(), nullptr, &list);
if (status == 0) {
freeaddrinfo(list);
return true;
......@@ -91,7 +102,6 @@ bool systemSupportsAddress(StringPtr addr) {
}
}
TEST(AsyncIo, AddressParsing) {
auto ioContext = setupAsyncIo();
auto& w = ioContext.waitScope;
......@@ -110,7 +120,7 @@ TEST(AsyncIo, AddressParsing) {
// We can parse services by name...
//
// For some reason, Android and some various Linux distros do not support service names.
if (systemSupportsAddress("1.2.3.4:http")) {
if (systemSupportsAddress("1.2.3.4", "http")) {
EXPECT_EQ("1.2.3.4:80", tryParse(w, network, "1.2.3.4:http", 5678));
EXPECT_EQ("*:80", tryParse(w, network, "*:http", 5678));
} else {
......@@ -122,7 +132,7 @@ TEST(AsyncIo, AddressParsing) {
if (systemSupportsAddress("::")) {
EXPECT_EQ("[::]:123", tryParse(w, network, "0::0", 123));
EXPECT_EQ("[12ab:cd::34]:321", tryParse(w, network, "[12ab:cd:0::0:34]:321", 432));
if (systemSupportsAddress("[12ab:cd::34]:http")) {
if (systemSupportsAddress("12ab:cd::34", "http")) {
EXPECT_EQ("[::]:80", tryParse(w, network, "[::]:http", 5678));
EXPECT_EQ("[12ab:cd::34]:80", tryParse(w, network, "[12ab:cd::34]:http", 5678));
} else {
......@@ -412,5 +422,154 @@ TEST(AsyncIo, AbstractUnixSocket) {
#endif // __linux__
KJ_TEST("CIDR parsing") {
KJ_EXPECT(_::CidrRange("1.2.3.4/16").toString() == "1.2.0.0/16");
KJ_EXPECT(_::CidrRange("1.2.255.4/18").toString() == "1.2.192.0/18");
KJ_EXPECT(_::CidrRange("1234::abcd:ffff:ffff/98").toString() == "1234::abcd:c000:0/98");
KJ_EXPECT(_::CidrRange::inet4({1,2,255,4}, 18).toString() == "1.2.192.0/18");
KJ_EXPECT(_::CidrRange::inet6({0x1234, 0x5678}, {0xabcd, 0xffff, 0xffff}, 98).toString() ==
"1234:5678::abcd:c000:0/98");
union {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
};
memset(&addr6, 0, sizeof(addr6));
{
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = htonl(0x0102dfff);
KJ_EXPECT(_::CidrRange("1.2.255.255/18").matches(&addr));
KJ_EXPECT(!_::CidrRange("1.2.255.255/19").matches(&addr));
KJ_EXPECT(_::CidrRange("1.2.0.0/16").matches(&addr));
KJ_EXPECT(!_::CidrRange("1.3.0.0/16").matches(&addr));
KJ_EXPECT(_::CidrRange("1.2.223.255/32").matches(&addr));
KJ_EXPECT(_::CidrRange("0.0.0.0/0").matches(&addr));
KJ_EXPECT(!_::CidrRange("::/0").matches(&addr));
}
{
addr4.sin_family = AF_INET6;
byte bytes[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
memcpy(addr6.sin6_addr.s6_addr, bytes, 16);
KJ_EXPECT(_::CidrRange("0102:03ff::/24").matches(&addr));
KJ_EXPECT(!_::CidrRange("0102:02ff::/24").matches(&addr));
KJ_EXPECT(_::CidrRange("0102:02ff::/23").matches(&addr));
KJ_EXPECT(_::CidrRange("0102:0304:0506:0708:090a:0b0c:0d0e:0f10/128").matches(&addr));
KJ_EXPECT(_::CidrRange("::/0").matches(&addr));
KJ_EXPECT(!_::CidrRange("0.0.0.0/0").matches(&addr));
}
{
addr4.sin_family = AF_INET6;
inet_pton(AF_INET6, "::ffff:1.2.223.255", &addr6.sin6_addr);
KJ_EXPECT(_::CidrRange("1.2.255.255/18").matches(&addr));
KJ_EXPECT(!_::CidrRange("1.2.255.255/19").matches(&addr));
KJ_EXPECT(_::CidrRange("1.2.0.0/16").matches(&addr));
KJ_EXPECT(!_::CidrRange("1.3.0.0/16").matches(&addr));
KJ_EXPECT(_::CidrRange("1.2.223.255/32").matches(&addr));
KJ_EXPECT(_::CidrRange("0.0.0.0/0").matches(&addr));
KJ_EXPECT(_::CidrRange("::/0").matches(&addr));
}
}
bool allowed4(_::NetworkFilter& filter, StringPtr addrStr) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, addrStr.cStr(), &addr.sin_addr);
return filter.shouldAllow(reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
}
bool allowed6(_::NetworkFilter& filter, StringPtr addrStr) {
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, addrStr.cStr(), &addr.sin6_addr);
return filter.shouldAllow(reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
}
KJ_TEST("NetworkFilter") {
_::NetworkFilter base;
KJ_EXPECT(allowed4(base, "8.8.8.8"));
KJ_EXPECT(!allowed4(base, "240.1.2.3"));
{
_::NetworkFilter filter({"public"}, {}, base);
KJ_EXPECT(allowed4(filter, "8.8.8.8"));
KJ_EXPECT(!allowed4(filter, "240.1.2.3"));
KJ_EXPECT(!allowed4(filter, "192.168.0.1"));
KJ_EXPECT(!allowed4(filter, "10.1.2.3"));
KJ_EXPECT(!allowed4(filter, "127.0.0.1"));
KJ_EXPECT(!allowed4(filter, "0.0.0.0"));
KJ_EXPECT(allowed6(filter, "2400:cb00:2048:1::c629:d7a2"));
KJ_EXPECT(!allowed6(filter, "fc00::1234"));
KJ_EXPECT(!allowed6(filter, "::1"));
KJ_EXPECT(!allowed6(filter, "::"));
}
{
_::NetworkFilter filter({"private"}, {"local"}, base);
KJ_EXPECT(!allowed4(filter, "8.8.8.8"));
KJ_EXPECT(!allowed4(filter, "240.1.2.3"));
KJ_EXPECT(allowed4(filter, "192.168.0.1"));
KJ_EXPECT(allowed4(filter, "10.1.2.3"));
KJ_EXPECT(!allowed4(filter, "127.0.0.1"));
KJ_EXPECT(!allowed4(filter, "0.0.0.0"));
KJ_EXPECT(!allowed6(filter, "2400:cb00:2048:1::c629:d7a2"));
KJ_EXPECT(allowed6(filter, "fc00::1234"));
KJ_EXPECT(!allowed6(filter, "::1"));
KJ_EXPECT(!allowed6(filter, "::"));
}
{
_::NetworkFilter filter({"1.0.0.0/8", "1.2.3.0/24"}, {"1.2.0.0/16", "1.2.3.4/32"}, base);
KJ_EXPECT(!allowed4(filter, "8.8.8.8"));
KJ_EXPECT(!allowed4(filter, "240.1.2.3"));
KJ_EXPECT(allowed4(filter, "1.0.0.1"));
KJ_EXPECT(!allowed4(filter, "1.2.2.1"));
KJ_EXPECT(allowed4(filter, "1.2.3.1"));
KJ_EXPECT(!allowed4(filter, "1.2.3.4"));
}
}
KJ_TEST("Network::restrictPeers()") {
auto ioContext = setupAsyncIo();
auto& w = ioContext.waitScope;
auto& network = ioContext.provider->getNetwork();
auto restrictedNetwork = network.restrictPeers({"public"});
KJ_EXPECT(tryParse(w, *restrictedNetwork, "8.8.8.8") == "8.8.8.8:0");
#if !_WIN32
KJ_EXPECT_THROW_MESSAGE("restrictPeers", tryParse(w, *restrictedNetwork, "unix:/foo"));
#endif
auto addr = restrictedNetwork->parseAddress("127.0.0.1").wait(w);
auto listener = addr->listen();
auto acceptTask = listener->accept()
.then([](kj::Own<kj::AsyncIoStream>) {
KJ_FAIL_EXPECT("should not have received connection");
}).eagerlyEvaluate(nullptr);
KJ_EXPECT_THROW_MESSAGE("restrictPeers", addr->connect().wait(w));
// We can connect to the listener but the connection will be immediately closed.
auto addr2 = network.parseAddress("127.0.0.1", listener->getPort()).wait(w);
auto conn = addr2->connect().wait(w);
KJ_EXPECT(conn->readAllText().wait(w) == "");
}
} // namespace
} // namespace kj
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -319,6 +319,67 @@ public:
virtual Own<NetworkAddress> getSockaddr(const void* sockaddr, uint len) = 0;
// Construct a network address from a legacy struct sockaddr.
virtual Own<Network> restrictPeers(
kj::ArrayPtr<const kj::StringPtr> allow,
kj::ArrayPtr<const kj::StringPtr> deny = nullptr) KJ_WARN_UNUSED_RESULT = 0;
// Constructs a new Network instance wrapping this one which restricts which peer addresses are
// permitted (both for outgoing and incoming connections).
//
// Communication will be allowed only with peers whose addresses match one of the patterns
// specified in the `allow` array. If a `deny` array is specified, then any address which matches
// a pattern in `deny` and *does not* match any more-specific pattern in `allow` will also be
// denied.
//
// The syntax of address patterns depends on the network, except that three special patterns are
// defined for all networks:
// - "private": Matches network addresses that are reserved by standards for private networks,
// such as "10.0.0.0/8" or "192.168.0.0/16". This is a superset of "local".
// - "public": Opposite of "private".
// - "local": Matches network addresses that are defined by standards to only be accessible from
// the local machine, such as "127.0.0.0/8" or Unix domain addresses.
// - "network": Opposite of "local".
//
// For the standard KJ network implementation, the following patterns are also recognized:
// - Network blocks specified in CIDR notation (ipv4 and ipv6), such as "192.0.2.0/24" or
// "2001:db8::/32".
// - "unix" to match all Unix domain addresses. (In the future, we may support specifying a
// glob.)
// - "unix-abstract" to match Linux's "abstract unix domain" addresses. (In the future, we may
// support specifying a glob.)
//
// Network restrictions apply *after* DNS resolution (otherwise they'd be useless).
//
// It is legal to parseAddress() a restricted address. An exception won't be thrown until
// connect() is called.
//
// It's possible to listen() on a restricted address. However, connections will only be accepted
// from non-restricted addresses; others will be dropped. If a particular listen address has no
// valid peers (e.g. because it's a unix socket address and unix sockets are not allowed) then
// listen() may throw (or may simply never receive any connections).
//
// Examples:
//
// auto restricted = network->restrictPeers({"public"});
//
// Allows connections only to/from public internet addresses. Use this when connecting to an
// address specified by a third party that is not trusted and is not themselves already on your
// private network.
//
// auto restricted = network->restrictPeers({"private"});
//
// Allows connections only to/from the private network. Use this on the server side to reject
// connections from the public internet.
//
// auto restricted = network->restrictPeers({"192.0.2.0/24"}, {"192.0.2.3/32"});
//
// Allows connections only to/from 192.0.2.*, except 192.0.2.3 which is blocked.
//
// auto restricted = network->restrictPeers({"10.0.0.0/8", "10.1.2.3/32"}, {"10.1.2.0/24"});
//
// Allows connections to/from 10.*.*.*, with the exception of 10.1.2.* (which is denied), with an
// exception to the exception of 10.1.2.3 (which is allowed, because it is matched by an allow
// rule that is more specific than the deny rule).
};
// =======================================================================================
......@@ -470,13 +531,21 @@ public:
//
// `flags` is a bitwise-OR of the values of the `Flags` enum.
virtual Own<ConnectionReceiver> wrapListenSocketFd(Fd fd, uint flags = 0) = 0;
class NetworkFilter {
public:
virtual bool shouldAllow(const struct sockaddr* addr, uint addrlen) = 0;
// Returns true if incoming connections or datagrams from the given peer should be accepted.
// If false, they will be dropped. This is used to implement kj::Network::restrictPeers().
};
virtual Own<ConnectionReceiver> wrapListenSocketFd(
Fd fd, NetworkFilter& filter, uint flags = 0) = 0;
// Create an AsyncIoStream wrapping a listen socket file descriptor. This socket should already
// have had `bind()` and `listen()` called on it, so it's ready for `accept()`.
//
// `flags` is a bitwise-OR of the values of the `Flags` enum.
virtual Own<DatagramPort> wrapDatagramSocketFd(Fd fd, uint flags = 0);
virtual Own<DatagramPort> wrapDatagramSocketFd(Fd fd, NetworkFilter& filter, uint flags = 0);
virtual Timer& getTimer() = 0;
// Returns a `Timer` based on real time. Time does not pass while event handlers are running --
......
......@@ -1280,7 +1280,7 @@ public:
return ArrayPtr<const T>(ptr, size_);
}
inline size_t size() const { return size_; }
inline constexpr size_t size() const { return size_; }
inline const T& operator[](size_t index) const {
KJ_IREQUIRE(index < size_, "Out-of-bounds ArrayPtr access.");
return ptr[index];
......@@ -1294,8 +1294,8 @@ public:
inline T* end() { return ptr + size_; }
inline T& front() { return *ptr; }
inline T& back() { return *(ptr + size_ - 1); }
inline const T* begin() const { return ptr; }
inline const T* end() const { return ptr + size_; }
inline constexpr const T* begin() const { return ptr; }
inline constexpr const T* end() const { return ptr + size_; }
inline const T& front() const { return *ptr; }
inline const T& back() const { return *(ptr + size_ - 1); }
......
......@@ -443,6 +443,8 @@ private:
class TlsNetwork: public kj::Network {
public:
TlsNetwork(TlsContext& tls, kj::Network& inner): tls(tls), inner(inner) {}
TlsNetwork(TlsContext& tls, kj::Own<kj::Network> inner)
: tls(tls), inner(*inner), ownInner(kj::mv(inner)) {}
Promise<Own<NetworkAddress>> parseAddress(StringPtr addr, uint portHint) override {
kj::String hostname;
......@@ -463,9 +465,19 @@ public:
KJ_UNIMPLEMENTED("TLS does not implement getSockaddr() because it needs to know hostnames");
}
Own<Network> restrictPeers(
kj::ArrayPtr<const kj::StringPtr> allow,
kj::ArrayPtr<const kj::StringPtr> deny = nullptr) override {
// TODO(someday): Maybe we could implement the ability to specify CA or hostname restrictions?
// Or is it better to let people do that via the TlsContext? A neat thing about
// restrictPeers() is that it's easy to make user-configurable.
return kj::heap<TlsNetwork>(tls, inner.restrictPeers(allow, deny));
}
private:
TlsContext& tls;
kj::Network& inner;
kj::Own<kj::Network> ownInner;
};
} // namespace
......
......@@ -173,6 +173,18 @@ TEST(String, ToString) {
}
#endif
KJ_TEST("string literals with _kj suffix") {
static constexpr StringPtr FOO = "foo"_kj;
KJ_EXPECT(FOO == "foo", FOO);
KJ_EXPECT(FOO[3] == 0);
KJ_EXPECT("foo\0bar"_kj == StringPtr("foo\0bar", 7));
static constexpr ArrayPtr<const char> ARR = "foo"_kj;
KJ_EXPECT(ARR.size() == 3);
KJ_EXPECT(kj::str(ARR) == "foo");
}
} // namespace
} // namespace _ (private)
} // namespace kj
......@@ -31,11 +31,29 @@
#include <string.h>
namespace kj {
class StringPtr;
class String;
class StringPtr;
class String;
class StringTree; // string-tree.h
}
class StringTree; // string-tree.h
constexpr kj::StringPtr operator "" _kj(const char* str, size_t n);
// You can append _kj to a string literal to make its type be StringPtr. There are a few cases
// where you must do this for correctness:
// - When you want to declare a constexpr StringPtr. Without _kj, this is a compile error.
// - When you want to initialize a static/global StringPtr from a string literal without forcing
// global constructor code to run at dynamic initialization time.
// - When you have a string literal that contains NUL characters. Without _kj, the string will
// be considered to end at the first NUL.
// - When you want to initialize an ArrayPtr<const char> from a string literal, without including
// the NUL terminator in the data. (Initializing an ArrayPtr from a regular string literal is
// a compile error specifically due to this ambiguity.)
//
// In other cases, there should be no difference between initializing a StringPtr from a regular
// string literal vs. one with _kj (assuming the compiler is able to optimize away strlen() on a
// string literal).
namespace kj {
// Our STL string SFINAE trick does not work with GCC 4.7, but it works with Clang and GCC 4.8, so
// we'll just preprocess it out if not supported.
......@@ -75,8 +93,8 @@ public:
// those who don't want it.
#endif
inline operator ArrayPtr<const char>() const;
inline ArrayPtr<const char> asArray() const;
inline constexpr operator ArrayPtr<const char>() const;
inline constexpr ArrayPtr<const char> asArray() const;
inline ArrayPtr<const byte> asBytes() const { return asArray().asBytes(); }
// Result does not include NUL terminator.
......@@ -121,9 +139,11 @@ public:
// Overflowed floating numbers return inf.
private:
inline StringPtr(ArrayPtr<const char> content): content(content) {}
inline constexpr StringPtr(ArrayPtr<const char> content): content(content) {}
ArrayPtr<const char> content;
friend constexpr kj::StringPtr (::operator "" _kj)(const char* str, size_t n);
};
inline bool operator==(const char* a, const StringPtr& b) { return b == a; }
......@@ -427,12 +447,12 @@ inline String Stringifier::operator*(const Array<T>& arr) const {
inline StringPtr::StringPtr(const String& value): content(value.begin(), value.size() + 1) {}
inline StringPtr::operator ArrayPtr<const char>() const {
return content.slice(0, content.size() - 1);
inline constexpr StringPtr::operator ArrayPtr<const char>() const {
return ArrayPtr<const char>(content.begin(), content.size() - 1);
}
inline ArrayPtr<const char> StringPtr::asArray() const {
return content.slice(0, content.size() - 1);
inline constexpr ArrayPtr<const char> StringPtr::asArray() const {
return ArrayPtr<const char>(content.begin(), content.size() - 1);
}
inline bool StringPtr::operator==(const StringPtr& other) const {
......@@ -531,4 +551,8 @@ inline String heapString(ArrayPtr<const char> value) {
} // namespace kj
constexpr kj::StringPtr operator "" _kj(const char* str, size_t n) {
return kj::StringPtr(kj::ArrayPtr<const char>(str, n + 1));
};
#endif // KJ_STRING_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment