def test_decode_smtp_header_email(self): cases = [ # In == Out for trivial ASCII cases ('Joe Doe <*****@*****.**>', 'Joe Doe <*****@*****.**>'), ('Joe <*****@*****.**>, Mike <*****@*****.**>', 'Joe <*****@*****.**>, Mike <*****@*****.**>'), # Same thing, but RFC822 quoted-strings must be preserved ('"Doe, Joe" <*****@*****.**>', '"Doe, Joe" <*****@*****.**>'), ('"Doe, Joe" <*****@*****.**>, "Foo, Mike" <*****@*****.**>', '"Doe, Joe" <*****@*****.**>, "Foo, Mike" <*****@*****.**>'), # RFC2047-encoded words have to be quoted after decoding, because # they are considered RFC822 `words` i.e. atom or quoted-string only! # It's ok to quote a single word even if unnecessary. # Example values produced by `formataddr((name, address), 'ascii')` ("=?utf-8?b?Sm/DqQ==?= <*****@*****.**>", '"Joé" <*****@*****.**>'), ("=?utf-8?b?Sm/DqQ==?= <*****@*****.**>, =?utf-8?b?RsO2w7YsIE1pa2U=?= <*****@*****.**>", '"Joé" <*****@*****.**>, "Föö, Mike" <*****@*****.**>'), ('=?utf-8?b?RG/DqSwg?= =?US-ASCII?Q?Joe?= <*****@*****.**>', '"Doé, ""Joe" <*****@*****.**>'), # Double-quotes may appear in the encoded form and /must/ be turned # into a RFC2822 quoted-pair (i.e. escaped) # "Trevor \"Banana\" Dumoulin" <*****@*****.**> ('=?utf-8?b?VHLDqXZvciAiQmFuYW5hIiBEdW1vdWxpbg==?= <*****@*****.**>', '"Trévor \\"Banana\\" Dumoulin" <*****@*****.**>'), ] for test, truth in cases: self.assertEqual(decode_smtp_header(test, quoted=True), truth)
def _attachment_invoice(self, msg_txt): body, attachments = self.env['mail.thread']._message_extract_payload(msg_txt) from_address = tools.decode_smtp_header(msg_txt.get('from')) for attachment in attachments: split_attachment = attachment.fname.rpartition('.') if len(split_attachment) < 3: _logger.info('E-invoice filename not compliant: %s', attachment.fname) continue attachment_name = split_attachment[0] attachment_ext = split_attachment[2] split_underscore = attachment_name.rsplit('_', 2) if len(split_underscore) < 2: _logger.info('E-invoice filename not compliant: %s', attachment.fname) continue if attachment_ext != 'zip': if split_underscore[1] in ['RC', 'NS', 'MC', 'MT', 'EC', 'SE', 'NE', 'DT']: # we have a receipt self._message_receipt_invoice(split_underscore[1], attachment) elif re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.fname): # we have a new E-invoice self._create_invoice_from_mail(attachment.content, attachment.fname, from_address) else: if split_underscore[1] == 'AT': # Attestazione di avvenuta trasmissione della fattura con impossibilità di recapito self._message_AT_invoice(attachment) else: _logger.info('New E-invoice in zip file: %s', attachment.fname) self._create_invoice_from_mail_with_zip(attachment, from_address)
def message_route_process(self, message, message_dict, routes): """ Override to update the parent mail statistics. The parent is found by using the References header of the incoming message and looking for matching message_id in mail.mail.statistics. """ if message.get('References'): message_ids = [x.strip() for x in decode_smtp_header(message['References']).split()] self.env['mail.mail.statistics'].set_replied(mail_message_ids=message_ids) return super(MailThread, self).message_route_process(message, message_dict, routes)
def message_parse(self, message, save_original=False): msg_dict = super().message_parse(message, save_original) if not isinstance(message, Message): # message_from_string works on a native str message = pycompat.to_native(message) message = email.message_from_string(message) msg_dict['reply_to'] = tools.decode_smtp_header(message.get('Reply-To')) return msg_dict
def message_parse(self, message, save_original=False): msg_dict = super(MailThread, self)\ .message_parse(message, save_original) original_from = tools.decode_smtp_header( message.get('X-Original-From')) if original_from: msg_dict['email_from'] = original_from msg_dict['from'] = original_from return msg_dict
def message_route_process(self, message, message_dict, routes): """ Override to update the parent mail statistics. The parent is found by using the References header of the incoming message and looking for matching message_id in mail.mail.statistics. """ if routes: references_msg_id_list = [] if message.get('References'): references_msg_id_list = [ x.strip() for x in tools.mail_header_msgid_re.findall( tools.decode_smtp_header(message['References'])) ] elif message.get('In-Reply-To'): references_msg_id_list = [ tools.decode_smtp_header(message['In-Reply-To'].strip()) ] if references_msg_id_list: self.env['mail.mail.statistics'].set_opened( mail_message_ids=references_msg_id_list) self.env['mail.mail.statistics'].set_replied( mail_message_ids=references_msg_id_list) return super(MailThread, self).message_route_process(message, message_dict, routes)
def _message_extract_payload(self, message, save_original=False): body, attachments = super()._message_extract_payload( message, save_original) if body: # Only remove signature for an e-mail coming from our domain email_from = tools.decode_smtp_header(message.get('from'), quoted=True) email_from = parseaddr(email_from)[1] email_domain = email_from.partition('@')[2] catchall_domain = self.env['ir.config_parameter'].sudo().get_param( "mail.catchall.domain") if email_domain == catchall_domain: body = self._remove_gmail_signatures(body) return body, attachments
def fetch_mail(self): for server in self: count, failed = 0, 0 pop_server = None if server.type == 'pop': _logger.info('Server tpye is POP') try: while True: pop_server = server.connect() (num_messages, total_size) = pop_server.stat() pop_server.list() _logger.info('Server tpye is POP inside while') _logger.info('total_size = %d', total_size) _logger.info('num_messages = %d', num_messages) for num in range( 1, min(MAX_POP_MESSAGES, num_messages) + 1): _logger.info( 'Server tpye is POP inside while INSIDE FOR') (header, messages, octets) = pop_server.retr(num) message = (b'\n').join(messages) res_id = None response = { 'errorCode': 100, 'message': 'File Uploaded Successfully' } try: if isinstance(message, xmlrpclib.Binary): message = bytes(message.data) if isinstance(message, pycompat.text_type): message = message.encode('utf-8') extract = getattr(email, 'message_from_bytes', email.message_from_string) message = extract(message) if not isinstance(message, Message): message = pycompat.to_native(message) message = email.message_from_string( message) email_to = tools.decode_message_header( message, 'To') match = re.search(r'[\w\.-]+@[\w\.-]+', email_to) email_to = str(match.group(0)) _logger.info('Email to %r', email_to) # if email_to == INCOMING_EMAIL_ID: _Attachment = namedtuple( 'Attachment', ('fname', 'content', 'info')) attachments = [] body = u'' email_from = tools.decode_message_header( message, 'From') _logger.info('Email from %r', email_from) match = re.search(r'[\w\.-]+@[\w\.-]+', email_from) email_from = str(match.group(0)) subject = tools.decode_message_header( message, 'Subject') tmpl_type = None if 'Inventory' in subject: tmpl_type = "Inventory" elif 'Requirement' in subject: tmpl_type = "Requirement" if message.get_content_maintype() != 'text': alternative = False for part in message.walk(): if part.get_content_type( ) == 'multipart/alternative': alternative = True if part.get_content_maintype( ) == 'multipart': continue # skip container filename = part.get_param( 'filename', None, 'content-disposition') if not filename: filename = part.get_param( 'name', None) if filename: if isinstance(filename, tuple): filename = email.utils.collapse_rfc2231_value( filename).strip() else: filename = tools.decode_smtp_header( filename) encoding = part.get_content_charset() if filename and part.get('content-id'): inner_cid = part.get( 'content-id').strip('><') attachments.append( _Attachment( filename, part.get_payload( decode=True), {'cid': inner_cid})) continue if filename or part.get( 'content-disposition', '' ).strip().startswith('attachment'): attachments.append( _Attachment( filename or 'attachment', part.get_payload( decode=True), {})) continue if part.get_content_type( ) == 'text/plain' and (not alternative or not body): body = tools.append_content_to_html( body, tools.ustr(part.get_payload( decode=True), encoding, errors='replace'), preserve=True) elif part.get_content_type( ) == 'text/html': body = tools.ustr( part.get_payload(decode=True), encoding, errors='replace') else: attachments.append( _Attachment( filename or 'attachment', part.get_payload( decode=True), {})) if len(attachments) > 0: encoding = message.get_content_charset( ) plain_text = html2text.HTML2Text() message_payload = plain_text.handle( tools.ustr(body, encoding, errors='replace')) if '- Forwarded message -' in message_payload: messages = message_payload.split( '- Forwarded message -') _logger.info( 'Forwarded message payload: %r', messages) total_parts = len(messages) originator_part = messages[ total_parts - 1] _logger.info( 'originator_part: %r', originator_part) match = re.search( r'[\w\.-]+@[\w\.-]+', originator_part) _logger.info('match: %r', match) if match: email_from_domain = re.search( "@[\w.]+", email_from).group(0) _logger.info( 'email_from_domain: %r', email_from_domain) email_to_domain = re.search( "@[\w.]+", email_to).group(0) _logger.info( 'email_to_domain: %r', email_to_domain) if email_to_domain != email_from_domain: email_from = None else: email_from = str( match.group(0)) _logger.info( 'email_to_domain email_from: %r', email_from) #_logger.info('message payload: %r %r', message_payload, email_from) if not email_from is None: users_model = self.env[ 'res.partner'].search([ ("email", "=", email_from) ]) if users_model: if len(users_model) == 1: user_attachment_dir = ATTACHMENT_DIR + str( datetime.now( ).strftime("%d%m%Y") ) + "/" + str( users_model.id) + "/" if not os.path.exists( os.path.dirname( user_attachment_dir )): try: os.makedirs( os.path. dirname( user_attachment_dir )) except OSError as exc: if exc.errno != errno.EEXIST: raise for attachment in attachments: filename = getattr( attachment, 'fname') if not filename is None: try: file_contents_bytes = getattr( attachment, 'content') file_path = user_attachment_dir + str( filename) file_ref = open( str(file_path ), "wb+") file_ref.write( file_contents_bytes ) file_ref.close( ) response = self.env[ 'sps.document.process'].process_document( users_model, file_path, tmpl_type, filename, 'Email' ) except Exception as e: _logger.info( str(e)) else: _logger.error( 'Presents Same Email Id for multiple users %r', email_from) response = dict( errorCode=101, message= 'Presents Same Email Id for multiple users : ' + str(email_from)) else: _logger.info( 'user not found for %r', email_from) response = dict( errorCode=102, message= 'User not found for : ' + str(email_from)) else: _logger.info( 'domain not matched for forwarded email' ) response = dict( errorCode=103, message= 'Domain not matched for forwarded email : ' + str(email_from)) else: _logger.info("No attachements found") response = dict( errorCode=104, message='No attachements found : ' + str(email_from)) else: _logger.info('Not a Multipart email') response = dict( errorCode=105, message='Not a Multipart email' + str(email_from)) pop_server.dele(num) if "errorCode" in response: self.send_mail( "Sending Email Response as " + str(response['message']) + " for user " + str(email_from)) except Exception: _logger.info( 'Failed to process mail from %s server %s.', server.type, server.name, exc_info=True) failed += 1 if res_id and server.action_id: server.action_id.with_context({ 'active_id': res_id, 'active_ids': [res_id], 'active_model': self.env.context.get( "thread_model", server.object_id.model) }).run() self.env.cr.commit() _logger.info('num_messages = %d', num_messages) if num_messages < MAX_POP_MESSAGES: break pop_server.quit() _logger.info( "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", num_messages, server.type, server.name, (num_messages - failed), failed) except Exception: _logger.info( "General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True) finally: _logger.info('Server tpye is POP inside finally') if pop_server: pop_server.quit() server.write({'date': fields.Datetime.now()}) return super(IncomingMailCronModel, self).fetch_mail()