|
@@ -19,101 +19,102 @@ from tracim.controllers.events import VALID_TOKEN_VALUE
|
19
|
19
|
TRACIM_SPECIAL_KEY_HEADER = "X-Tracim-Key"
|
20
|
20
|
|
21
|
21
|
|
22
|
|
-def str_header(header: Header) -> str:
|
23
|
|
- return str(make_header(decode_header(header)))
|
24
|
|
-
|
25
|
|
-
|
26
|
|
-def decode_mail(msg: Message)-> dict:
|
27
|
|
- """
|
28
|
|
- Get useful header and body content and decode from Message
|
29
|
|
- :param msg:
|
30
|
|
- :return:
|
31
|
|
- """
|
32
|
|
- mail_data = {}
|
33
|
|
-
|
34
|
|
- try:
|
35
|
|
- mail_data['subject'] = str_header(msg['subject'])
|
36
|
|
- mail_data['from'] = parseaddr(msg['From'])[1]
|
37
|
|
- # Reply key
|
38
|
|
- mail_data['to'] = parseaddr(msg['To'])[1]
|
39
|
|
- # INFO - G.M - 2017-11-15
|
40
|
|
- # We only need to save the first/oldest addr of references
|
41
|
|
- mail_data['references'] = parseaddr(msg['References'])[1]
|
42
|
|
- if TRACIM_SPECIAL_KEY_HEADER in msg:
|
43
|
|
- mail_data[TRACIM_SPECIAL_KEY_HEADER] = str_header(msg[TRACIM_SPECIAL_KEY_HEADER]) # nopep8
|
44
|
|
-
|
45
|
|
- except Exception:
|
46
|
|
- # FIXME - G.M - 2017-11-15 - handle exceptions correctly
|
47
|
|
- return {}
|
48
|
|
-
|
49
|
|
- # TODO - G.M - 2017-11-15 - Parse properly HTML (and text ?) body
|
50
|
|
- body = get_body_mime_part(msg)
|
51
|
|
- if body:
|
52
|
|
- charset = body.get_content_charset('iso-8859-1')
|
53
|
|
- ctype = body.get_content_type()
|
54
|
|
- if ctype == "text/plain":
|
55
|
|
- mail_data['body'] = body.get_payload(decode=True).decode(charset)
|
56
|
|
- elif ctype == "text/html":
|
57
|
|
- mail_data['body'] = body.get_payload(decode=True).decode(charset)
|
|
22
|
+class DecodedMail(object):
|
|
23
|
+
|
|
24
|
+ def __init__(self, message: Message):
|
|
25
|
+ self._message = message
|
|
26
|
+
|
|
27
|
+ def _decode_header(self, header_title: str) -> typing.Optional[str]:
|
|
28
|
+ # FIXME : Handle exception
|
|
29
|
+ if header_title in self._message:
|
|
30
|
+ return str(make_header(decode_header(header)))
|
58
|
31
|
else:
|
59
|
|
- pass
|
60
|
|
- else:
|
61
|
|
- pass
|
62
|
|
-
|
63
|
|
- return mail_data
|
64
|
|
-
|
65
|
|
-
|
66
|
|
-def get_body_mime_part(msg) -> Message:
|
67
|
|
- # FIXME - G.M - 2017-11-16 - Use stdlib msg.get_body feature for py3.6+
|
68
|
|
- # FIXME - G.M - 2017-11-16 - Check support for non-multipart mail
|
69
|
|
- #assert msg.is_multipart()
|
70
|
|
- part = None
|
71
|
|
- # Check for html
|
72
|
|
- for part in msg.walk():
|
73
|
|
- ctype = part.get_content_type()
|
74
|
|
- cdispo = str(part.get('Content-Disposition'))
|
75
|
|
- if ctype == 'text/html' and 'attachment' not in cdispo:
|
76
|
|
- return part
|
77
|
|
- # checj fir plain text
|
78
|
|
- for part in msg.walk():
|
79
|
|
- ctype = part.get_content_type()
|
80
|
|
- cdispo = str(part.get('Content-Disposition'))
|
81
|
|
- if ctype == 'text/plain' and 'attachment' not in cdispo:
|
82
|
|
- return part
|
83
|
|
- return part
|
84
|
|
-
|
85
|
|
-
|
86
|
|
-def get_tracim_content_key(mail_data: dict) -> typing.Optional[str]:
|
87
|
|
-
|
88
|
|
- """ Link mail_data dict to tracim content
|
89
|
|
- First try checking special header, them check 'to' header
|
90
|
|
- and finally check first(oldest) mail-id of 'references' header
|
91
|
|
- """
|
92
|
|
- key = None
|
93
|
|
- if TRACIM_SPECIAL_KEY_HEADER in mail_data:
|
94
|
|
- key = mail_data[TRACIM_SPECIAL_KEY_HEADER]
|
95
|
|
- if key is None and 'to' in mail_data:
|
96
|
|
- key = find_key_from_mail_adress(mail_data['to'])
|
97
|
|
- if key is None and 'references' in mail_data:
|
98
|
|
- mail_adress = mail_data['references']
|
99
|
|
- key = find_key_from_mail_adress(mail_adress)
|
100
|
|
- return key
|
101
|
|
-
|
102
|
|
-
|
103
|
|
-def find_key_from_mail_adress(mail_address: str) -> typing.Optional[str]:
|
104
|
|
- """ Parse mail_adress-like string
|
105
|
|
- to retrieve key.
|
106
|
|
-
|
107
|
|
- :param mail_address: user+key@something like string
|
108
|
|
- :return: key
|
109
|
|
- """
|
110
|
|
- username = mail_address.split('@')[0]
|
111
|
|
- username_data = username.split('+')
|
112
|
|
- if len(username_data) == 2:
|
113
|
|
- key = username_data[1]
|
114
|
|
- else:
|
|
32
|
+ return None
|
|
33
|
+
|
|
34
|
+ def get_subject(self) -> typing.Optional[str]:
|
|
35
|
+ return self._decode_header('subject')
|
|
36
|
+
|
|
37
|
+ def get_from_address(self) -> typing.Optional[str]:
|
|
38
|
+ return parseaddr(self._message['From'])[1]
|
|
39
|
+
|
|
40
|
+ def get_to_address(self)-> typing.Optional[str]:
|
|
41
|
+ return parseaddr(self._message['To'])[1]
|
|
42
|
+
|
|
43
|
+ def get_first_ref(self) -> typing.Optional[str]:
|
|
44
|
+ return parseaddr(self._message['References'])[1]
|
|
45
|
+
|
|
46
|
+ def get_special_key(self) -> typing.Optional[str]:
|
|
47
|
+ return self._decode_header(TRACIM_SPECIAL_KEY_HEADER)
|
|
48
|
+
|
|
49
|
+ def get_body(self) -> typing.Optional[str]:
|
|
50
|
+ body_part = self._get_mime_body_message()
|
|
51
|
+ body = None
|
|
52
|
+ if body_part:
|
|
53
|
+ charset = body_part.get_content_charset('iso-8859-1')
|
|
54
|
+ ctype = body_part.get_content_type()
|
|
55
|
+ if ctype == "text/plain":
|
|
56
|
+ body = body_part.get_payload(decode=True).decode(
|
|
57
|
+ charset)
|
|
58
|
+ elif ctype == "text/html":
|
|
59
|
+ body = body_part.get_payload(decode=True).decode(
|
|
60
|
+ charset)
|
|
61
|
+ return body
|
|
62
|
+
|
|
63
|
+ def _get_mime_body_message(self) -> typing.Optional[Message]:
|
|
64
|
+ # FIXME - G.M - 2017-11-16 - Use stdlib msg.get_body feature for py3.6+
|
|
65
|
+ # FIXME - G.M - 2017-11-16 - Check support for non-multipart mail
|
|
66
|
+ # assert msg.is_multipart()
|
|
67
|
+ part = None
|
|
68
|
+ # Check for html
|
|
69
|
+ for part in self._message.walk():
|
|
70
|
+ ctype = part.get_content_type()
|
|
71
|
+ cdispo = str(part.get('Content-Disposition'))
|
|
72
|
+ if ctype == 'text/html' and 'attachment' not in cdispo:
|
|
73
|
+ return part
|
|
74
|
+ # checj fir plain text
|
|
75
|
+ for part in self._message.walk():
|
|
76
|
+ ctype = part.get_content_type()
|
|
77
|
+ cdispo = str(part.get('Content-Disposition'))
|
|
78
|
+ if ctype == 'text/plain' and 'attachment' not in cdispo:
|
|
79
|
+ return part
|
|
80
|
+ return part
|
|
81
|
+
|
|
82
|
+ def get_key(self) -> typing.Optional[str]:
|
|
83
|
+
|
|
84
|
+ """
|
|
85
|
+ First try checking special header, them check 'to' header
|
|
86
|
+ and finally check first(oldest) mail-id of 'references' header
|
|
87
|
+ """
|
115
|
88
|
key = None
|
116
|
|
- return key
|
|
89
|
+ first_ref = self.get_first_ref()
|
|
90
|
+ to_address = self.get_to_address()
|
|
91
|
+ special_key = self.get_special_key()
|
|
92
|
+
|
|
93
|
+ if special_key:
|
|
94
|
+ key = special_key
|
|
95
|
+ if not key and to_address:
|
|
96
|
+ key = DecodedMail.find_key_from_mail_address(to_address)
|
|
97
|
+ if not key and first_ref:
|
|
98
|
+ key = DecodedMail.find_key_from_mail_address(first_ref)
|
|
99
|
+
|
|
100
|
+ return key
|
|
101
|
+
|
|
102
|
+ @staticmethod
|
|
103
|
+ def find_key_from_mail_address(mail_address: str) \
|
|
104
|
+ -> typing.Optional[str]:
|
|
105
|
+ """ Parse mail_adress-like string
|
|
106
|
+ to retrieve key.
|
|
107
|
+
|
|
108
|
+ :param mail_address: user+key@something like string
|
|
109
|
+ :return: key
|
|
110
|
+ """
|
|
111
|
+ username = mail_address.split('@')[0]
|
|
112
|
+ username_data = username.split('+')
|
|
113
|
+ if len(username_data) == 2:
|
|
114
|
+ key = username_data[1]
|
|
115
|
+ else:
|
|
116
|
+ key = None
|
|
117
|
+ return key
|
117
|
118
|
|
118
|
119
|
|
119
|
120
|
class MailFetcher(object):
|
|
@@ -196,7 +197,8 @@ class MailFetcher(object):
|
196
|
197
|
rv, data = self._connection.fetch(num, '(RFC822)')
|
197
|
198
|
if rv == 'OK':
|
198
|
199
|
msg = message_from_bytes(data[0][1])
|
199
|
|
- self._mails.append(msg)
|
|
200
|
+ decodedmsg = DecodedMail(msg)
|
|
201
|
+ self._mails.append(decodedmsg)
|
200
|
202
|
else:
|
201
|
203
|
log = 'IMAP : Unable to get mail : {}'
|
202
|
204
|
logger.debug(self, log.format(str(rv)))
|
|
@@ -210,12 +212,11 @@ class MailFetcher(object):
|
210
|
212
|
def _notify_tracim(self) -> None:
|
211
|
213
|
while self._mails:
|
212
|
214
|
mail = self._mails.pop()
|
213
|
|
- decoded_mail = decode_mail(mail)
|
214
|
215
|
msg = {"token": VALID_TOKEN_VALUE,
|
215
|
|
- "user_mail": decoded_mail['from'],
|
216
|
|
- "content_id": get_tracim_content_key(decoded_mail),
|
|
216
|
+ "user_mail": mail.get_from_address(),
|
|
217
|
+ "content_id": mail.get_key(),
|
217
|
218
|
"payload": {
|
218
|
|
- "content": decoded_mail['body'],
|
|
219
|
+ "content": mail.get_body(),
|
219
|
220
|
}}
|
220
|
221
|
# FIXME - G.M - 2017-11-15 - Catch exception from http request
|
221
|
222
|
requests.post(self.endpoint, json=msg)
|