diff --git a/src/detector.rs b/src/detector.rs index ad0965a..d767c9f 100644 --- a/src/detector.rs +++ b/src/detector.rs @@ -98,11 +98,23 @@ pub fn detect_pdf_type_with_config>( crate::validate_pdf_file(&path)?; // First, load metadata only (fast operation) - let metadata = Document::load_metadata(&path)?; + let metadata = match Document::load_metadata(&path) { + Ok(m) => m, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_metadata_with_password(&path, "")? + } + Err(e) => return Err(e.into()), + }; // Then load the full document for content inspection // We use filtered loading to skip heavy objects we don't need - let doc = Document::load(&path)?; + let doc = match Document::load(&path) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_with_password(&path, "")? + } + Err(e) => return Err(e.into()), + }; detect_from_document(&doc, metadata.page_count, &config) } @@ -120,10 +132,22 @@ pub fn detect_pdf_type_mem_with_config( crate::validate_pdf_bytes(buffer)?; // Load metadata first (fast) - let metadata = Document::load_metadata_mem(buffer)?; + let metadata = match Document::load_metadata_mem(buffer) { + Ok(m) => m, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_metadata_mem_with_password(buffer, "")? + } + Err(e) => return Err(e.into()), + }; // Load document for inspection - let doc = Document::load_mem(buffer)?; + let doc = match Document::load_mem(buffer) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_mem_with_password(buffer, "")? + } + Err(e) => return Err(e.into()), + }; detect_from_document(&doc, metadata.page_count, &config) } diff --git a/src/extractor/mod.rs b/src/extractor/mod.rs index 00081af..19f03bc 100644 --- a/src/extractor/mod.rs +++ b/src/extractor/mod.rs @@ -33,14 +33,26 @@ pub use layout::group_into_lines; /// Extract text from PDF file as plain string pub fn extract_text>(path: P) -> Result { crate::validate_pdf_file(&path)?; - let doc = Document::load(path)?; + let doc = match Document::load(&path) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_with_password(&path, "")? + } + Err(e) => return Err(e.into()), + }; extract_text_from_doc(&doc) } /// Extract text from PDF memory buffer pub fn extract_text_mem(buffer: &[u8]) -> Result { crate::validate_pdf_bytes(buffer)?; - let doc = Document::load_mem(buffer)?; + let doc = match Document::load_mem(buffer) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_mem_with_password(buffer, "")? + } + Err(e) => return Err(e.into()), + }; extract_text_from_doc(&doc) } @@ -76,7 +88,13 @@ pub(crate) fn extract_text_with_positions_and_rects>( page_filter: Option<&HashSet>, ) -> Result<(Vec, Vec), PdfError> { crate::validate_pdf_file(&path)?; - let doc = Document::load(path)?; + let doc = match Document::load(&path) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_with_password(&path, "")? + } + Err(e) => return Err(e.into()), + }; let font_cmaps = FontCMaps::from_doc(&doc); extract_positioned_text_from_doc(&doc, &font_cmaps, page_filter) } @@ -101,7 +119,13 @@ pub(crate) fn extract_text_with_positions_mem_and_rects( page_filter: Option<&HashSet>, ) -> Result<(Vec, Vec), PdfError> { crate::validate_pdf_bytes(buffer)?; - let doc = Document::load_mem(buffer)?; + let doc = match Document::load_mem(buffer) { + Ok(d) => d, + Err(ref e) if crate::is_encrypted_lopdf_error(e) => { + Document::load_mem_with_password(buffer, "")? + } + Err(e) => return Err(e.into()), + }; let font_cmaps = FontCMaps::from_doc(&doc); extract_positioned_text_from_doc(&doc, &font_cmaps, page_filter) } diff --git a/src/lib.rs b/src/lib.rs index e0bcf6b..8bf034f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -500,6 +500,7 @@ impl From for PdfError { | lopdf::Error::InvalidPassword | lopdf::Error::AlreadyEncrypted | lopdf::Error::UnsupportedSecurityHandler(_) => PdfError::Encrypted, + lopdf::Error::Unimplemented(msg) if msg.contains("encrypted") => PdfError::Encrypted, lopdf::Error::Parse(ref pe) if pe.to_string().contains("invalid file header") => { PdfError::NotAPdf("invalid PDF file header".to_string()) } @@ -514,6 +515,17 @@ impl From for PdfError { } } +/// Check whether a `lopdf::Error` represents an encryption-related failure. +pub(crate) fn is_encrypted_lopdf_error(e: &lopdf::Error) -> bool { + matches!( + e, + lopdf::Error::Decryption(_) + | lopdf::Error::InvalidPassword + | lopdf::Error::AlreadyEncrypted + | lopdf::Error::UnsupportedSecurityHandler(_) + ) || matches!(e, lopdf::Error::Unimplemented(msg) if msg.contains("encrypted")) +} + // --------------------------------------------------------------------------- // PDF validation helpers // ---------------------------------------------------------------------------