fix(client): validate suppressed tree objects
This commit is contained in:
@@ -1707,6 +1707,29 @@ fn object_ownership_revision(state: &Session, key: u64) -> Option<RequestId> {
|
|||||||
.max()
|
.max()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_object_consistency(state: &Session, object: &Object) -> Result<(), ClientError> {
|
||||||
|
let Some(reference) = &object.r#ref else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(existing) = state
|
||||||
|
.refs
|
||||||
|
.get(reference)
|
||||||
|
.and_then(|key| state.objects.get(key))
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if existing.object.validator.is_some()
|
||||||
|
&& existing.object.validator == object.validator
|
||||||
|
&& existing.object != *object
|
||||||
|
{
|
||||||
|
return Err(ClientError::new(
|
||||||
|
ClientErrorKind::InvalidResponse,
|
||||||
|
"object representation changed for the same ref and validator",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_object(
|
fn insert_object(
|
||||||
state: &mut Session,
|
state: &mut Session,
|
||||||
path: &str,
|
path: &str,
|
||||||
@@ -1714,6 +1737,7 @@ fn insert_object(
|
|||||||
maximum: usize,
|
maximum: usize,
|
||||||
revision: RequestId,
|
revision: RequestId,
|
||||||
) -> Result<u64, ClientError> {
|
) -> Result<u64, ClientError> {
|
||||||
|
validate_object_consistency(state, &object)?;
|
||||||
ensure_entry_slot(state, path, maximum)?;
|
ensure_entry_slot(state, path, maximum)?;
|
||||||
let existing_path_entry = state.entries.get(path).cloned();
|
let existing_path_entry = state.entries.get(path).cloned();
|
||||||
let existing_path_key = existing_path_entry
|
let existing_path_key = existing_path_entry
|
||||||
@@ -1731,16 +1755,6 @@ fn insert_object(
|
|||||||
});
|
});
|
||||||
let key = if let Some(reference) = &object.r#ref {
|
let key = if let Some(reference) = &object.r#ref {
|
||||||
if let Some(key) = state.refs.get(reference).copied() {
|
if let Some(key) = state.refs.get(reference).copied() {
|
||||||
if let Some(existing) = state.objects.get(&key)
|
|
||||||
&& existing.object.validator.is_some()
|
|
||||||
&& existing.object.validator == object.validator
|
|
||||||
&& existing.object != object
|
|
||||||
{
|
|
||||||
return Err(ClientError::new(
|
|
||||||
ClientErrorKind::InvalidResponse,
|
|
||||||
"object representation changed for the same ref and validator",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
key
|
key
|
||||||
} else {
|
} else {
|
||||||
make_object_slot(state, maximum, replaceable_key)?
|
make_object_slot(state, maximum, replaceable_key)?
|
||||||
@@ -2017,6 +2031,7 @@ fn apply_tree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (path, object, _) in &nodes {
|
for (path, object, _) in &nodes {
|
||||||
|
validate_object_consistency(state, object)?;
|
||||||
let newer_owner = state
|
let newer_owner = state
|
||||||
.entries
|
.entries
|
||||||
.get(path)
|
.get(path)
|
||||||
|
|||||||
@@ -327,6 +327,53 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
|||||||
client.object(&session, "/b/shared").unwrap().validator,
|
client.object(&session, "/b/shared").unwrap().validator,
|
||||||
Some(b"newest".to_vec())
|
Some(b"newest".to_vec())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
observe_object(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
"/child",
|
||||||
|
object("child", Some("child-ref"), b"stable"),
|
||||||
|
);
|
||||||
|
let older_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||||
|
let newer_child = client.prepare_fetch(&session, "/child").unwrap();
|
||||||
|
client
|
||||||
|
.complete(
|
||||||
|
newer_child.id,
|
||||||
|
fetch_response("/child", &object("child", Some("child-ref"), b"stable")),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut contradictory = object("child", Some("child-ref"), b"stable");
|
||||||
|
contradictory.description = Some("same-path contradiction".into());
|
||||||
|
let root_request = FetchTreeRequest {
|
||||||
|
path: "/".into(),
|
||||||
|
depth: 1,
|
||||||
|
limit: 4,
|
||||||
|
};
|
||||||
|
let error = client
|
||||||
|
.complete(
|
||||||
|
older_tree.id,
|
||||||
|
encode_fetch_tree_response(
|
||||||
|
&root_request,
|
||||||
|
&FetchTreeResponse {
|
||||||
|
root: ObjectTreeNode {
|
||||||
|
object: object("", None, b"root"),
|
||||||
|
children: vec![ObjectTreeNode {
|
||||||
|
object: contradictory,
|
||||||
|
children: vec![],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
truncated: false,
|
||||||
|
},
|
||||||
|
wire_limits(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error.kind, ClientErrorKind::InvalidResponse);
|
||||||
|
assert_eq!(
|
||||||
|
client.object(&session, "/child").unwrap().state,
|
||||||
|
ObservationState::Fresh
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user