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.
This commit is contained in:
VoiceLessQ authored and Dylan M. Taylor committed 2026-10-30 22:43:46 -04:00
1 parent 2b8bd856ce
commit f0b5b22dce
1 file changed
+16 -1
+16 -1
View File
@@ -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