From bd52298073f054ae817990a840d570b23c1e1a25 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 9 Oct 2014 09:14:22 +0200 Subject: [PATCH 1/4] [IMP] mail: parsing emails with several html parts If an email contains several text/html parts inside a multipart email, the previous code was only keeping the last content part. The Content-Type: multipart/mixed allows several independent part (RFC1341 7.2.2), so two html is technically valid. With this patch, the two parts are concatenated. (opw 614755) Modify append_content_to_html regex to make sure the regex keeps the content of the html instead of removing it. e.g.: "123 456 789" used to be stripped to "123 789" while we expect "123 456 789" --- addons/mail/mail_thread.py | 9 ++++- addons/mail/tests/test_mail_gateway.py | 55 ++++++++++++++++++++++++++ openerp/tools/mail.py | 2 +- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 63cb3a845a1..ee039533af6 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -801,9 +801,13 @@ class mail_thread(osv.AbstractModel): body = tools.append_content_to_html(u'', body, preserve=True) else: alternative = False + mixed = False + html = u'' for part in message.walk(): if part.get_content_type() == 'multipart/alternative': alternative = True + if part.get_content_type() == 'multipart/mixed': + mixed = True if part.get_content_maintype() == 'multipart': continue # skip container # part.get_filename returns decoded value if able to decode, coded otherwise. @@ -830,8 +834,11 @@ class mail_thread(osv.AbstractModel): encoding, errors='replace'), preserve=True) # 3) text/html -> raw elif part.get_content_type() == 'text/html': + # mutlipart/alternative have one text and a html part, keep only the second + # mixed allows several html parts, append html content + append_content = not alternative or (html and mixed) html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace') - if alternative: + if not append_content: body = html else: body = tools.append_content_to_html(body, html, plaintext=False) diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py index bc2f207464c..c952c6ab076 100644 --- a/addons/mail/tests/test_mail_gateway.py +++ b/addons/mail/tests/test_mail_gateway.py @@ -141,6 +141,53 @@ X-Attachment-Id: f_hkpb27k00 dGVzdAo= --089e01536c4ed4d17204e49b8e96--""" +MAIL_MULTIPART_MIXED_TWO = """X-Original-To: raoul@grosbedon.fr +Delivered-To: raoul@grosbedon.fr +Received: by mail1.grosbedon.com (Postfix, from userid 10002) + id E8166BFACA; Fri, 23 Aug 2013 13:18:01 +0200 (CEST) +From: "Bruce Wayne" +Content-Type: multipart/alternative; + boundary="Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227" +Message-Id: <6BB1FAB2-2104-438E-9447-07AE2C8C4A92@sexample.com> +Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\)) + +--Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227 +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=us-ascii + +First and second part + +--Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227 +Content-Type: multipart/mixed; + boundary="Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F" + +--Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F +Content-Transfer-Encoding: 7bit +Content-Type: text/html; + charset=us-ascii + +First part + +--Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F +Content-Disposition: inline; + filename=thetruth.pdf +Content-Type: application/pdf; + name="thetruth.pdf" +Content-Transfer-Encoding: base64 + +SSBhbSB0aGUgQmF0TWFuCg== + +--Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F +Content-Transfer-Encoding: 7bit +Content-Type: text/html; + charset=us-ascii + +Second part +--Apple-Mail=_CA6C687E-6AA0-411E-B0FE-F0ABB4CFED1F-- + +--Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227-- +""" class TestMailgateway(TestMailBase): @@ -202,6 +249,14 @@ class TestMailgateway(TestMailBase): self.assertIn('
Should create a multipart/mixed: from gmail, bold, with attachment.

