Based on upstream 3ab5172e9c8f14fa1f7b24e7147eac74e2412b62 with minor adjustments to apply to 1.7.4 diff --git a/lib/collection/src/collection/snapshots.rs b/lib/collection/src/collection/snapshots.rs index e5a8be9c..ca48fb9e 100644 --- a/lib/collection/src/collection/snapshots.rs +++ b/lib/collection/src/collection/snapshots.rs @@ -241,35 +241,35 @@ impl Collection { .await } + /// Get full file path for a collection snapshot by name + /// + /// This enforces the file to be inside the snapshots directory pub async fn get_snapshot_path(&self, snapshot_name: &str) -> CollectionResult { - let snapshot_path = self.snapshots_path.join(snapshot_name); - - let absolute_snapshot_path = - snapshot_path - .canonicalize() - .map_err(|_| CollectionError::NotFound { - what: format!("Snapshot {snapshot_name}"), - })?; - - let absolute_snapshot_dir = - self.snapshots_path - .canonicalize() - .map_err(|_| CollectionError::NotFound { - what: format!("Snapshot directory: {}", self.snapshots_path.display()), - })?; + let absolute_snapshot_dir = self.snapshots_path.canonicalize().map_err(|_| { + CollectionError::not_found(format!( + "Snapshot directory: {}", + self.snapshots_path.display() + )) + })?; + + let absolute_snapshot_path = absolute_snapshot_dir + .join(snapshot_name) + .canonicalize() + .map_err(|_| CollectionError::not_found(format!("Snapshot {snapshot_name}")))?; if !absolute_snapshot_path.starts_with(absolute_snapshot_dir) { - return Err(CollectionError::NotFound { - what: format!("Snapshot {snapshot_name}"), - }); + return Err(CollectionError::not_found(format!( + "Snapshot {snapshot_name}" + ))); } - if !snapshot_path.exists() { - return Err(CollectionError::NotFound { - what: format!("Snapshot {snapshot_name}"), - }); + if !absolute_snapshot_path.exists() { + return Err(CollectionError::not_found(format!( + "Snapshot {snapshot_name}" + ))); } - Ok(snapshot_path) + + Ok(absolute_snapshot_path) } pub async fn list_shard_snapshots( diff --git a/lib/collection/src/operations/types.rs b/lib/collection/src/operations/types.rs index afc38d0f..63eae16e 100644 --- a/lib/collection/src/operations/types.rs +++ b/lib/collection/src/operations/types.rs @@ -906,6 +906,10 @@ impl CollectionError { CollectionError::BadInput { description } } + pub fn not_found(what: impl Into) -> CollectionError { + CollectionError::NotFound { what: what.into() } + } + pub fn bad_request(description: String) -> CollectionError { CollectionError::BadRequest { description } } diff --git a/lib/storage/src/content_manager/errors.rs b/lib/storage/src/content_manager/errors.rs index 1ad8d413..4528e485 100644 --- a/lib/storage/src/content_manager/errors.rs +++ b/lib/storage/src/content_manager/errors.rs @@ -46,6 +46,12 @@ impl StorageError { } } + pub fn not_found(description: impl Into) -> StorageError { + StorageError::NotFound { + description: description.into(), + } + } + /// Used to override the `description` field of the resulting `StorageError` pub fn from_inconsistent_shard_failure( err: CollectionError, diff --git a/lib/storage/src/content_manager/snapshots/mod.rs b/lib/storage/src/content_manager/snapshots/mod.rs index 8a417377..9965006a 100644 --- a/lib/storage/src/content_manager/snapshots/mod.rs +++ b/lib/storage/src/content_manager/snapshots/mod.rs @@ -24,17 +24,33 @@ pub struct SnapshotConfig { pub collections_aliases: HashMap, } +/// Get full file path for a full snapshot by name +/// +/// This enforces the file to be inside the snapshots directory pub async fn get_full_snapshot_path( toc: &TableOfContent, snapshot_name: &str, ) -> Result { - let snapshot_path = Path::new(toc.snapshots_path()).join(snapshot_name); - if !snapshot_path.exists() { - return Err(StorageError::NotFound { - description: format!("Full storage snapshot {snapshot_name} not found"), - }); + let snapshots_path = toc.snapshots_path(); + + let absolute_snapshot_dir = Path::new(snapshots_path) + .canonicalize() + .map_err(|_| StorageError::not_found(format!("Snapshot directory: {snapshots_path}")))?; + + let absolute_snapshot_path = absolute_snapshot_dir + .join(snapshot_name) + .canonicalize() + .map_err(|_| StorageError::not_found(format!("Snapshot {snapshot_name}")))?; + + if !absolute_snapshot_path.starts_with(absolute_snapshot_dir) { + return Err(StorageError::not_found(format!("Snapshot {snapshot_name}"))); } - Ok(snapshot_path) + + if !absolute_snapshot_path.exists() { + return Err(StorageError::not_found(format!("Snapshot {snapshot_name}"))); + } + + Ok(absolute_snapshot_path) } pub async fn do_delete_full_snapshot(