From 513fc849d6001fbaa97ad874af3aaeea81cc19f5 Mon Sep 17 00:00:00 2001 From: Chriopter Date: Mon, 19 Oct 2026 13:26:39 +0200 Subject: [PATCH] Send Content-Length for empty Apple Music POST requests --- crates/brumm/src/provider/apple/mod.rs | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/brumm/src/provider/apple/mod.rs b/crates/brumm/src/provider/apple/mod.rs index b38c853..d92e078 100644 --- a/crates/brumm/src/provider/apple/mod.rs +++ b/crates/brumm/src/provider/apple/mod.rs @@ -139,6 +139,10 @@ impl Client { rb = rb .header("Content-Type", "application/json") .body(serde_json::to_vec(&b)?); + } else if method == Method::POST { + // Library additions carry IDs in the query, but Apple still + // requires an explicit length for the empty POST body. + rb = rb.header(reqwest::header::CONTENT_LENGTH, 0); } let resp = rb.send().await?; let status = resp.status(); @@ -801,6 +805,33 @@ mod tests { assert!(parse_link("https://example.com/x").is_none()); } + #[tokio::test] + async fn add_to_library_sends_zero_content_length() { + let (base, seen) = serve(Arc::new(|s: &Seen| { + if s.headers.get("content-length").map(String::as_str) != Some("0") { + return (411, String::new()); + } + (204, String::new()) + })) + .await; + let client = Client::with_base(&base, "dev", "user"); + for kind in ["songs", "albums", "playlists"] { + client + .add_to_library(kind, &["123".into(), "456".into()]) + .await + .unwrap(); + } + let seen = seen.lock().unwrap(); + assert_eq!(seen.len(), 3); + for (s, kind) in seen.iter().zip(["songs", "albums", "playlists"]) { + assert_eq!(s.method, "POST"); + let (path, query) = split(&s.target); + assert_eq!(path, "/v1/me/library"); + assert_eq!(query[&format!("ids[{kind}]")], "123,456"); + assert!(s.body.is_empty()); + } + } + #[tokio::test] async fn library_pagination_keeps_favorites_and_limit() { let (base, seen) = serve(Arc::new(|s: &Seen| {