use std::collections::HashSet;
use pagesift_api::types::Attachment;
use super::*;
impl Scraper {
pub(super) async fn export_web_images(&self, doc: &mut Document, mode: RobotsMode) {
let Some(urls) = doc.images.clone() else {
return;
};
let user_cap = self.policy.fetcher().config().max_body_bytes;
let mut budget = pagesift_net::resources::body_cap(user_cap);
let mut seen_urls = HashSet::new();
let mut seen: HashSet<String> = doc.attachments.iter().map(|a| a.hash.clone()).collect();
let (mut tried, mut skipped) = (0usize, 0usize);
for url in urls {
if !(url.starts_with("http://") || url.starts_with("https://"))
|| !seen_urls.insert(url.clone())
{
continue;
}
tried += 1;
if budget == 0 {
skipped += 1;
continue;
}
let mut req = FetchRequest::new(url.as_str());
req.max_body_bytes = Some(budget);
let resp = match self.policy.fetch(req, mode).await {
Ok(r) if (200..300).contains(&r.status) && !r.truncated => r,
_ => {
skipped += 1;
continue;
}
};
let Some(format) = image_format(&resp.body, resp.content_type()) else {
skipped += 1;
continue;
};
budget = budget.saturating_sub(resp.body.len());
let a = Attachment::new(resp.body, format, Some(url));
if seen.insert(a.hash.clone()) {
doc.attachments.push(a);
}
}
if skipped > 0
&& let Some(meta) = doc.metadata.pagesift.as_mut()
{
meta.warnings.push(format!(
"images_not_exported: {skipped} of {tried} images (robots, size limit, fetch error or not an image)"
));
}
}
}
pub(super) fn image_format(body: &[u8], _content_type: Option<&str>) -> Option<&'static str> {
pagesift_api::types::attachment::sniff_image_format(body)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_come_from_content_not_headers() {
assert_eq!(image_format(b"\x89PNG\r\n\x1a\n....", None), Some("png"));
assert_eq!(
image_format(&[0xFF, 0xD8, 0xFF, 0xE0], Some("text/html")),
Some("jpg")
);
assert_eq!(image_format(b"RIFF\0\0\0\0WEBPVP8 ", None), Some("webp"));
assert_eq!(
image_format(b"<svg xmlns=...>", Some("image/svg+xml; charset=utf-8")),
Some("svg")
);
assert_eq!(image_format(b"<html>", Some("text/html")), None);
assert_eq!(image_format(b"<html>", Some("image/svg+xml")), None);
assert_eq!(image_format(b"not an image", Some("image/tiff")), None);
assert_eq!(
image_format(
b"\xef\xbb\xbf <?xml version=\"1.0\"?>\n<!--x--><svg viewBox=\"0 0 1 1\"/>",
None
),
Some("svg")
);
assert_eq!(image_format(b"<svgfoo/>", Some("image/svg+xml")), None);
assert_eq!(image_format(b"II\x2a\0payload", None), Some("tif"));
assert_eq!(image_format(b"\0\0\0\x0cjP \r\n\x87\n", None), Some("jp2"));
}
}