mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
@@ -20,9 +20,10 @@ from modoboa.lib import u2u_decode
|
||||
class EmailAddress(object):
|
||||
|
||||
def __init__(self, address):
|
||||
self.fulladdress = u2u_decode.u2u_decode(address).strip("\r\t\n")
|
||||
(self.name, self.address) = parseaddr(self.fulladdress)
|
||||
if self.name == "":
|
||||
self.name, self.address = u2u_decode.decode_address(address)
|
||||
if self.name:
|
||||
self.fulladdress = "{} <{}>".format(self.name, self.address)
|
||||
else:
|
||||
self.fulladdress = self.address
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -14,6 +14,7 @@ from rest_framework.test import APITestCase
|
||||
from modoboa.core import models as core_models
|
||||
|
||||
from . import sysutils
|
||||
from . import u2u_decode
|
||||
|
||||
try:
|
||||
s = socket.create_connection(('127.0.0.1', 25))
|
||||
@@ -103,3 +104,46 @@ class ModoAPITestCase(ParametersMixin, APITestCase):
|
||||
"""Setup."""
|
||||
super(ModoAPITestCase, self).setUp()
|
||||
self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)
|
||||
|
||||
|
||||
class U2UTestCase(TestCase):
|
||||
"""Test RFC1342 decoding utilities."""
|
||||
|
||||
def test_header_decoding(self):
|
||||
"""Simple decoding."""
|
||||
samples = [
|
||||
("=?ISO-8859-15?Q?=20Profitez de tous les services en ligne sur "
|
||||
"impots.gouv.fr?=",
|
||||
"Profitez de tous les services en ligne sur impots.gouv.fr"),
|
||||
("=?ISO-8859-1?Q?Accus=E9?= de =?ISO-8859-1?Q?r=E9ception?= de "
|
||||
"votre annonce",
|
||||
"Accusé de réception de votre annonce"),
|
||||
("Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord",
|
||||
"Sm\xf6rg\xe5sbord")
|
||||
]
|
||||
for sample in samples:
|
||||
self.assertEqual(u2u_decode.u2u_decode(sample[0]), sample[1])
|
||||
|
||||
def test_address_header_decoding(self):
|
||||
"""Check address decoding."""
|
||||
mailsploit_sample = (
|
||||
"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?==?utf-8?Q?=00?="
|
||||
"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@mailsploit.com")
|
||||
expected_result = (
|
||||
"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?==?utf-8?Q??="
|
||||
"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?=@mailsploit.com")
|
||||
self.assertEqual(
|
||||
u2u_decode.decode_address(mailsploit_sample),
|
||||
("", expected_result)
|
||||
)
|
||||
mailsploit_sample = (
|
||||
'"=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?==?utf-8?Q?=0A=00?="\n'
|
||||
'<=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?==?utf-8?Q?=0A=00?='
|
||||
'@mailsploit.com>')
|
||||
expected_result = (
|
||||
'potus@whitehouse.gov',
|
||||
'=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3Y=?==?utf-8?Q??='
|
||||
'@mailsploit.com')
|
||||
self.assertEqual(
|
||||
u2u_decode.decode_address(mailsploit_sample),
|
||||
expected_result)
|
||||
|
||||
+38
-13
@@ -5,14 +5,17 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from email.header import decode_header, make_header
|
||||
from email.utils import parseaddr
|
||||
import re
|
||||
|
||||
from django.utils.encoding import smart_text
|
||||
|
||||
# check spaces between encoded_words (and strip them)
|
||||
sre = re.compile(r'\?=[ \t]+=\?')
|
||||
sre = re.compile(r"\?=[ \t]+=\?")
|
||||
# re pat for MIME encoded_word (without trailing spaces)
|
||||
mre = re.compile(r'=\?[^?]*?\?[bq]\?[^?\t]*?\?=', re.I)
|
||||
mre = re.compile(r"=\?[^?]*?\?[bq]\?[^?\t]*?\?=", re.I)
|
||||
# re do detect encoded ASCII characters
|
||||
ascii_re = re.compile(r"=[\dA-F]{2,3}", re.I)
|
||||
|
||||
|
||||
def clean_spaces(m):
|
||||
@@ -24,6 +27,14 @@ def clean_spaces(m):
|
||||
return m.group(0).replace(" ", "=20")
|
||||
|
||||
|
||||
def clean_non_printable_char(m):
|
||||
"""Strip non printable characters."""
|
||||
code = int(m.group(0)[1:], 16)
|
||||
if code < 20:
|
||||
return ""
|
||||
return m.group(0)
|
||||
|
||||
|
||||
def decode_mime(m):
|
||||
"""Substitute matching encoded_word with unicode equiv."""
|
||||
h = decode_header(clean_spaces(m))
|
||||
@@ -34,20 +45,34 @@ def decode_mime(m):
|
||||
return u
|
||||
|
||||
|
||||
def clean_header(header):
|
||||
"""Clean header function."""
|
||||
header = "".join(header.splitlines())
|
||||
header = sre.sub("?==?", header)
|
||||
return ascii_re.sub(clean_non_printable_char, header)
|
||||
|
||||
|
||||
def u2u_decode(s):
|
||||
"""utility function for (final) decoding of mime header
|
||||
|
||||
note: resulting string is in one line (no \n within)
|
||||
note2: spaces between enc_words are stripped (see RFC2047)
|
||||
|
||||
>>> u2u_decode('=?ISO-8859-15?Q?=20Profitez de tous les services en ligne sur impots.gouv.fr?=')
|
||||
u' Profitez de tous les services en ligne sur impots.gouv.fr'
|
||||
>>> u2u_decode('=?ISO-8859-1?Q?Accus=E9?= de =?ISO-8859-1?Q?r=E9ception?= de votre annonce')
|
||||
u'Accus\xe9 de r\xe9ception de votre annonce'
|
||||
>>> u2u_decode('Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord')
|
||||
u'Sm\xf6rg\xe5sbord'
|
||||
"""
|
||||
s = ''.join(s.splitlines())
|
||||
s = sre.sub('?==?', s)
|
||||
u = mre.sub(decode_mime, s)
|
||||
return u
|
||||
return mre.sub(decode_mime, clean_header(s)).strip(" \r\t\n")
|
||||
|
||||
|
||||
def decode_address(value):
|
||||
"""Special function for address decoding.
|
||||
|
||||
We need a dedicated processing because RFC1342 explicitely says
|
||||
address MUST NOT contain encoded-word:
|
||||
|
||||
These are the ONLY locations where an encoded-word may appear. In
|
||||
particular, an encoded-word MUST NOT appear in any portion of an
|
||||
"address". In addition, an encoded-word MUST NOT be used in a
|
||||
Received header field.
|
||||
"""
|
||||
phrase, address = parseaddr(clean_header(value))
|
||||
if phrase:
|
||||
phrase = mre.sub(decode_mime, phrase)
|
||||
return phrase, address
|
||||
|
||||
Reference in New Issue
Block a user