diff --git a/backend/crates/providers/openai/src/transport/profile/platform_release/artifact.rs b/backend/crates/providers/openai/src/transport/profile/platform_release/artifact.rs index 0baac707c..c52433af1 100644 --- a/backend/crates/providers/openai/src/transport/profile/platform_release/artifact.rs +++ b/backend/crates/providers/openai/src/transport/profile/platform_release/artifact.rs @@ -11,7 +11,11 @@ const MAX_CORE: u64 = 512 * 1024 * 1024; const MAX_ASAR_PREFIX: u64 = 128 * 1024 * 1024; pub fn read_windows(source: R, arch: &str) -> io::Result { - let mut zip = zip::ZipArchive::new(source).map_err(|_| invalid())?; + // MSIX 从文件起点开始,固定偏移避免自动探测扫描整包、耗尽 HTTP Range 下载预算 + let config = zip::read::Config { + archive_offset: zip::read::ArchiveOffset::Known(0), + }; + let mut zip = zip::ZipArchive::with_config(config, source).map_err(|_| invalid())?; if zip.len() > 20_000 { return Err(invalid()); } diff --git a/backend/crates/providers/openai/tests/transport/profile/platform_release/artifact.rs b/backend/crates/providers/openai/tests/transport/profile/platform_release/artifact.rs index 1e4f16be8..7736e572f 100644 --- a/backend/crates/providers/openai/tests/transport/profile/platform_release/artifact.rs +++ b/backend/crates/providers/openai/tests/transport/profile/platform_release/artifact.rs @@ -6,7 +6,22 @@ use provider_openai::transport::profile::platform_release::{ xz::IndexedXz, }; use serde_json::json; -use std::io::{Cursor, Read, Write}; +use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; + +// seek 不重置累计读取预算,用小体积夹具捕获整包扫描 +struct ReadBudget(io::Take); + +impl Read for ReadBudget { + fn read(&mut self, buffer: &mut [u8]) -> io::Result { + self.0.read(buffer) + } +} + +impl Seek for ReadBudget { + fn seek(&mut self, position: SeekFrom) -> io::Result { + self.0.get_mut().seek(position) + } +} fn core(windows: bool, arm: bool) -> Vec { let mut bytes = vec![0; 8192]; @@ -115,6 +130,26 @@ fn windows_and_linux_read_versions_from_the_same_package_and_check_architecture( assert!(read_linux(Cursor::new(deb(arm)), other).is_err()); } } +#[test] +fn windows_reads_versions_within_budget_despite_large_unrelated_entries() { + for (arch, arm) in [("x86_64", false), ("arm64", true)] { + let bytes = zip(arm, &core(true, arm)); + let expected = read_windows(Cursor::new(&bytes), arch).unwrap(); + let mut archive = zip::ZipWriter::new_append(Cursor::new(bytes)).unwrap(); + archive + .start_file( + "app/unrelated.bin", + zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Stored), + ) + .unwrap(); + io::copy(&mut io::repeat(0).take(2 * 1024 * 1024), &mut archive).unwrap(); + let bytes = archive.finish().unwrap().into_inner(); + let source = ReadBudget(Cursor::new(bytes).take(64 * 1024)); + assert_eq!(read_windows(source, arch).unwrap(), expected); + } +} + #[test] fn truncated_or_corrupt_archives_do_not_publish_versions() { let zip = zip(false, &core(true, false));