diff --git a/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs b/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs index 17a90d4d6..b359ae887 100644 --- a/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs +++ b/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs @@ -7,7 +7,7 @@ use crate::{ }, shared::webdav::{EntryPath, StoragePath}, }; -use sea_query::{Alias, Expr, Iden, Order, PostgresQueryBuilder, Query, SimpleExpr}; +use sea_query::{Alias, Expr, Func, Iden, Order, PostgresQueryBuilder, Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use sqlx::{postgres::PgRow, Row}; @@ -181,7 +181,12 @@ impl EntryRepository { USER_TABLE, Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) - .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", full_path))) // Everything that starts with the path + .and_where( + Func::cust(Alias::new("starts_with")) + .arg(Expr::col((ENTRY_TABLE, EntryIden::Path))) + .arg(&full_path) + .into(), + ) .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .limit(1) .to_owned(); @@ -292,7 +297,12 @@ impl EntryRepository { USER_TABLE, Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) - .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", dir_path))) // Everything that starts with the path + .and_where( + Func::cust(Alias::new("starts_with")) + .arg(Expr::col((ENTRY_TABLE, EntryIden::Path))) + .arg(&dir_path) + .into(), + ) .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); @@ -379,7 +389,12 @@ impl EntryRepository { USER_TABLE, Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) - .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", full_path))) // Everything that starts with the path + .and_where( + Func::cust(Alias::new("starts_with")) + .arg(Expr::col((ENTRY_TABLE, EntryIden::Path))) + .arg(&full_path) + .into(), + ) .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); @@ -819,6 +834,19 @@ mod tests { } } assert_eq!(set.len(), 6); + + // Treat `_` literally so a lookalike sibling is not included. + create_entry_for_path(&db, user.id, "/test/myXfolder/secret.txt").await; + let entries = EntryRepository::list_shallow( + &EntryPath::new(user_pubkey, StoragePath::new("/test/my_folder/").unwrap()), + None, + None, + false, + &mut db.pool().into(), + ) + .await + .unwrap(); + assert!(entries.is_empty()); } #[tokio::test] @@ -1138,6 +1166,19 @@ mod tests { } } assert_eq!(set.len(), 7); + + // Treat `%` literally so a lookalike sibling is not included. + create_entry_for_path(&db, user.id, "/test/myANYfolder/secret.txt").await; + let entries = EntryRepository::list_deep( + &EntryPath::new(user_pubkey, StoragePath::new("/test/my%folder/").unwrap()), + None, + None, + false, + &mut db.pool().into(), + ) + .await + .unwrap(); + assert!(entries.is_empty()); } #[tokio::test] @@ -1319,5 +1360,15 @@ mod tests { .await .unwrap(); assert!(!exists); + + // Treat `_` literally so a lookalike sibling does not make the directory exist. + create_entry_for_path(&db, user.id, "/test/ghostX/secret.txt").await; + let exists = EntryRepository::contains_directory( + &EntryPath::new(user_pubkey, StoragePath::new("/test/ghost_/").unwrap()), + &mut db.pool().into(), + ) + .await + .unwrap(); + assert!(!exists); } }