From 52d5ccb241a6e515abfd6cd1356b8a0bda292992 Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Thu, 7 May 2026 12:13:59 -0700 Subject: [PATCH] fix(app-server): accept create attrs on staged uploads --- .../src/request_processors/upload_sftp.rs | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/upload_sftp.rs b/codex-rs/app-server/src/request_processors/upload_sftp.rs index cc2231572d..bdeaadd5e8 100644 --- a/codex-rs/app-server/src/request_processors/upload_sftp.rs +++ b/codex-rs/app-server/src/request_processors/upload_sftp.rs @@ -151,11 +151,8 @@ impl Handler for UploadSftpHandler { id: u32, filename: String, pflags: OpenFlags, - attrs: FileAttributes, + _attrs: FileAttributes, ) -> Result { - if has_attributes(&attrs) { - return Err(UploadSftpError::Unsupported); - } if !self.is_allowed_path(&filename).await { return Err(UploadSftpError::PermissionDenied); } @@ -207,17 +204,6 @@ impl Handler for UploadSftpHandler { } } -fn has_attributes(attrs: &FileAttributes) -> bool { - attrs.size.is_some() - || attrs.uid.is_some() - || attrs.user.is_some() - || attrs.gid.is_some() - || attrs.group.is_some() - || attrs.permissions.is_some() - || attrs.atime.is_some() - || attrs.mtime.is_some() -} - fn ok_status(id: u32) -> Status { Status { id, @@ -283,4 +269,36 @@ mod tests { assert!(err.to_string().contains("Permission denied")); assert!(!path.exists()); } + + #[tokio::test] + async fn accepts_create_attributes_for_allocated_paths() { + let tempdir = TempDir::new().expect("create temp dir"); + let path = tempdir.path().join("uploads").join("note.txt"); + tokio::fs::create_dir_all(path.parent().expect("path parent")) + .await + .expect("create upload parent"); + let allowed_paths = Arc::new(Mutex::new(HashSet::from([path.clone()]))); + let (client_stream, server_stream) = tokio::io::duplex(UPLOAD_SFTP_STREAM_BUFFER_BYTES); + russh_sftp::server::run(server_stream, UploadSftpHandler::new(allowed_paths)).await; + + let session = SftpSession::new(client_stream) + .await + .expect("initialize sftp"); + let mut attrs = FileAttributes::empty(); + attrs.size = Some(0); + let mut file = session + .open_with_flags_and_attributes( + path.to_string_lossy(), + OpenFlags::CREATE | OpenFlags::TRUNCATE | OpenFlags::WRITE, + attrs, + ) + .await + .expect("open staged upload with attrs"); + file.write_all(b"hello").await.expect("write staged upload"); + file.shutdown().await.expect("close staged upload"); + assert_eq!( + tokio::fs::read_to_string(path).await.expect("read upload"), + "hello" + ); + } }