Handle non-UTF-8 DocumentInfo keys in repair_docinfo_nuls (closes #1540)

Some PDFs use a /Name dictionary key in /DocumentInfo whose bytes are not
valid UTF-8/PDFDocEncoding, e.g. a Latin-1 /Saks#e5r. Older pikepdf raised
UnicodeDecodeError while iterating such a block, crashing the pipeline
during PDF/A conversion. repair_docinfo_nuls is documented to log and
continue on a malformed DocumentInfo block, so catch UnicodeDecodeError
alongside TypeError.

Add a mock-based unit test that drives the decode-error branch (current
pikepdf surrogate-escapes instead of raising) and an end-to-end test over
the reporter's file, committed as docinfo_latin1_key.pdf.
This commit is contained in:
James R. Barlow
2026-06-27 01:50:58 -07:00
parent 8b20bb3c5b
commit ea7ad7d683
5 changed files with 79 additions and 3 deletions
+3
View File
@@ -76,6 +76,9 @@ the copyright holder(s) and license(s) applicable to these resources.
* - missing_docinfo.pdf
- synthetic
- PDF file with no /DocumentInfo section
* - docinfo_latin1_key.pdf
- synthetic
- PDF whose /DocumentInfo dictionary has a /Name key with Latin-1 bytes (/Saks#e5r) that is not valid UTF-8
* - overlay.pdf
- synthetic
- PDF file generated by PDFPen pro that triggered content stream parse errors
+32
View File
@@ -0,0 +1,32 @@
%PDF-1.3
%¿÷¢þ
1 0 obj
<< /Pages 3 0 R /Type /Catalog >>
endobj
2 0 obj
<< /Author (Geomatikk AS) /Beskrivelse () /Creator (OCRmyPDF 16.10.0 / EasyOCR-PDF 1.7.2) /CreatorVersion (6.36.0.918) /Dokumentidplanreg () /Enhetsnavn () /Hyperlink (1) /Opprinnelse () /Producer (pikepdf 9.5.2) /RegistrationDate (N/A) /Saksansvarlig#20enhet () /Saksbehandler () /Saksnr () /Saks#e5r () /Status () >>
endobj
3 0 obj
<< /Count 1 /Kids [ 4 0 R ] /Type /Pages >>
endobj
4 0 obj
<< /Contents 5 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << >> /Type /Page >>
endobj
5 0 obj
<< /Length 0 /Filter /FlateDecode >>
stream
endstream
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000398 00000 n
0000000457 00000 n
0000000563 00000 n
trailer << /Info 2 0 R /Root 1 0 R /Size 6 /ID [<c5231b8cfab9c82526c0da7475add5da><c5231b8cfab9c82526c0da7475add5da>] >>
startxref
633
%%EOF
+28 -1
View File
@@ -6,13 +6,14 @@ from __future__ import annotations
import datetime as dt
import warnings
from shutil import copyfile
from unittest.mock import MagicMock, patch
import pikepdf
import pytest
from pikepdf.models.metadata import decode_pdf_date
from ocrmypdf._jobcontext import PdfContext
from ocrmypdf._metadata import metadata_fixup
from ocrmypdf._metadata import metadata_fixup, repair_docinfo_nuls
from ocrmypdf._pipeline import convert_to_pdfa
from ocrmypdf.api import setup_plugin_infrastructure
from ocrmypdf.cli import get_options_and_plugins
@@ -43,6 +44,32 @@ def test_preserve_docinfo(output_type, resources, outpdf):
assert pdfa_info['output'] == output_type
def test_repair_docinfo_nuls_undecodable_key(caplog):
"""A DocumentInfo key with bytes that don't decode must not crash.
Some PDFs use a /Name dictionary key in DocumentInfo whose bytes are not
valid PDFDocEncoding/UTF-8 (e.g. Latin-1 ``/Saks#e5r``). Older pikepdf
raised UnicodeDecodeError while iterating such a dictionary. The repair
must log and continue rather than propagate the exception. See #1540.
"""
pdf = MagicMock()
pdf.docinfo.items.side_effect = UnicodeDecodeError(
'utf-8', b'Saks\xe5r', 4, 5, 'invalid continuation byte'
)
# Make isinstance(pdf.docinfo, Dictionary) succeed so we reach the loop.
with patch('ocrmypdf._metadata.Dictionary', MagicMock):
result = repair_docinfo_nuls(pdf)
assert result is False
assert 'malformed DocumentInfo' in caplog.text
def test_repair_docinfo_nuls_undecodable_key_real_file(resources):
"""Opening a real file with a Latin-1 DocumentInfo key must not crash."""
with pikepdf.open(resources / 'docinfo_latin1_key.pdf') as pdf:
# Should return without raising regardless of pikepdf's decode behavior.
repair_docinfo_nuls(pdf)
@pytest.mark.parametrize("output_type", ['pdfa', 'pdf'])
def test_override_metadata(output_type, resources, outpdf, caplog):
input_file = resources / 'c02-22.pdf'