', res.get('body', ''), 'message_parse: html version should be in body after parsing multipart/mixed') + res = self.mail_thread.message_parse(cr, uid, MAIL_MULTIPART_MIXED_TWO) + self.assertNotIn('First and second part', res.get('body', ''), + 'message_parse: text version should not be in body after parsing multipart/mixed') + self.assertIn('First part', res.get('body', ''), + 'message_parse: first part of the html version should be in body after parsing multipart/mixed') + self.assertIn('Second part', res.get('body', ''), + 'message_parse: second part of the html version should be in body after parsing multipart/mixed') + def test_10_message_process(self): """ Testing incoming emails processing. """ cr, uid, user_raoul = self.cr, self.uid, self.user_raoul diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py index 49ec5849b18..b0212db11ae 100644 --- a/openerp/tools/mail.py +++ b/openerp/tools/mail.py @@ -282,7 +282,7 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai elif plaintext: content = '\n%s\n' % plaintext2html(content, container_tag) else: - content = re.sub(r'(?i)(||)', '', content) + content = re.sub(r'(?i)(]*>)', '', content) content = u'\n%s\n' % ustr(content) # Force all tags to lowercase html = re.sub(r'(])', From eeb1f0355cce763be6a6a00cb2fc96dd9a58bc76 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 9 Oct 2014 18:50:45 +0200 Subject: [PATCH 2/4] [FIX] res.users: exactly match login when operator in ilike, equal --- openerp/addons/base/res/res_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 909b14d5601..137b953eeb6 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -353,7 +353,7 @@ class res_users(osv.osv): if not context: context={} ids = [] - if name: + if name and operator in ['=', 'ilike']: ids = self.search(cr, user, [('login','=',name)]+ args, limit=limit, context=context) if not ids: ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context) From d38adf3d9247fc65d83ea57edfaa5fba7b82404c Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 9 Oct 2014 18:56:21 +0200 Subject: [PATCH 3/4] [FIX] account_invoice: using has_group to determine if user has group group_supplier_inv_check_total has_group do not care about the fact res.groups is access restricted --- addons/account/account_invoice.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 25e6cbae0ec..9390e2cf3fd 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -925,9 +925,7 @@ class account_invoice(osv.osv): self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj) # I disabled the check_total feature - group_check_total_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'group_supplier_inv_check_total')[1] - group_check_total = self.pool.get('res.groups').browse(cr, uid, group_check_total_id, context=context) - if group_check_total and uid in [x.id for x in group_check_total.users]: + if self.pool['res.users'].has_group(cr, uid, 'account.group_supplier_inv_check_total'): if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)): raise osv.except_osv(_('Bad Total!'), _('Please verify the price of the invoice!\nThe encoded total does not match the computed total.')) From 1b49a876196de1f4b6568ff1751f7c7c6b2d5002 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 10 Oct 2014 17:04:03 +0200 Subject: [PATCH 4/4] [FIX] ir.mail.server: restore parsing of multiple RFC2822 addresses including non-ASCII chars Rev f2cf6ced1 modified RFC2822 parsing in order to better support unicode characters inside the Name part of an address header. However the patch broke handling of multiple addresses (comma separated) - silently discarding all recipients except the first one, as soon as any non-ASCII character was present. This patch restores the functionality while preserving the fix from f2cf6ced1, and simplifies the code using email.utils utility functions. Fixes (again) lp:1272610, OPW 607683 --- openerp/addons/base/ir/ir_mail_server.py | 31 ++++++------------------ 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 7851d700133..2624abb8c5f 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -24,7 +24,7 @@ from email.MIMEBase import MIMEBase from email.MIMEMultipart import MIMEMultipart from email.Charset import Charset from email.Header import Header -from email.Utils import formatdate, make_msgid, COMMASPACE, parseaddr +from email.utils import formatdate, make_msgid, COMMASPACE, getaddresses, formataddr from email import Encoders import logging import re @@ -138,29 +138,14 @@ def encode_rfc2822_address_header(header_text): ``"Name"`` portion by the RFC2047-encoded version, preserving the address part untouched. """ - header_text_utf8 = tools.ustr(header_text).encode('utf-8') - header_text_ascii = try_coerce_ascii(header_text_utf8) - if header_text_ascii: - return header_text_ascii - - name, email = parseaddr(header_text_utf8) - if not name: - return email - - # non-ASCII characters are present, attempt to - # replace all "Name" patterns with the RFC2047- - # encoded version - name_encoded = str(Header(name, 'utf-8')) - header_text_utf8 = "%s <%s>" % (name_encoded, email) - # try again after encoding - header_text_ascii = try_coerce_ascii(header_text_utf8) - if header_text_ascii: - return header_text_ascii - # fallback to extracting pure addresses only, which could - # still cause a failure downstream if the actual addresses - # contain non-ASCII characters - return COMMASPACE.join(extract_rfc2822_addresses(header_text_utf8)) + def encode_addr(addr): + name, email = addr + if not try_coerce_ascii(name): + name = str(Header(name, 'utf-8')) + return formataddr((name, email)) + addresses = getaddresses([tools.ustr(header_text).encode('utf-8')]) + return COMMASPACE.join(map(encode_addr, addresses)) class ir_mail_server(osv.osv): """Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""