[FIX] tools.pdf: do not crash when PDF has no attachments

In the PDF, `DictionaryObject` can be wrapped in `IndirectObject`. For nested
dictionaries, using `get` on a `DictionaryObject` will unwrap the result if it's
an `IndirectObject`, but not `__getitem__` so when trying to futher call `get`
on the result will cause an error: we need to unwrap the object first.
Instead, we patch `DictionaryObject.get()` for it to unwrap the object in case
it's an `IndirectObject`.

This is a follow-up commit of fece5ab1bf2fef043b131f1bd0886f0841116103

closes odoo/odoo#53269

X-original-commit: 189a0b28ec7fdb7472f936450cfd7b4bfd6912ed
Signed-off-by: Olivier Dony (odo) <odo@openerp.com>
Signed-off-by: bfr-o <bfr-o@users.noreply.github.com>
This commit is contained in:
Benjamin Frantzen (bfr)
2020-06-18 16:49:32 +00:00
parent 80379606db
commit ae15f89f73
4 changed files with 135 additions and 2 deletions
+1
View File
@@ -45,3 +45,4 @@ from . import test_tests_tags
from . import test_base_document_layout
from . import test_form_create
from . import test_cloc
from . import test_pdf
+44
View File
@@ -0,0 +1,44 @@
%PDF-1.3
%µ¶
1 0 obj
<</Type/Catalog/Pages 2 0 R/Names 5 0 R>>
endobj
2 0 obj
<</Type/Pages/Kids[3 0 R]/Count 1>>
endobj
3 0 obj
<</Type/Page/Parent 2 0 R/Resources<</Font<</F1<</Type/Font/Subtype/Type1/BaseFont/Arial>>>>>>/Contents 4 0 R>>
endobj
4 0 obj
<</Length 44>>
stream
BT
/F1 100 Tf
10 400 Td
(!Odoo Test!) Tj
ET
endstream
endobj
5 0 obj
<</Foo/Bar>>
endobj
xref
0 6
0000000000 00001 f
0000000016 00000 n
0000000074 00000 n
0000000126 00000 n
0000000254 00000 n
0000000346 00000 n
trailer
<</Size 6/Root 1 0 R>>
startxref
376
%%EOF
+75
View File
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
from odoo.tools import pdf
from odoo.modules.module import get_module_resource
import io
class TestPdf(TransactionCase):
""" Tests on pdf. """
def setUp(self):
super().setUp()
file_path = get_module_resource('base', 'tests', 'minimal.pdf')
self.file = open(file_path, 'rb').read()
self.minimal_reader_buffer = io.BytesIO(self.file)
self.minimal_pdf_reader = pdf.OdooPdfFileReader(self.minimal_reader_buffer)
def test_odoo_pdf_file_reader(self):
attachments = list(self.minimal_pdf_reader.getAttachments())
self.assertEqual(len(attachments), 0)
pdf_writer = pdf.PdfFileWriter()
pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
pdf_writer.addAttachment('test_attachment.txt', b'My awesome attachment')
attachments = list(self.minimal_pdf_reader.getAttachments())
self.assertEqual(len(attachments), 1)
def test_odoo_pdf_file_writer(self):
attachments = list(self.minimal_pdf_reader.getAttachments())
self.assertEqual(len(attachments), 0)
pdf_writer = pdf.OdooPdfFileWriter()
pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
pdf_writer.addAttachment('test_attachment.txt', b'My awesome attachment')
attachments = list(self.minimal_pdf_reader.getAttachments())
self.assertEqual(len(attachments), 1)
pdf_writer.addAttachment('another_attachment.txt', b'My awesome OTHER attachment')
attachments = list(self.minimal_pdf_reader.getAttachments())
self.assertEqual(len(attachments), 2)
def test_merge_pdf(self):
self.assertEqual(self.minimal_pdf_reader.getNumPages(), 1)
page = self.minimal_pdf_reader.getPage(0)
merged_pdf = pdf.merge_pdf([self.file, self.file])
merged_reader_buffer = io.BytesIO(merged_pdf)
merged_pdf_reader = pdf.OdooPdfFileReader(merged_reader_buffer)
self.assertEqual(merged_pdf_reader.getNumPages(), 2)
merged_reader_buffer.close()
def test_branded_file_writer(self):
# It's not easy to create a PDF with PyPDF2, so instead we copy minimal.pdf with our custom pdf writer
pdf_writer = pdf.PdfFileWriter() # BrandedFileWriter
pdf_writer.cloneReaderDocumentRoot(self.minimal_pdf_reader)
writer_buffer = io.BytesIO()
pdf_writer.write(writer_buffer)
branded_content = writer_buffer.getvalue()
writer_buffer.close()
# Read the metadata of the newly created pdf.
reader_buffer = io.BytesIO(branded_content)
pdf_reader = pdf.PdfFileReader(reader_buffer)
pdf_info = pdf_reader.getDocumentInfo()
self.assertEqual(pdf_info['/Producer'], 'Odoo')
self.assertEqual(pdf_info['/Creator'], 'Odoo')
reader_buffer.close()
def tearDown(self):
super().tearDown()
self.minimal_reader_buffer.close()
+15 -2
View File
@@ -12,6 +12,17 @@ import hashlib
DEFAULT_PDF_DATETIME_FORMAT = "D:%Y%m%d%H%M%S+00'00'"
# make sure values are unwrapped by calling the specialized __getitem__
def _unwrapping_get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
DictionaryObject.get = _unwrapping_get
class BrandedFileWriter(PdfFileWriter):
def __init__(self):
super().__init__()
@@ -25,7 +36,8 @@ PdfFileWriter = BrandedFileWriter
def merge_pdf(pdf_data):
''' Merge a collection of PDF documents in one
''' Merge a collection of PDF documents in one.
Note that the attachments are not merged.
:param list pdf_data: a list of PDF datastrings
:return: a unique merged PDF datastring
'''
@@ -40,7 +52,8 @@ def merge_pdf(pdf_data):
def rotate_pdf(pdf):
''' Rotate clockwise PDF (90°)
''' Rotate clockwise PDF (90°) into a new PDF.
Note that the attachments are not copied.
:param pdf: a PDF to rotate
:return: a PDF rotated
'''