fix(xlsx): read .xls files whose Workbook stream ends in padding

Merged
US
24d ago
+29-2

What

partition_xlsx() now reads .xls files whose Workbook stream ends in a few bytes of padding, such as many files written by xlwt. Before, these raised struct.error: unpack requires a buffer of 4 bytes from the msoffcrypto encryption check. Closes #4544.

xlwt workbook, 3 rows (example-docs/xlwt-padded-workbook.xls)

Before: struct.error: unpack requires a buffer of 4 bytes
After:  [Table("Fruit Count Apple 3 Pear 7")]

Why

msoffcrypto's is_encrypted() scans an XLS Workbook stream as 4-byte record headers, looking for a FilePass record. xlwt pads the stream with zeros to a 4096-byte boundary, and when fewer than 4 bytes of padding are left, the scan raises (nolze/msoffcrypto-tool#83). A struct.error from that check is now read as "not encrypted". This does not let a password-protected .xls through: its FilePass record directly follows the BOF record, so the scan finds it before any padding. msoffcrypto's rc4cryptoapi_password.xls and xor_password_123456789012345.xls samples are still rejected with "XLSX file is password protected.", and its plain.xls sample still partitions.

Validation

  • test_partition_xlsx_reads_xls_whose_workbook_stream_ends_in_padding partitions a 5.6 KB xlwt fixture. It fails on main with the struct.error and passes here.
  • test_unstructured/partition/test_xlsx.py: 108 passed.
  • ruff check and ruff format --check pass on the changed files.
  • CHANGELOG entry under 0.27.27, with the version bumped. #4543 also bumps to 0.27.27; I'll rebase whichever merges second.