Commit 9032d060 authored by Kenton Varda's avatar Kenton Varda

Merge branch 'master' of github.com:kentonv/capnproto

parents 5b34c066 c4943308
cmake_minimum_required(VERSION 3.1)
project("Cap'n Proto Root" CXX)
add_subdirectory(c++)
project("Cap'n Proto" CXX)
cmake_minimum_required(VERSION 3.1)
project("Cap'n Proto" CXX)
set(VERSION 0.7-dev)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
......
......@@ -49,6 +49,7 @@ struct FieldHash {
struct JsonCodec::Impl {
bool prettyPrint = false;
HasMode hasMode = HasMode::NON_NULL;
size_t maxNestingDepth = 64;
std::unordered_map<Type, HandlerBase*, TypeHash> typeHandlers;
......@@ -195,6 +196,8 @@ void JsonCodec::setMaxNestingDepth(size_t maxNestingDepth) {
impl->maxNestingDepth = maxNestingDepth;
}
void JsonCodec::setHasMode(HasMode mode) { impl->hasMode = mode; }
kj::String JsonCodec::encode(DynamicValue::Reader value, Type type) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
......@@ -308,7 +311,7 @@ void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder
uint fieldCount = 0;
for (auto i: kj::indices(nonUnionFields)) {
fieldCount += (hasField[i] = structValue.has(nonUnionFields[i]));
fieldCount += (hasField[i] = structValue.has(nonUnionFields[i], impl->hasMode));
}
// We try to write the union field, if any, in proper order with the rest.
......@@ -318,7 +321,7 @@ void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder
KJ_IF_MAYBE(field, which) {
// Even if the union field is null, if it is not the default field of the union then we
// have to print it anyway.
unionFieldIsNull = !structValue.has(*field);
unionFieldIsNull = !structValue.has(*field, impl->hasMode);
if (field->getProto().getDiscriminantValue() != 0 || !unionFieldIsNull) {
++fieldCount;
} else {
......
......@@ -74,6 +74,12 @@ public:
// Set maximum nesting depth when decoding JSON to prevent highly nested input from overflowing
// the call stack. The default is 64.
void setHasMode(HasMode mode);
// Normally, primitive field values are always included even if they are equal to the default
// value (HasMode::NON_NULL -- only null pointers are omitted). You can use
// setHasMode(HasMode::NON_DEFAULT) to specify that default-valued primitive fields should be
// omitted as well.
template <typename T>
kj::String encode(T&& value) const;
// Encode any Cap'n Proto value to JSON, including primitives and
......
......@@ -2475,16 +2475,16 @@ private:
const char* linkage = scope.size() == 0 ? "extern " : "static ";
switch (type.which()) {
case schema::Value::BOOL:
case schema::Value::INT8:
case schema::Value::INT16:
case schema::Value::INT32:
case schema::Value::INT64:
case schema::Value::UINT8:
case schema::Value::UINT16:
case schema::Value::UINT32:
case schema::Value::UINT64:
case schema::Value::ENUM:
case schema::Type::BOOL:
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::ENUM:
return ConstText {
false,
kj::strTree("static constexpr ", typeName_, ' ', upperCase, " = ",
......@@ -2497,9 +2497,9 @@ private:
"#endif\n")
};
case schema::Value::VOID:
case schema::Value::FLOAT32:
case schema::Value::FLOAT64: {
case schema::Type::VOID:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64: {
// TODO(msvc): MSVC doesn't like float- or class-typed constexprs. As soon as this is fixed,
// treat VOID, FLOAT32, and FLOAT64 the same as the other primitives.
kj::String value = literalValue(schema.getType(), constProto.getValue()).flatten();
......@@ -2513,7 +2513,7 @@ private:
};
}
case schema::Value::TEXT: {
case schema::Type::TEXT: {
kj::String constType = kj::strTree(
"::capnp::_::ConstText<", schema.as<Text>().size(), ">").flatten();
return ConstText {
......@@ -2524,7 +2524,7 @@ private:
};
}
case schema::Value::DATA: {
case schema::Type::DATA: {
kj::String constType = kj::strTree(
"::capnp::_::ConstData<", schema.as<Data>().size(), ">").flatten();
return ConstText {
......@@ -2535,7 +2535,7 @@ private:
};
}
case schema::Value::STRUCT: {
case schema::Type::STRUCT: {
kj::String constType = kj::strTree(
"::capnp::_::ConstStruct<", typeName_, ">").flatten();
return ConstText {
......@@ -2546,7 +2546,7 @@ private:
};
}
case schema::Value::LIST: {
case schema::Type::LIST: {
kj::String constType = kj::strTree(
"::capnp::_::ConstList<", typeName(type.asList().getElementType(), nullptr), ">")
.flatten();
......@@ -2558,8 +2558,8 @@ private:
};
}
case schema::Value::ANY_POINTER:
case schema::Value::INTERFACE:
case schema::Type::ANY_POINTER:
case schema::Type::INTERFACE:
return ConstText { false, kj::strTree(), kj::strTree() };
}
......
......@@ -422,15 +422,20 @@ TEST(DynamicApi, Has) {
// Primitive fields are always present even if set to default.
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
root.set("int32Field", 123);
EXPECT_TRUE(root.has("int32Field"));
EXPECT_TRUE(root.has("int32Field", HasMode::NON_DEFAULT));
root.set("int32Field", -12345678);
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
// Pointers are absent until initialized.
EXPECT_FALSE(root.has("structField"));
EXPECT_FALSE(root.has("structField", HasMode::NON_DEFAULT));
root.init("structField");
EXPECT_TRUE(root.has("structField"));
EXPECT_TRUE(root.has("structField", HasMode::NON_DEFAULT));
}
TEST(DynamicApi, HasWhenEmpty) {
......@@ -443,6 +448,11 @@ TEST(DynamicApi, HasWhenEmpty) {
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("structField"));
EXPECT_FALSE(root.has("int32List"));
EXPECT_FALSE(root.has("voidField", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("structField", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("int32List", HasMode::NON_DEFAULT));
}
TEST(DynamicApi, SetEnumFromNative) {
......
......@@ -414,7 +414,7 @@ DynamicValue::Pipeline DynamicStruct::Pipeline::get(StructSchema::Field field) {
KJ_UNREACHABLE;
}
bool DynamicStruct::Reader::has(StructSchema::Field field) const {
bool DynamicStruct::Reader::has(StructSchema::Field field, HasMode mode) const {
KJ_REQUIRE(field.getContainingStruct() == schema, "`field` is not a field of this struct.");
auto proto = field.getProto();
......@@ -441,20 +441,35 @@ bool DynamicStruct::Reader::has(StructSchema::Field field) const {
switch (type.which()) {
case schema::Type::VOID:
// Void is always equal to the default.
return mode == HasMode::NON_NULL;
case schema::Type::BOOL:
return mode == HasMode::NON_NULL ||
reader.getDataField<bool>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint8_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT16:
case schema::Type::UINT16:
case schema::Type::ENUM:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint16_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT32:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint32_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT64:
case schema::Type::UINT64:
case schema::Type::FLOAT64:
case schema::Type::ENUM:
// Primitive types are always present.
return true;
return mode == HasMode::NON_NULL ||
reader.getDataField<uint64_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::TEXT:
case schema::Type::DATA:
......@@ -985,11 +1000,11 @@ DynamicValue::Builder DynamicStruct::Builder::get(kj::StringPtr name) {
DynamicValue::Pipeline DynamicStruct::Pipeline::get(kj::StringPtr name) {
return get(schema.getFieldByName(name));
}
bool DynamicStruct::Reader::has(kj::StringPtr name) const {
return has(schema.getFieldByName(name));
bool DynamicStruct::Reader::has(kj::StringPtr name, HasMode mode) const {
return has(schema.getFieldByName(name), mode);
}
bool DynamicStruct::Builder::has(kj::StringPtr name) {
return has(schema.getFieldByName(name));
bool DynamicStruct::Builder::has(kj::StringPtr name, HasMode mode) {
return has(schema.getFieldByName(name), mode);
}
void DynamicStruct::Builder::set(kj::StringPtr name, const DynamicValue::Reader& value) {
set(schema.getFieldByName(name), value);
......
......@@ -169,6 +169,21 @@ private:
// -------------------------------------------------------------------
enum class HasMode: uint8_t {
// Specifies the meaning of "has(field)".
NON_NULL,
// "has(field)" only returns false if the field is a pointer and the pointer is null. This is the
// default behavior.
NON_DEFAULT
// "has(field)" returns false if the field is set to its default value. This differs from
// NON_NULL only in the handling of primitive values.
//
// "Equal to default value" is technically defined as the field value being encoded as all-zero
// on the wire (since primitive values are XORed by their defined default value when encoded).
};
class DynamicStruct::Reader {
public:
typedef DynamicStruct Reads;
......@@ -191,12 +206,10 @@ public:
DynamicValue::Reader get(StructSchema::Field field) const;
// Read the given field value.
bool has(StructSchema::Field field) const;
// Tests whether the given field is set to its default value. For pointer values, this does
// not actually traverse the value comparing it with the default, but simply returns true if the
// pointer is non-null. For members of unions, has() returns false if the union member is not
// active, but does not necessarily return true if the member is active (depends on the field's
// value).
bool has(StructSchema::Field field, HasMode mode = HasMode::NON_NULL) const;
// Tests whether the given field is "present". If the field is a union member and is not the
// active member, this always returns false. Otherwise, the field's value is interpreted
// according to `mode`.
kj::Maybe<StructSchema::Field> which() const;
// If the struct contains an (unnamed) union, and the currently-active field within that union
......@@ -206,7 +219,7 @@ public:
// newer version of the protocol and is using a field of the union that you don't know about yet.
DynamicValue::Reader get(kj::StringPtr name) const;
bool has(kj::StringPtr name) const;
bool has(kj::StringPtr name, HasMode mode = HasMode::NON_NULL) const;
// Shortcuts to access fields by name. These throw exceptions if no such field exists.
private:
......@@ -261,12 +274,11 @@ public:
DynamicValue::Builder get(StructSchema::Field field);
// Read the given field value.
inline bool has(StructSchema::Field field) { return asReader().has(field); }
// Tests whether the given field is set to its default value. For pointer values, this does
// not actually traverse the value comparing it with the default, but simply returns true if the
// pointer is non-null. For members of unions, has() returns whether the field is currently
// active and the union as a whole is non-default -- so, the only time has() will return false
// for an active union field is if it is the default active field and it has its default value.
inline bool has(StructSchema::Field field, HasMode mode = HasMode::NON_NULL)
{ return asReader().has(field, mode); }
// Tests whether the given field is "present". If the field is a union member and is not the
// active member, this always returns false. Otherwise, the field's value is interpreted
// according to `mode`.
kj::Maybe<StructSchema::Field> which();
// If the struct contains an (unnamed) union, and the currently-active field within that union
......@@ -292,7 +304,7 @@ public:
// field null.
DynamicValue::Builder get(kj::StringPtr name);
bool has(kj::StringPtr name);
bool has(kj::StringPtr name, HasMode mode = HasMode::NON_NULL);
void set(kj::StringPtr name, const DynamicValue::Reader& value);
void set(kj::StringPtr name, std::initializer_list<DynamicValue::Reader> value);
DynamicValue::Builder init(kj::StringPtr name);
......
......@@ -1168,7 +1168,6 @@ TEST(Rpc, RealmGatewayImportExport) {
kj::EventLoop loop;
kj::WaitScope waitScope(loop);
TestNetwork network;
TestRestorer restorer;
TestNetworkAdapter& clientNetwork = network.add("client");
TestNetworkAdapter& serverNetwork = network.add("server");
RpcSystem<test::TestSturdyRefHostId> rpcClient =
......@@ -1222,7 +1221,6 @@ TEST(Rpc, RealmGatewayImportExport) {
kj::EventLoop loop;
kj::WaitScope waitScope(loop);
TestNetwork network;
TestRestorer restorer;
TestNetworkAdapter& clientNetwork = network.add("client");
TestNetworkAdapter& serverNetwork = network.add("server");
RpcSystem<test::TestSturdyRefHostId> rpcClient =
......
......@@ -2619,6 +2619,12 @@ KJ_TEST("HttpClient connection management") {
.wait(io.waitScope);
KJ_EXPECT(count == 0);
// Connections where we didn't even wait for the response headers are not reused.
doRequest().wait(io.waitScope);
KJ_EXPECT(count == 1);
client->request(HttpMethod::GET, kj::str("/foo"), HttpHeaders(headerTable));
KJ_EXPECT(count == 0);
// Connections where we failed to write the full request body are not reused.
doRequest().wait(io.waitScope);
KJ_EXPECT(count == 1);
......
......@@ -993,7 +993,7 @@ public:
}
bool canReuse() {
return !broken;
return !broken && pendingMessageCount == 0;
}
// ---------------------------------------------------------------------------
......@@ -1005,6 +1005,7 @@ public:
KJ_REQUIRE_NONNULL(onMessageDone)->fulfill();
onMessageDone = nullptr;
--pendingMessageCount;
}
void abortRead() {
......@@ -1066,6 +1067,7 @@ public:
}
kj::Promise<kj::ArrayPtr<char>> readMessageHeaders() {
++pendingMessageCount;
auto paf = kj::newPromiseAndFulfiller<void>();
auto promise = messageReadQueue
......@@ -1194,6 +1196,9 @@ private:
bool broken = false;
// Becomes true if the caller failed to read the whole entity-body before closing the stream.
uint pendingMessageCount = 0;
// Number of reads we have queued up.
kj::Promise<void> messageReadQueue = kj::READY_NOW;
kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> onMessageDone;
......@@ -2413,7 +2418,8 @@ public:
settings(kj::mv(settings)) {}
bool canReuse() {
// Returns true if we can reuse this HttpClient for another request.
// Returns true if we can immediately reuse this HttpClient for another message (so all
// previous messages have been fully read).
return !upgraded && !closed && httpInput.canReuse() && httpOutput.canReuse();
}
......
......@@ -99,7 +99,7 @@ void ensureOpenSslInitialized() {
// AsyncIoStream is simply wrapping a file descriptor (or other readiness-based stream?) and use
// that directly if so.
class TlsConnection: public kj::AsyncIoStream {
class TlsConnection final: public kj::AsyncIoStream {
public:
TlsConnection(kj::Own<kj::AsyncIoStream> stream, SSL_CTX* ctx)
: TlsConnection(*stream, ctx) {
......@@ -372,7 +372,7 @@ private:
// =======================================================================================
// Implementations of ConnectionReceiver, NetworkAddress, and Network as wrappers adding TLS.
class TlsConnectionReceiver: public kj::ConnectionReceiver {
class TlsConnectionReceiver final: public kj::ConnectionReceiver {
public:
TlsConnectionReceiver(TlsContext& tls, kj::Own<kj::ConnectionReceiver> inner)
: tls(tls), inner(kj::mv(inner)) {}
......@@ -400,7 +400,7 @@ private:
kj::Own<kj::ConnectionReceiver> inner;
};
class TlsNetworkAddress: public kj::NetworkAddress {
class TlsNetworkAddress final: public kj::NetworkAddress {
public:
TlsNetworkAddress(TlsContext& tls, kj::String hostname, kj::Own<kj::NetworkAddress>&& inner)
: tls(tls), hostname(kj::mv(hostname)), inner(kj::mv(inner)) {}
......@@ -435,7 +435,7 @@ private:
kj::Own<kj::NetworkAddress> inner;
};
class TlsNetwork: public kj::Network {
class TlsNetwork final: public kj::Network {
public:
TlsNetwork(TlsContext& tls, kj::Network& inner): tls(tls), inner(inner) {}
TlsNetwork(TlsContext& tls, kj::Own<kj::Network> inner)
......
......@@ -132,6 +132,14 @@ KJ_TEST("parse / stringify URL") {
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo?bar%20baz=qux+quux",
"https://capnproto.org/foo?bar+baz=qux+quux");
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "bar baz");
KJ_EXPECT(url.query[0].value == "qux quux");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar#garply");
KJ_EXPECT(url.scheme == "https");
......@@ -232,7 +240,7 @@ KJ_TEST("URL percent encoding") {
parseAndCheck(
"https://b b: bcd@capnproto.org/f o?b r=b z#q x",
"https://b%20b:%20bcd@capnproto.org/f%20o?b%20r=b%20z#q%20x");
"https://b%20b:%20bcd@capnproto.org/f%20o?b+r=b+z#q%20x");
}
KJ_TEST("URL relative paths") {
......@@ -340,6 +348,9 @@ KJ_TEST("parse relative URL") {
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault",
"https://capnproto.org/foo/bar?grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault+garply=waldo",
"https://capnproto.org/foo/bar?grault+garply=waldo");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"grault",
"https://capnproto.org/foo/grault");
......
......@@ -88,6 +88,12 @@ String percentDecode(ArrayPtr<const char> text, bool& hadErrors) {
return kj::mv(result);
}
String percentDecodeQuery(ArrayPtr<const char> text, bool& hadErrors) {
auto result = decodeWwwForm(text);
if (result.hadErrors) hadErrors = true;
return kj::mv(result);
}
} // namespace
Url::~Url() noexcept(false) {}
......@@ -195,9 +201,10 @@ Maybe<Url> Url::tryParse(StringPtr text, Context context) {
if (part.size() > 0) {
KJ_IF_MAYBE(key, trySplit(part, '=')) {
result.query.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) });
result.query.add(QueryParam { percentDecodeQuery(*key, err),
percentDecodeQuery(part, err) });
} else {
result.query.add(QueryParam { percentDecode(part, err), nullptr });
result.query.add(QueryParam { percentDecodeQuery(part, err), nullptr });
}
}
} while (text.startsWith("&"));
......@@ -331,9 +338,10 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
if (part.size() > 0) {
KJ_IF_MAYBE(key, trySplit(part, '=')) {
result.query.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) });
result.query.add(QueryParam { percentDecodeQuery(*key, err),
percentDecodeQuery(part, err) });
} else {
result.query.add(QueryParam { percentDecode(part, err), nullptr });
result.query.add(QueryParam { percentDecodeQuery(part, err), nullptr });
}
}
} while (text.startsWith("&"));
......@@ -407,10 +415,10 @@ String Url::toString(Context context) const {
for (auto& param: query) {
chars.add(first ? '?' : '&');
first = false;
chars.addAll(encodeUriComponent(param.name));
chars.addAll(encodeWwwForm(param.name));
if (param.value.size() > 0) {
chars.add('=');
chars.addAll(encodeUriComponent(param.value));
chars.addAll(encodeWwwForm(param.value));
}
}
......
......@@ -279,6 +279,9 @@ KJ_TEST("URI encoding/decoding") {
KJ_EXPECT(encodeUriComponent("\xab\xba") == "%AB%BA");
KJ_EXPECT(encodeUriComponent(StringPtr("foo\0bar", 7)) == "foo%00bar");
// Encode characters reserved by application/x-www-form-urlencoded, but not by RFC 2396.
KJ_EXPECT(encodeUriComponent("'foo'! (~)") == "'foo'!%20(~)");
expectRes(decodeUriComponent("foo%20bar"), "foo bar");
expectRes(decodeUriComponent("%ab%BA"), "\xab\xba");
......@@ -287,8 +290,43 @@ KJ_TEST("URI encoding/decoding") {
expectRes(decodeUriComponent("foo%xxx"), "fooxxx", true);
expectRes(decodeUriComponent("foo%"), "foo", true);
{
byte bytes[] = {12, 34, 56};
KJ_EXPECT(decodeBinaryUriComponent(encodeUriComponent(bytes)).asPtr() == bytes);
// decodeBinaryUriComponent() takes a DecodeUriOptions struct as its second parameter, but it
// once took a single `bool nulTerminate`. Verify that the old behavior still compiles and
// works.
auto bytesWithNul = decodeBinaryUriComponent(encodeUriComponent(bytes), true);
KJ_ASSERT(bytesWithNul.size() == 4);
KJ_EXPECT(bytesWithNul[3] == '\0');
KJ_EXPECT(bytesWithNul.slice(0, 3) == bytes);
}
}
KJ_TEST("application/x-www-form-urlencoded encoding/decoding") {
KJ_EXPECT(encodeWwwForm("foo") == "foo");
KJ_EXPECT(encodeWwwForm("foo bar") == "foo+bar");
KJ_EXPECT(encodeWwwForm("\xab\xba") == "%AB%BA");
KJ_EXPECT(encodeWwwForm(StringPtr("foo\0bar", 7)) == "foo%00bar");
// Encode characters reserved by application/x-www-form-urlencoded, but not by RFC 2396.
KJ_EXPECT(encodeWwwForm("'foo'! (~)") == "%27foo%27%21+%28%7E%29");
expectRes(decodeWwwForm("foo%20bar"), "foo bar");
expectRes(decodeWwwForm("foo+bar"), "foo bar");
expectRes(decodeWwwForm("%ab%BA"), "\xab\xba");
expectRes(decodeWwwForm("foo%1xxx"), "foo\1xxx", true);
expectRes(decodeWwwForm("foo%1"), "foo\1", true);
expectRes(decodeWwwForm("foo%xxx"), "fooxxx", true);
expectRes(decodeWwwForm("foo%"), "foo", true);
{
byte bytes[] = {12, 34, 56};
DecodeUriOptions options { /*.nulTerminate=*/false, /*.plusToSpace=*/true };
KJ_EXPECT(decodeBinaryUriComponent(encodeWwwForm(bytes), options) == bytes);
}
}
KJ_TEST("C escape encoding/decoding") {
......
......@@ -404,9 +404,27 @@ String encodeUriComponent(ArrayPtr<const byte> bytes) {
return String(result.releaseAsArray());
}
String encodeWwwForm(ArrayPtr<const byte> bytes) {
Vector<char> result(bytes.size() + 1);
for (byte b: bytes) {
if (('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') || ('0' <= b && b <= '9') ||
b == '-' || b == '_' || b == '.' || b == '*') {
result.add(b);
} else if (b == ' ') {
result.add('+');
} else {
result.add('%');
result.add(HEX_DIGITS_URI[b/16]);
result.add(HEX_DIGITS_URI[b%16]);
}
}
result.add('\0');
return String(result.releaseAsArray());
}
EncodingResult<Array<byte>> decodeBinaryUriComponent(
ArrayPtr<const char> text, bool nulTerminate) {
Vector<byte> result(text.size() + nulTerminate);
ArrayPtr<const char> text, DecodeUriOptions options) {
Vector<byte> result(text.size() + options.nulTerminate);
bool hadErrors = false;
const char* ptr = text.begin();
......@@ -432,12 +450,15 @@ EncodingResult<Array<byte>> decodeBinaryUriComponent(
} else {
hadErrors = true;
}
} else if (options.plusToSpace && *ptr == '+') {
++ptr;
result.add(' ');
} else {
result.add(*ptr++);
}
}
if (nulTerminate) result.add(0);
if (options.nulTerminate) result.add(0);
return { result.releaseAsArray(), hadErrors };
}
......
......@@ -124,10 +124,40 @@ EncodingResult<Array<byte>> decodeHex(ArrayPtr<const char> text);
String encodeUriComponent(ArrayPtr<const byte> bytes);
String encodeUriComponent(ArrayPtr<const char> bytes);
EncodingResult<Array<byte>> decodeBinaryUriComponent(
ArrayPtr<const char> text, bool nulTerminate = false);
EncodingResult<String> decodeUriComponent(ArrayPtr<const char> text);
// Encode/decode URI components using % escapes. See Javascript's encodeURIComponent().
// Encode/decode URI components using % escapes for characters listed as "reserved" in RFC 2396.
// This is the same behavior as JavaScript's `encodeURIComponent()`.
//
// See https://tools.ietf.org/html/rfc2396#section-2.3
String encodeWwwForm(ArrayPtr<const byte> bytes);
String encodeWwwForm(ArrayPtr<const char> bytes);
EncodingResult<String> decodeWwwForm(ArrayPtr<const char> text);
// Encode/decode URI components using % escapes and '+' (for spaces) according to the
// application/x-www-form-urlencoded format defined by the WHATWG URL specification.
//
// See https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
struct DecodeUriOptions {
// Parameter to `decodeBinaryUriComponent()`.
// This struct is intentionally convertible from bool, in order to maintain backwards
// compatibility with code written when `decodeBinaryUriComponent()` took a boolean second
// parameter.
DecodeUriOptions(bool nulTerminate = false, bool plusToSpace = false)
: nulTerminate(nulTerminate), plusToSpace(plusToSpace) {}
bool nulTerminate;
// Append a terminal NUL byte.
bool plusToSpace;
// Convert '+' to ' ' characters before percent decoding. Used to decode
// application/x-www-form-urlencoded text, such as query strings.
};
EncodingResult<Array<byte>> decodeBinaryUriComponent(
ArrayPtr<const char> text, DecodeUriOptions options = DecodeUriOptions());
// Decode URI components using % escapes. This is a lower-level interface used to implement both
// `decodeUriComponent()` and `decodeWwwForm()`
String encodeCEscape(ArrayPtr<const byte> bytes);
String encodeCEscape(ArrayPtr<const char> bytes);
......@@ -181,7 +211,16 @@ inline String encodeUriComponent(ArrayPtr<const char> text) {
return encodeUriComponent(text.asBytes());
}
inline EncodingResult<String> decodeUriComponent(ArrayPtr<const char> text) {
auto result = decodeBinaryUriComponent(text, true);
auto result = decodeBinaryUriComponent(text, DecodeUriOptions { /*.nulTerminate=*/true });
return { String(result.releaseAsChars()), result.hadErrors };
}
inline String encodeWwwForm(ArrayPtr<const char> text) {
return encodeWwwForm(text.asBytes());
}
inline EncodingResult<String> decodeWwwForm(ArrayPtr<const char> text) {
auto result = decodeBinaryUriComponent(text, DecodeUriOptions { /*.nulTerminate=*/true,
/*.plusToSpace=*/true });
return { String(result.releaseAsChars()), result.hadErrors };
}
......@@ -239,6 +278,14 @@ inline EncodingResult<String> decodeUriComponent(const char (&text)[s]) {
return decodeUriComponent(arrayPtr(text, s-1));
}
template <size_t s>
inline String encodeWwwForm(const char (&text)[s]) {
return encodeWwwForm(arrayPtr(text, s - 1));
}
template <size_t s>
inline EncodingResult<String> decodeWwwForm(const char (&text)[s]) {
return decodeWwwForm(arrayPtr(text, s-1));
}
template <size_t s>
inline String encodeCEscape(const char (&text)[s]) {
return encodeCEscape(arrayPtr(text, s - 1));
}
......
......@@ -631,7 +631,7 @@ void MainBuilder::MainImpl::usageError(StringPtr programName, StringPtr message)
class MainBuilder::Impl::OptionDisplayOrder {
public:
bool operator()(const Option* a, const Option* b) {
bool operator()(const Option* a, const Option* b) const {
if (a == b) return false;
char aShort = '\0';
......
......@@ -32,7 +32,7 @@ kj::Exception Timer::makeTimeoutException() {
struct TimerImpl::Impl {
struct TimerBefore {
bool operator()(TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs);
bool operator()(TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs) const;
};
using Timers = std::multiset<TimerPromiseAdapter*, TimerBefore>;
Timers timers;
......@@ -66,7 +66,7 @@ private:
};
inline bool TimerImpl::Impl::TimerBefore::operator()(
TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs) {
TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs) const {
return lhs->time < rhs->time;
}
......
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