2
2
from __future__ import absolute_import , unicode_literals
3
3
4
4
import email
5
- from email .encoders import encode_base64
6
5
from email .header import decode_header , Header
7
6
from email .mime .application import MIMEApplication
8
7
from email .mime .multipart import MIMEMultipart
9
8
from email .mime .text import MIMEText
10
9
11
10
from html2text import html2text
11
+ from six import PY2
12
12
13
13
import re
14
14
@@ -74,6 +74,50 @@ def parse(raw_message):
74
74
mail .email = email .message_from_string (raw_message )
75
75
return mail
76
76
77
+ @staticmethod
78
+ def fix_header_name (header_name ):
79
+ """
80
+ Fix header names according to RFC 4021:
81
+ https://tools.ietf.org/html/rfc4021#section-2.1.5
82
+ :param header_name: Name of the header to fix
83
+ :type header_name: str
84
+ :return: Fixed name of the header
85
+ :rtype: str
86
+ """
87
+ headers = [
88
+ 'Date' , 'From' , 'Sender' , 'Reply-To' , 'To' , 'Cc' , 'Bcc' ,
89
+ 'Message-ID' , 'In-Reply-To' , 'References' , 'Subject' , 'Comments' ,
90
+ 'Keywords' , 'Resent-Date' , 'Resent-From' , 'Resent-Sender' ,
91
+ 'Resent-To' , 'Resent-Cc' , 'Resent-Bcc' , 'Resent-Reply-To' ,
92
+ 'Resent-Message-ID' , 'Return-Path' , 'Received' , 'Encrypted' ,
93
+ 'Disposition-Notification-To' , 'Disposition-Notification-Options' ,
94
+ 'Accept-Language' , 'Original-Message-ID' , 'PICS-Label' , 'Encoding' ,
95
+ 'List-Archive' , 'List-Help' , 'List-ID' , 'List-Owner' , 'List-Post' ,
96
+ 'List-Subscribe' , 'List-Unsubscribe' , 'Message-Context' ,
97
+ 'DL-Expansion-History' , 'Alternate-Recipient' ,
98
+ 'Original-Encoded-Information-Types' , 'Content-Return' ,
99
+ 'Generate-Delivery-Report' , 'Prevent-NonDelivery-Report' ,
100
+ 'Obsoletes' , 'Supersedes' , 'Content-Identifier' , 'Delivery-Date' ,
101
+ 'Expiry-Date' , 'Expires' , 'Reply-By' , 'Importance' ,
102
+ 'Incomplete-Copy' , 'Priority' , 'Sensitivity' , 'Language' ,
103
+ 'Conversion' , 'Conversion-With-Loss' , 'Message-Type' ,
104
+ 'Autosubmitted' , 'Autoforwarded' , 'Discarded-X400-IPMS-Extensions' ,
105
+ 'Discarded-X400-MTS-Extensions' , 'Disclose-Recipients' ,
106
+ 'Deferred-Delivery' , 'Latest-Delivery-Time' ,
107
+ 'Originator-Return-Address' , 'X400-Content-Identifier' ,
108
+ 'X400-Content-Return' , 'X400-Content-Type' , 'X400-MTS-Identifier' ,
109
+ 'X400-Originator' , 'X400-Received' , 'X400-Recipients' , 'X400-Trace' ,
110
+ 'MIME-Version' , 'Content-ID' , 'Content-Description' ,
111
+ 'Content-Transfer-Encoding' , 'Content-Type' , 'Content-Base' ,
112
+ 'Content-Location' , 'Content-features' , 'Content-Disposition' ,
113
+ 'Content-Language' , 'Content-Alternative' , 'Content-MD5' ,
114
+ 'Content-Duration' ,
115
+ ]
116
+ for header in headers :
117
+ if header_name .lower () == header .lower ():
118
+ return header
119
+ return ''
120
+
77
121
def header (self , header , default = None ):
78
122
"""
79
123
Get the email Header always in Unicode
@@ -88,10 +132,11 @@ def header(self, header, default=None):
88
132
for part in decode_header (header_value ):
89
133
if part [1 ]:
90
134
result .append (part [0 ].decode (part [1 ]))
135
+ elif isinstance (part [0 ], bytes ):
136
+ result .append (part [0 ].decode ('utf-8' ))
91
137
else :
92
138
result .append (part [0 ])
93
139
header_value = '' .join (result )
94
-
95
140
return header_value
96
141
97
142
def add_header (self , header , value ):
@@ -108,9 +153,43 @@ def add_header(self, header, value):
108
153
if not (header and value ):
109
154
raise ValueError ('Header not provided!' )
110
155
recipients_headers = ['to' , 'cc' , 'bcc' ]
111
- if isinstance (value , list ) and header .lower () in recipients_headers :
112
- value = ',' .join (value )
113
- header_value = Header (value , charset = 'utf-8' ).encode ()
156
+ if header .lower () in recipients_headers or header .lower () == 'from' :
157
+ if not isinstance (value , list ):
158
+ value = [value ]
159
+ header_value = []
160
+ for addr in value :
161
+ # For each address in the recipients headers
162
+ # Do the Header Object
163
+ # PY3 works fine with Header(values, charset='utf-8')
164
+ # PY2:
165
+ # - Does not escape correctly the unicode values
166
+ # - Must encode the display name as a HEADER
167
+ # so the item is encoded properly
168
+ # - The encoded display name and the address are joined
169
+ # into the Header of the email
170
+ mail_addr = address .parse (addr )
171
+ display_name = Header (
172
+ mail_addr .display_name , charset = 'utf-8' ).encode ()
173
+ if display_name :
174
+ # decode_header method in PY2 does not look for closed items
175
+ # so a ' ' separator is required between items of a Header
176
+ if PY2 :
177
+ base_addr = '{} <{}>'
178
+ else :
179
+ base_addr = '{}<{}>'
180
+ header_value .append (
181
+ base_addr .format (
182
+ display_name ,
183
+ mail_addr .address
184
+ ).strip ()
185
+ )
186
+ else :
187
+ header_value .append (mail_addr .address )
188
+ header_value = ',' .join (header_value )
189
+ else :
190
+ header_value = Header (value , charset = 'utf-8' ).encode ()
191
+ # Get correct header name or add the one provided if custom header key
192
+ header = Email .fix_header_name (header ) or header
114
193
self .email [header ] = header_value
115
194
return header_value
116
195
@@ -198,7 +277,7 @@ def is_reply(self):
198
277
"""
199
278
return (not self .is_forwarded and (
200
279
bool (self .header ('In-Reply-To' ))
201
- or bool (re .match (RE_PATTERNS , self .header ('Subject' , '' )))
280
+ or bool (re .match (RE_PATTERNS , self .header ('Subject' , '' )))
202
281
))
203
282
204
283
@property
0 commit comments