blob: 24056b86ffd6081dc9fca85e00e9cd4755d92781 [file] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// Binary conformance tests checking that invalid UTF-8 in a string field is
// rejected, and that a bytes field accepts it. This replaces the legacy
// BinaryAndJsonConformanceSuiteImpl<M>::TestInvalidUtf8String() (which only
// ran for the proto3-style message types) and the suite-level
// BinaryAndJsonConformanceSuite::RunUtf8ValidationTests(); the test names and
// the requests sent to the testee are identical to the legacy ones.
//
// TODO: Once conformance tests can express failures that are
// not expected to be fixed, add proto2 coverage here.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
#include "conformance/binary_test_util.h"
#include "conformance/binary_wireformat.h"
#include "conformance/matchers.h"
#include "conformance/message_type_fixtures.h"
#include "conformance/testee.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::testing::ValuesIn;
// Four bytes that aren't valid UTF-8.
constexpr absl::string_view kInvalidUtf8 = "\xA0\xB0\xC0\xD0";
// Instantiated over the proto3-style test message types only: proto3 rejects
// invalid UTF-8 in string fields, proto2 doesn't validate it.
class Utf8StringTest : public MessageTypeConformanceTest {
private:
TestPriority DefaultPriority() const override { return kP1; }
};
// Field 14 is optional_string.
TEST_P(Utf8StringTest, Singular) {
EXPECT_THAT(Testee("RejectInvalidUtf8.String.Singular")
.ParseBinary(message(), LengthPrefixedField(14, kInvalidUtf8))
.ParseOnly(),
Yields(IsParseError()));
}
// Field 44 is repeated_string.
TEST_P(Utf8StringTest, Repeated) {
EXPECT_THAT(Testee("RejectInvalidUtf8.String.Repeated")
.ParseBinary(message(), LengthPrefixedField(44, kInvalidUtf8))
.ParseOnly(),
Yields(IsParseError()));
}
// Field 113 is oneof_string.
TEST_P(Utf8StringTest, Oneof) {
EXPECT_THAT(
Testee("RejectInvalidUtf8.String.Oneof")
.ParseBinary(message(), LengthPrefixedField(113, kInvalidUtf8))
.ParseOnly(),
Yields(IsParseError()));
}
// Field 69 is map_string_string; its entries have key = 1 and value = 2.
TEST_P(Utf8StringTest, MapKey) {
EXPECT_THAT(
Testee("RejectInvalidUtf8.String.MapKey")
.ParseBinary(
message(),
LengthPrefixedField(69, Wire(LengthPrefixedField(1, kInvalidUtf8),
LengthPrefixedField(2, "foo"))))
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(Utf8StringTest, MapValue) {
EXPECT_THAT(
Testee("RejectInvalidUtf8.String.MapValue")
.ParseBinary(message(),
LengthPrefixedField(
69, Wire(LengthPrefixedField(1, "foo"),
LengthPrefixedField(2, kInvalidUtf8))))
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(All, Utf8StringTest,
ValuesIn(Proto3TestMessageTypes()),
MessageTypeParamName);
// The extension counterparts only exist for TestAllTypesEdition2023, where
// field 133 is the extension_string extension and field 134 is the
// extension_bytes extension.
using Utf8ExtensionTest = Edition2023ConformanceTest;
TEST_F(Utf8ExtensionTest, String) {
EXPECT_THAT(
Testee(kP1, "RejectInvalidUtf8.String.Extension")
.ParseBinary(message(), LengthPrefixedField(133, kInvalidUtf8))
.ParseOnly(),
Yields(IsParseError()));
}
// Bytes fields aren't validated, so the same bytes must be accepted.
TEST_F(Utf8ExtensionTest, Bytes) {
constexpr absl::string_view kExpected = R"pb(
[protobuf_test_messages.editions.extension_bytes]: "\xA0\xB0\xC0\xD0"
)pb";
EXPECT_THAT(
Testee("AcceptInvalidUtf8.Bytes.Extension")
.ParseBinary(message(), LengthPrefixedField(134, kInvalidUtf8))
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(kExpected))));
}
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google