diff --git a/tools/inspector_protocol/encoding/encoding.cc b/tools/inspector_protocol/encoding/encoding.cc index c1078769c7feaf..353316a555373d 100644 --- a/tools/inspector_protocol/encoding/encoding.cc +++ b/tools/inspector_protocol/encoding/encoding.cc @@ -843,14 +843,15 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) { return; case MajorType::NEGATIVE: { // INT32. // INT32 is a signed int32 (int32 makes sense for the - // inspector_protocol, it's not a CBOR limitation); in CBOR, - // the negative values for INT32 are represented as NEGATIVE, - // that is, -1 INT32 is represented as 1 << 5 | 0 (major type 1, - // additional info value 0). So here, we compute the INT32 value - // and then check it against the INT32 min. - int64_t actual_value = - -static_cast(token_start_internal_value_) - 1; - if (!success || actual_value < std::numeric_limits::min()) { + // inspector_protocol, it's not a CBOR limitation); in CBOR, the + // negative values for INT32 are represented as NEGATIVE, that is, -1 + // INT32 is represented as 1 << 5 | 0 (major type 1, additional info + // value 0). The minimal allowed INT32 value in our protocol is + // std::numeric_limits::min(). We check for it by directly + // checking the payload against the maximal allowed signed (!) int32 + // value. + if (!success || token_start_internal_value_ > + std::numeric_limits::max()) { SetError(Error::CBOR_INVALID_INT32); return; } @@ -1857,7 +1858,7 @@ class JsonParser { // If the |Char| we're dealing with is really a byte, then // we have utf8 here, and we need to check for multibyte characters // and transcode them to utf16 (either one or two utf16 chars). - if (sizeof(Char) == sizeof(uint8_t) && c >= 0x7f) { + if (sizeof(Char) == sizeof(uint8_t) && c > 0x7f) { // Inspect the leading byte to figure out how long the utf8 // byte sequence is; while doing this initialize |codepoint| // with the first few bits. @@ -1896,7 +1897,7 @@ class JsonParser { // Disallow overlong encodings for ascii characters, as these // would include " and other characters significant to JSON // string termination / control. - if (codepoint < 0x7f) + if (codepoint <= 0x7f) return false; // Invalid in UTF8, and can't be represented in UTF16 anyway. if (codepoint > 0x10ffff) diff --git a/tools/inspector_protocol/encoding/encoding_test.cc b/tools/inspector_protocol/encoding/encoding_test.cc index b8d75e09baaf31..338d1ece10b87f 100644 --- a/tools/inspector_protocol/encoding/encoding_test.cc +++ b/tools/inspector_protocol/encoding/encoding_test.cc @@ -234,6 +234,24 @@ TEST(EncodeDecodeInt32Test, RoundtripsInt32Max) { EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); } +TEST(EncodeDecodeInt32Test, RoundtripsInt32Min) { + // std::numeric_limits is encoded as a uint32 after the initial byte. + std::vector encoded; + EncodeInt32(std::numeric_limits::min(), &encoded); + // 1 for initial byte, 4 for the uint32. + // first three bits: major type = 1; + // remaining five bits: additional info = 26, indicating payload is uint32. + EXPECT_THAT(encoded, ElementsAreArray(std::array{ + {1 << 5 | 26, 0x7f, 0xff, 0xff, 0xff}})); + + // Reverse direction: decode with CBORTokenizer. + CBORTokenizer tokenizer(SpanFrom(encoded)); + EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag()); + EXPECT_EQ(std::numeric_limits::min(), tokenizer.GetInt32()); + tokenizer.Next(); + EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag()); +} + TEST(EncodeDecodeInt32Test, CantRoundtripUint32) { // 0xdeadbeef is a value which does not fit below // std::numerical_limits::max(), so we can't encode @@ -261,15 +279,21 @@ TEST(EncodeDecodeInt32Test, DecodeErrorCases) { std::vector data; std::string msg; }; - std::vector tests{ - {TestCase{ - {24}, - "additional info = 24 would require 1 byte of payload (but it's 0)"}, - TestCase{{27, 0xaa, 0xbb, 0xcc}, - "additional info = 27 would require 8 bytes of payload (but " - "it's 3)"}, - TestCase{{29}, "additional info = 29 isn't recognized"}}}; - + std::vector tests{{ + TestCase{ + {24}, + "additional info = 24 would require 1 byte of payload (but it's 0)"}, + TestCase{{27, 0xaa, 0xbb, 0xcc}, + "additional info = 27 would require 8 bytes of payload (but " + "it's 3)"}, + TestCase{{29}, "additional info = 29 isn't recognized"}, + TestCase{{1 << 5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + "Max UINT64 payload is outside the allowed range"}, + TestCase{{1 << 5 | 26, 0xff, 0xff, 0xff, 0xff}, + "Max UINT32 payload is outside the allowed range"}, + TestCase{{1 << 5 | 26, 0x80, 0x00, 0x00, 0x00}, + "UINT32 payload w/ high bit set is outside the allowed range"}, + }}; for (const TestCase& test : tests) { SCOPED_TRACE(test.msg); CBORTokenizer tokenizer(SpanFrom(test.data)); @@ -1517,6 +1541,22 @@ TEST_F(JsonParserTest, SimpleDictionary) { log_.str()); } +TEST_F(JsonParserTest, UsAsciiDelCornerCase) { + // DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control + // character according to Unicode, it's not considered a control + // character in https://tools.ietf.org/html/rfc7159#section-7, so + // it can be placed directly into the JSON string, without JSON escaping. + std::string json = "{\"foo\": \"a\x7f\"}"; + ParseJSON(GetTestPlatform(), SpanFrom(json), &log_); + EXPECT_TRUE(log_.status().ok()); + EXPECT_EQ( + "map begin\n" + "string16: foo\n" + "string16: a\x7f\n" + "map end\n", + log_.str()); +} + TEST_F(JsonParserTest, Whitespace) { std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t"; ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);