From f0b5b22dcef07539ca36f13ab83e21acbbb5228b Mon Sep 17 00:00:00 2001 From: VoiceLessQ Date: Fri, 30 Oct 2026 11:01:55 -0200 Subject: [PATCH] fix(update): read GitHub API answers up to a 16 MiB cap get_api shared the 4 MiB limit for small answers. A launcher version list of 100 releases can pass that: SolidCraft's was 6.5 MB (craft-launcher#1021). Other small requests keep 4 MiB. --- crates/update/src/http.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/update/src/http.rs b/crates/update/src/http.rs index 963eed1e..6a154348 100644 --- a/crates/update/src/http.rs +++ b/crates/update/src/http.rs @@ -11,6 +11,8 @@ use sha2::{Digest, Sha256}; /// Largest JSON or text answer we read. const MAX_SMALL: u64 = 4 << 20; +/// Largest GitHub API answer we read: 100 releases of an app with 49 assets are about 10 MB. +const MAX_API: u64 = 16 << 20; /// What a GET of a small resource produced. #[derive(Debug, PartialEq, Eq)] @@ -154,7 +156,7 @@ impl Http { match status { 200 => { let etag = header("etag"); - let text = read_text_capped(&mut resp, MAX_SMALL).map_err(|e| format!("unreadable answer from GitHub ({e})"))?; + let text = read_text_capped(&mut resp, MAX_API).map_err(|e| format!("unreadable answer from GitHub ({e})"))?; Ok(Fetched::Body { text, etag }) } 304 => Ok(Fetched::NotModified), @@ -996,6 +998,19 @@ pub(crate) mod tests { assert_eq!(http.get_feed(&url).unwrap(), "{\"ok\":true}"); } + #[test] + fn a_long_release_list_is_read_up_to_the_api_cap() { + // SolidCraft's list of 100 releases was 6.5 MB, past MAX_SMALL (craft-launcher#1021). + let long = vec![b' '; (MAX_SMALL as usize) + 1024]; + let (url, _) = serve(long.clone(), vec![Reply::Full]); + let http = Http::new(crate::TEST_AGENT).unwrap(); + assert!(matches!(http.get_api(&url, None), Ok(Fetched::Body { text, .. }) if text.len() == long.len())); + // Past MAX_API it is refused outright, not truncated and parsed. + let (url, _) = serve(vec![b' '; (MAX_API as usize) + 1024], vec![Reply::Full]); + let err = http.get_api(&url, None).unwrap_err(); + assert!(err.contains("larger than"), "{err}"); + } + #[test] fn a_gzip_bomb_answer_is_refused_before_parsing() { // A few hundred bytes on the wire that decode to more than MAX_SMALL. ureq's own limit