From 4f40be205d05fedd18b4511a920771e1ba49c634 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Thu, 12 Jun 2025 21:19:47 -0700 Subject: [PATCH] Adds CONTENT_LENGTH workaround for CocoaHTTPServer bug --- kordophone/src/api/http_client.rs | 28 +++++++++++++++------- kordophone/src/api/mod.rs | 1 + kordophone/src/tests/test_client.rs | 1 + kordophoned/src/daemon/attachment_store.rs | 5 +++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/kordophone/src/api/http_client.rs b/kordophone/src/api/http_client.rs index ddd6d65..c604c6e 100644 --- a/kordophone/src/api/http_client.rs +++ b/kordophone/src/api/http_client.rs @@ -13,7 +13,6 @@ use async_trait::async_trait; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tokio::net::TcpStream; -use tokio_util::io::ReaderStream; use futures_util::stream::{BoxStream, Stream}; use futures_util::task::Context; @@ -292,32 +291,45 @@ impl APIInterface for HTTPAPIClient { async fn upload_attachment( &mut self, - data: tokio::io::BufReader, + mut data: tokio::io::BufReader, filename: &str, + size: u64, ) -> Result where R: tokio::io::AsyncRead + Unpin + Send + Sync + 'static, { + use tokio::io::AsyncReadExt; + #[derive(Deserialize, Debug)] struct UploadAttachmentResponse { #[serde(rename = "fileTransferGUID")] guid: String, } + // TODO: We can still use Body::wrap_stream here, but we need to make sure to plumb the CONTENT_LENGTH header, + // otherwise CocoaHTTPServer will crash because of a bug. + // + // See ff03e73758f30c081a9319a8c04025cba69b8393 for what this was like before. + let mut bytes = Vec::new(); + data.read_to_end(&mut bytes) + .await + .map_err(|e| Error::ClientError(e.to_string()))?; + let endpoint = format!("uploadAttachment?filename={}", filename); - let mut data_opt = Some(data); + let mut bytes_opt = Some(bytes); let response: UploadAttachmentResponse = self .deserialized_response_with_body_retry( &endpoint, Method::POST, move || { - let stream = ReaderStream::new( - data_opt.take().expect("Stream already consumed during retry"), - ); - Body::wrap_stream(stream) + Body::from( + bytes_opt + .take() + .expect("Body already consumed during retry"), + ) }, - false, // don't retry auth for streaming body + false, ) .await?; diff --git a/kordophone/src/api/mod.rs b/kordophone/src/api/mod.rs index c0e67c0..7220511 100644 --- a/kordophone/src/api/mod.rs +++ b/kordophone/src/api/mod.rs @@ -56,6 +56,7 @@ pub trait APIInterface { &mut self, data: tokio::io::BufReader, filename: &str, + size: u64, ) -> Result where R: tokio::io::AsyncRead + Unpin + Send + Sync + 'static; diff --git a/kordophone/src/tests/test_client.rs b/kordophone/src/tests/test_client.rs index ffb9874..122e74c 100644 --- a/kordophone/src/tests/test_client.rs +++ b/kordophone/src/tests/test_client.rs @@ -132,6 +132,7 @@ impl APIInterface for TestClient { &mut self, data: tokio::io::BufReader, filename: &str, + size: u64, ) -> Result where R: tokio::io::AsyncRead + Unpin + Send + Sync + 'static, diff --git a/kordophoned/src/daemon/attachment_store.rs b/kordophoned/src/daemon/attachment_store.rs index 6b1c00b..b8c1718 100644 --- a/kordophoned/src/daemon/attachment_store.rs +++ b/kordophoned/src/daemon/attachment_store.rs @@ -195,7 +195,10 @@ impl AttachmentStore { let filename = incoming_path.file_name().unwrap().to_str().unwrap(); log::trace!(target: target::ATTACHMENTS, "Uploading attachment to server: {}", &filename); let mut client = Daemon::get_client_impl(database).await?; - let guid = client.upload_attachment(reader, filename).await?; + + let metadata = std::fs::metadata(&temporary_path)?; + let size = metadata.len(); + let guid = client.upload_attachment(reader, filename, size).await?; // Delete the temporary file. log::debug!(target: target::ATTACHMENTS, "Upload completed with guid {}, deleting temporary file: {}", guid, temporary_path.display());