use pagesift_api::types::{Upload, UploadKind};
use super::*;
const DEFAULT_PARSE_TIMEOUT: Duration = Duration::from_secs(30);
impl Scraper {
pub async fn parse(&self, upload: Upload, opts: &ScrapeOptions) -> Result<Document, ApiError> {
let Some(audit) = &self.audit else {
return self.scrape_inner(Source::Upload(upload), opts).await;
};
let name = format!("upload:{}", upload.filename);
let size = upload.bytes.len();
let started = Instant::now();
let result = self.scrape_inner(Source::Upload(upload), opts).await;
audit.record_parse(&name, size, opts, &result, started.elapsed());
result
}
pub(super) async fn parse_upload(
&self,
upload: Upload,
opts: &ScrapeOptions,
) -> Result<Document, ApiError> {
let started = Instant::now();
let deadline = started
+ opts
.timeout
.map_or(DEFAULT_PARSE_TIMEOUT, Duration::from_millis);
let content_type = match upload.kind {
UploadKind::Html => upload
.content_type
.clone()
.filter(|c| c.to_ascii_lowercase().contains("html")),
UploadKind::Pdf | UploadKind::Office(_) => None,
};
let source = upload
.url
.as_ref()
.map_or_else(|| upload.filename.clone(), Url::to_string);
let meta = Metadata {
source_url: Some(source),
status_code: 200,
content_type: Some(
content_type
.clone()
.unwrap_or_else(|| upload.kind.mime().into()),
),
proxy_used: "basic".into(),
..Default::default()
};
let ext = PagesiftMeta {
engine: Some("upload".into()),
..Default::default()
};
let progress = Progress {
started,
timings: BTreeMap::new(),
warnings: Vec::new(),
};
match upload.kind {
UploadKind::Pdf => {
self.pdf_document(upload.bytes, opts, (meta, ext), progress, Some(deadline))
.await
}
UploadKind::Office(format) => {
let input = office::OfficeInput {
bytes: upload.bytes,
format,
content_type: upload.content_type,
};
self.office_document(input, opts, (meta, ext), progress, Some(deadline))
.await
}
UploadKind::Html => {
let html = pagesift_net::decode_owned(upload.bytes, content_type.as_deref(), None);
let base = upload.url.unwrap_or_else(|| upload_base(&upload.filename));
self.parse_html(html, &base, opts, (meta, ext), progress, deadline)
.await
}
}
}
async fn parse_html(
&self,
decoded: pagesift_net::Decoded,
base: &Url,
opts: &ScrapeOptions,
(mut meta, mut ext): (Metadata, PagesiftMeta),
mut p: Progress,
deadline: Instant,
) -> Result<Document, ApiError> {
if let Some(warning) = decoded.warning() {
p.warnings.push(warning);
}
let stage = self
.policy
.fetcher()
.acquire_stage(pagesift_net::NetworkStage::Parse)
.await;
let t = Instant::now();
let work = process_html(decoded.text, base, Some(200), opts);
let (doc, html) = match tokio::time::timeout_at(deadline.into(), work).await {
Ok(Ok(result)) => {
stage.finish(pagesift_net::StageOutcome::Success);
result
}
Ok(Err(error)) => {
stage.finish(pagesift_net::StageOutcome::DeterministicFailure);
return Err(error);
}
Err(_) => {
stage.finish(pagesift_net::StageOutcome::DeterministicFailure);
return Err(ApiError::new(
ErrorCode::Timeout,
"HTML extraction did not finish within the timeout",
));
}
};
p.timings.insert("extract".into(), ms(t.elapsed()));
if doc.metadata.video_fallback {
p.warnings.push(
"video_metadata: no readable body text; used the page's embedded video metadata"
.into(),
);
}
if doc.barrier.is_some() {
p.warnings.push(
"access_barrier: the uploaded HTML looks like a bot-check or access-barrier page, not the content"
.into(),
);
}
fill_metadata(&mut meta, &doc.metadata);
if meta
.favicon
.as_deref()
.is_some_and(|f| f.starts_with("file:"))
{
meta.favicon = None;
}
fill_structured(&mut ext, &doc);
fill_fingerprints(&mut ext, &doc);
p.warnings.extend(doc.coverage.warnings());
let raw_bytes = Some(thin_basis_bytes(&html, &doc.markdown));
let video_fallback = doc.metadata.video_fallback;
Ok(finish(
document(doc, opts, html),
meta,
ext,
p.timings,
p.warnings,
raw_bytes,
video_fallback,
p.started,
))
}
}
fn upload_base(filename: &str) -> Url {
let mut base = Url::parse("file:///").expect("static URL");
base.set_path(filename);
base
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn upload_base_resolves_relative_links_locally() {
let base = upload_base("my report.html");
assert_eq!(base.as_str(), "file:///my%20report.html");
assert_eq!(
base.join("img/a.png").unwrap().as_str(),
"file:///img/a.png"
);
}
}