Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Firestore/core/src/bundle/bundle_serializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@ ResourcePath BundleSerializer::DecodeName(JsonReader& reader,
}
auto path =
ResourcePath::FromString(document_name.get_ref<const std::string&>());
if (!rpc_serializer_.IsLocalResourceName(path)) {
if (!rpc_serializer_.IsLocalResourceName(path) || path.size() < 5 ||
path[4] != "documents") {
Comment thread
cherylEnkidu marked this conversation as resolved.
reader.Fail("Resource name is not valid for current instance: " +
path.CanonicalString());
return {};
Expand Down Expand Up @@ -647,6 +648,11 @@ BundledDocumentMetadata BundleSerializer::DecodeDocumentMetadata(
if (!reader.ok()) {
return {};
}
if (!DocumentKey::IsDocumentKey(path)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-  if (!DocumentKey::IsDocumentKey(path)) {
+  if (path.empty() || !DocumentKey::IsDocumentKey(path)) {

reader.Fail("Resource name is not a valid document key: " +
path.CanonicalString());
return {};
}
DocumentKey key = DocumentKey(path);

SnapshotVersion read_time = DecodeSnapshotVersion(
Expand Down Expand Up @@ -678,6 +684,11 @@ BundleDocument BundleSerializer::DecodeDocument(JsonReader& reader,
if (!reader.ok()) {
return {};
}
if (!DocumentKey::IsDocumentKey(path)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-  if (!DocumentKey::IsDocumentKey(path)) {
+  if (path.empty() || !DocumentKey::IsDocumentKey(path)) {

reader.Fail("Resource name is not a valid document key: " +
path.CanonicalString());
return {};
}
DocumentKey key = DocumentKey(path);

SnapshotVersion update_time = DecodeSnapshotVersion(
Expand Down
3 changes: 2 additions & 1 deletion Firestore/core/src/remote/serializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1521,7 +1521,8 @@ bool Serializer::IsLocalResourceName(const ResourcePath& path) const {

bool Serializer::IsLocalDocumentKey(absl::string_view path) const {
auto resource = ResourcePath::FromStringView(path);
return IsLocalResourceName(resource) &&
return IsLocalResourceName(resource) && resource.size() >= 5 &&
resource[4] == "documents" &&
DocumentKey::IsDocumentKey(resource.PopFirst(5));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation calls DocumentKey::IsDocumentKey(resource.PopFirst(5)). PopFirst allocates a new ResourcePath vector and copies all segment strings in memory. This allocation is unnecessary because we only need to verify if the number of segments remaining after the database prefix is even.

Suggested Diff

bool Serializer::IsLocalDocumentKey(absl::string_view path) const {
  auto resource = ResourcePath::FromStringView(path);
  return IsLocalResourceName(resource) && resource.size() >= 5 &&
         resource[4] == "documents" &&
-        DocumentKey::IsDocumentKey(resource.PopFirst(5));
+        (resource.size() - 5) % 2 == 0;
}

}

Expand Down
33 changes: 33 additions & 0 deletions Firestore/core/test/unit/bundle/bundle_serializer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,39 @@ TEST_F(BundleSerializerTest, DecodeInvalidBundledDocumentMetadataFails) {
}
}

TEST_F(BundleSerializerTest, DecodeNonDocumentKeyResourceNameFails) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current DecodeNonDocumentKeyResourceNameFails test only validates collection paths. Could you expand the test suite to verify that:

Short paths (size < 5, e.g., /projects/p/databases/default) fail gracefully instead of crashing.
Root documents paths (which resolve to an empty key) fail gracefully.

for example:

TEST_F(BundleSerializerTest, DecodeNonDocumentKeyResourceNameFails) {
  // 1. Collection Path (Odd number of segments, fails key check)
  {
    ProtoDocument document = TestDocument(ProtoValue());
    document.set_name(FullPath("bundle"));
    std::string json_string;
    MessageToJsonString(document, &json_string);

    JsonReader reader;
    bundle_serializer.DecodeDocument(reader, Parse(json_string));
    EXPECT_NOT_OK(reader.status());
  }

  // 2. Root Path (Popped path is empty, size 0, must fail)
  {
    ProtoDocument document = TestDocument(ProtoValue());
    document.set_name(FullPath(""));
    std::string json_string;
    MessageToJsonString(document, &json_string);

    JsonReader reader;
    bundle_serializer.DecodeDocument(reader, Parse(json_string));
    EXPECT_NOT_OK(reader.status());
  }

  // 3. Short Path (Size < 5, must fail prefix validation instead of crashing PopFirst)
  {
    ProtoDocument document = TestDocument(ProtoValue());
    document.set_name("projects/p/databases/default");
    std::string json_string;
    MessageToJsonString(document, &json_string);

    JsonReader reader;
    bundle_serializer.DecodeDocument(reader, Parse(json_string));
    EXPECT_NOT_OK(reader.status());
  }
  
  // (Duplicate these three cases for DecodeDocumentMetadata below as well)
}

// A resource name that points at a collection (odd number of segments after
// the `documents` prefix) passes the local-resource-name check but is not a
// valid `DocumentKey`. It must be rejected gracefully instead of tripping the
// `HARD_ASSERT` inside the `DocumentKey` constructor.
{
ProtoDocument document = TestDocument(ProtoValue());
document.set_name(FullPath("bundle"));
std::string json_string;
MessageToJsonString(document, &json_string);

JsonReader reader;
bundle_serializer.DecodeDocument(reader, Parse(json_string));
EXPECT_NOT_OK(reader.status());
}

{
ProtoBundledDocumentMetadata metadata;
metadata.set_name(FullPath("bundle"));
metadata.set_exists(true);
google::protobuf::Timestamp t1;
t1.set_seconds(0);
t1.set_nanos(0);
*metadata.mutable_read_time() = t1;
std::string json_string;
MessageToJsonString(metadata, &json_string);

JsonReader reader;
bundle_serializer.DecodeDocumentMetadata(reader, Parse(json_string));
EXPECT_NOT_OK(reader.status());
}
}

TEST_F(BundleSerializerTest, DecodeTargetWithoutImplicitOrderByOnName) {
std::string json(
R"({"name":"myNamedQuery",
Expand Down
Loading