From b0067dfd72c1f5f16609d6877759511f643de361 Mon Sep 17 00:00:00 2001 From: "Andrzej(pian)" Date: Wed, 13 Dec 2023 13:07:18 +0100 Subject: [PATCH] [FIX] mail: mime type alias Some printers incorrectly specify the content-type of PDFs as "pdf" instead of the correct "application/pdf." To handle this situation, an alias has been introduced, treating content-type "pdf" as "application/pdf." [Reproducing original bug] 1. run odoo with documents module 2. prepare email file 2.1 create email with a pdf attached 2.2 export email to .eml 2.3 modify content-type of the pdf from "application/pdf" to "pdf" 3. send prepared email to odoo to inbox-financial alias (the default one creating documents) 4. Go to documents, observe that the created PDF is blank! [Motivation for fix] Since: 1. Odoo uses the Python built-in module for parsing emails* (the actual issue lies outside Odoo code). 2. "pdf" is not an accepted MIME type according to current norms (https://datatracker.ietf.org/doc/html/rfc6838#section-4.2). It's not an actual Odoo bug. However, redirecting this issue to the appropriate printer manufacturer and waiting for a fix may be time-consuming, hence the proposed fix. *function parsing email with the Python built-in module https://github.com/odoo/odoo/blob/407ea60796a2b18eb02f5569e1cfab9f1163a572/addons/mail/models/mail_thread.py#L1282 opw-3462260 closes odoo/odoo#147363 X-original-commit: e95a19341536e442239a7e940089d7f66ab9f534 Signed-off-by: Thibault Delavallee (tde) --- addons/mail/models/mail_thread.py | 4 ++++ addons/test_mail/data/test_mail_data.py | 20 ++++++++++++++++++++ addons/test_mail/tests/test_mail_gateway.py | 13 +++++++++++++ 3 files changed, 37 insertions(+) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 8b9c9de72d4..28e25a259c8 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1523,6 +1523,10 @@ class MailThread(models.AbstractModel): part.set_charset('utf-8') encoding = part.get_content_charset() # None if attachment + # Correcting MIME type for PDF files + if part.get('Content-Type', '').startswith('pdf;'): + part.replace_header('Content-Type', 'application/pdf' + part.get('Content-Type', '')[3:]) + content = part.get_content() info = {'encoding': encoding} # 0) Inline Attachments -> attachments, with a third part in the tuple to match cid / attachment diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py index c2e6fdb0454..6e6626a16a4 100644 --- a/addons/test_mail/data/test_mail_data.py +++ b/addons/test_mail/data/test_mail_data.py @@ -1292,3 +1292,23 @@ To: lucie@petitebedaine.fr From: "Bruce Wayne" SSBhbSB0aGUgQmF0TWFuCg==""" + +MAIL_PDF_MIME_TEMPLATE = """\ +To: {to} +From: {email_from} +Subject: {subject} +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_4200734_24778174.1344608186754" +Date: Fri, 10 Aug 2012 14:16:26 +0000 + +------=_Part_4200734_24778174.1344608186754 +Content-Type: {pdf_mime}; name="scan_soraya.lernout_1691652648.pdf" +Content-Transfer-Encoding: base64 + +JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg== + +------=_Part_4200734_24778174.1344608186754-- +""" + +PDF_PARSED = b'''%PDF-1.1\n%\xc2\xa5\xc2\xb1\xc3\xab\n\n1 0 obj\n << /Type /Catalog\n /Pages 2 0 R\n >>\nendobj\n\n2 0 obj\n << /Type /Pages\n /Kids [3 0 R]\n /Count 1\n /MediaBox [0 0 300 144]\n >>\nendobj\n\n3 0 obj\n << /Type /Page\n /Parent 2 0 R\n /Resources\n << /Font\n << /F1\n << /Type /Font\n /Subtype /Type1\n /BaseFont /Times-Roman\n >>\n >>\n >>\n /Contents 4 0 R\n >>\nendobj\n\n4 0 obj\n << /Length 55 >>\nstream\n BT\n /F1 18 Tf\n 0 0 Td\n (Hello World) Tj\n ET\nendstream\nendobj\n\nxref\n0 5\n0000000000 65535 f \n0000000018 00000 n \n0000000077 00000 n \n0000000178 00000 n \n0000000457 00000 n \ntrailer\n << /Root 1 0 R\n /Size 5\n >>\nstartxref\n565\n%%EOF\n''' diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index c5fc9377ffe..35040c2b10b 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -83,6 +83,19 @@ class TestEmailParsing(MailCommon): res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_MULTIPART_WEIRD_FILENAME)) self.assertEqual(res['attachments'][0][0], '62_@;,][)=.(ÇÀÉ.txt') + def test_message_parse_attachment_pdf_nonstandard_mime(self): + # This test checks if aliasing content-type (mime type) of "pdf" with "application/pdf" works correctly. (i.e. Treat "pdf" as "application/pdf") + + # Baseline check. Parsing mail with "application/pdf" + mail_with_standard_mime = self.format(test_mail_data.MAIL_PDF_MIME_TEMPLATE, pdf_mime="application/pdf") + res_std = self.env['mail.thread'].message_parse(self.from_string(mail_with_standard_mime)) + self.assertEqual(res_std['attachments'][0].content, test_mail_data.PDF_PARSED, "Attachment with Content-Type: application/pdf must parse without error") + + # Parsing the same email, but with content-type set to "pdf" + mail_with_aliased_mime = self.format(test_mail_data.MAIL_PDF_MIME_TEMPLATE, pdf_mime="pdf") + res_alias = self.env['mail.thread'].message_parse(self.from_string(mail_with_aliased_mime)) + self.assertEqual(res_alias['attachments'][0].content, test_mail_data.PDF_PARSED, "Attachment with aliased Content-Type: pdf must parse without error") + def test_message_parse_bugs(self): """ Various corner cases or message parsing """ # message without Final-Recipient