mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
pixiv: harden ugoira zip extraction, degrade instead of panicking
Sniff the frame extension from magic bytes instead of the entry filename, cap each frame at 64 MiB (declared size + streamed read), and map a panicked encode worker to the existing degrade path instead of expect()-panicking the whole fetch handler.
This commit is contained in:
@@ -238,23 +238,48 @@ impl PixivAPI {
|
|||||||
// frames are uniformly jpg or png per artwork.
|
// frames are uniformly jpg or png per artwork.
|
||||||
let mut archive = zip::ZipArchive::new(Cursor::new(zip_bytes))
|
let mut archive = zip::ZipArchive::new(Cursor::new(zip_bytes))
|
||||||
.map_err(|e| format!("unzip: {e}"))?;
|
.map_err(|e| format!("unzip: {e}"))?;
|
||||||
// pixiv ugoira frames are uniformly jpg or png per artwork; take
|
if archive.len() == 0 {
|
||||||
// the extension from the first entry.
|
return Err("empty frame zip".to_string());
|
||||||
let extension = if archive.len() > 0 {
|
}
|
||||||
let first_name = archive
|
// Uniform jpg or png per artwork; sniff the first entry's
|
||||||
.by_index(0)
|
// magic bytes instead of trusting its filename.
|
||||||
.map_err(|e| e.to_string())?
|
let mut first = archive.by_index(0).map_err(|e| e.to_string())?;
|
||||||
.name()
|
let mut first_bytes = Vec::new();
|
||||||
.to_string();
|
first
|
||||||
first_name.rsplit('.').next().unwrap_or("jpg").to_string()
|
.take(64 * 1024 * 1024 + 1)
|
||||||
|
.read_to_end(&mut first_bytes)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
if first_bytes.len() > 64 * 1024 * 1024 {
|
||||||
|
return Err("frame exceeds size cap".to_string());
|
||||||
|
}
|
||||||
|
let extension = if first_bytes.starts_with(&[0xFF, 0xD8]) {
|
||||||
|
"jpg"
|
||||||
|
} else if first_bytes.starts_with(b"\x89PNG") {
|
||||||
|
"png"
|
||||||
} else {
|
} else {
|
||||||
"jpg".to_string()
|
"jpg"
|
||||||
};
|
};
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for i in 0..archive.len() {
|
{
|
||||||
|
let path = frames_dir
|
||||||
|
.path()
|
||||||
|
.join(format!("img_{count:05}.{extension}"));
|
||||||
|
std::fs::write(&path, &first_bytes).map_err(|e| e.to_string())?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
for i in 1..archive.len() {
|
||||||
let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
|
let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
|
||||||
|
if entry.size() > 64 * 1024 * 1024 {
|
||||||
|
return Err(format!("frame {i} exceeds size cap"));
|
||||||
|
}
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
entry.read_to_end(&mut bytes).map_err(|e| e.to_string())?;
|
entry
|
||||||
|
.take(64 * 1024 * 1024 + 1)
|
||||||
|
.read_to_end(&mut bytes)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
if bytes.len() > 64 * 1024 * 1024 {
|
||||||
|
return Err(format!("frame {i} exceeds size cap"));
|
||||||
|
}
|
||||||
let path = frames_dir
|
let path = frames_dir
|
||||||
.path()
|
.path()
|
||||||
.join(format!("img_{count:05}.{extension}"));
|
.join(format!("img_{count:05}.{extension}"));
|
||||||
@@ -304,7 +329,10 @@ impl PixivAPI {
|
|||||||
Ok((output.to_string_lossy().into_owned(), out_dir))
|
Ok((output.to_string_lossy().into_owned(), out_dir))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.expect("ugoira encode worker panicked");
|
.map_err(|e| {
|
||||||
|
log::error!("ugoira encode worker panicked for {illust_id}: {e}");
|
||||||
|
PixivError::Api(format!("ugoira worker failed: {e}"))
|
||||||
|
})?;
|
||||||
match result {
|
match result {
|
||||||
Ok(pair) => Ok(Some(pair)),
|
Ok(pair) => Ok(Some(pair)),
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user