Exemple #1
0
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):
        """ Override to udpate mass mailing statistics based on bounce emails """
        bounce_alias = self.env['ir.config_parameter'].get_param(
            "mail.bounce.alias")
        email_to = decode_message_header(message, 'To')
        email_to_localpart = (tools.email_split(email_to)
                              or [''])[0].split('@', 1)[0].lower()

        if bounce_alias and bounce_alias in email_to_localpart:
            bounce_re = re.compile(
                "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias),
                re.UNICODE)
            bounce_match = bounce_re.search(email_to)
            if bounce_match:
                bounced_mail_id = bounce_match.group(1)
                self.env['mail.mail.statistics'].set_bounced(
                    mail_mail_ids=[bounced_mail_id])

        return super(MailThread,
                     self).message_route(message, message_dict, model,
                                         thread_id, custom_values)
Exemple #2
0
    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 = tools.decode_message_header(message, 'References')
            in_reply_to = tools.decode_message_header(message, 'In-Reply-To').strip()
            thread_references = references or in_reply_to
            # even if 'reply_to' in ref (cfr mail/mail_thread) that indicates a new thread redirection
            # (aka bypass alias configuration in gateway) consider it as a reply for statistics purpose
            references_msg_id_list = tools.mail_header_msgid_re.findall(thread_references)

            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)
Exemple #3
0
    def message_route_verify(self, message, message_dict, route,
                             update_author=True, assert_model=True,
                             create_fallback=True, allow_private=False,
                             drop_alias=False):
        res = super(MailThread, self).message_route_verify(
            message, message_dict, route,
            update_author=update_author,
            assert_model=assert_model,
            create_fallback=create_fallback,
            allow_private=allow_private,
            drop_alias=drop_alias)

        if res:
            alias = route[4]
            email_from = decode_message_header(message, 'From')
            message_id = message.get('Message-Id')

            # Alias: check alias_contact settings for employees
            if alias and alias.alias_contact == 'employees':
                email_address = email_split(email_from)[0]
                employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
                if not employee:
                    employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
                if not employee:
                    mail_template = self.env.ref('hr.mail_template_data_unknown_employee_email_address')
                    self._routing_warn(_('alias %s does not accept unknown employees') % alias.alias_name, _('skipping'), message_id, route, False)
                    self._routing_create_bounce_email(email_from, mail_template.body_html, message)
                    return False
        return res
Exemple #4
0
    def message_route_process(self, message, message_dict, routes):
        rcpt_tos = ','.join([
            tools.decode_message_header(message, 'Delivered-To'),
            tools.decode_message_header(message, 'To'),
            tools.decode_message_header(message, 'Cc'),
            tools.decode_message_header(message, 'Resent-To'),
            tools.decode_message_header(message, 'Resent-Cc')
        ])
        rcpt_tos_websiteparts = [
            e.split('@')[1].lower() for e in tools.email_split(rcpt_tos)
        ]
        website = self.env['website'].sudo().search([('domain', 'in',
                                                      rcpt_tos_websiteparts)])
        if website:
            self = self.with_context(website_id=website[0].id)

        return super(MailThread,
                     self).message_route_process(message, message_dict, routes)
Exemple #5
0
    def notify_bounce_partners(self, message, fetchmail, message_dict):
        message_id = message.get('Message-Id')
        email_from = tools.decode_message_header(message, 'From')
        email_from_localpart = ((tools.email_split(email_from)
                                 or [''])[0].split('@', 1)[0].lower())

        # same criteria used by odoo
        # see addons/mail/models/mail_thread.py
        if (message.get_content_type() == 'multipart/report'
                or email_from_localpart == 'mailer-daemon'):

            references = tools.decode_message_header(message, 'References')
            in_reply_to = tools.decode_message_header(message,
                                                      'In-Reply-To').strip()
            thread_references = references or in_reply_to
            msg_references = [
                ref for ref in tools.mail_header_msgid_re.findall(
                    thread_references) if 'reply_to' not in ref
            ]
            MailMessage = self.env['mail.message']
            mail_messages = MailMessage.sudo().search(
                [('message_id', 'in', msg_references)], limit=1)
            recipients = mail_messages.mapped('author_id')
            recipients |= fetchmail.bounce_notify_partner_ids

            if not recipients:
                _logger.info(
                    'Not notifying bounce email from %s with Message-Id %s: '
                    'no recipients found', email_from, message_id)
                return

            _logger.info('Notifying bounce email from %s with Message-Id %s',
                         email_from, message_id)
            email = self.env['mail.mail'].create({
                'body_html':
                (u"%s<br/><br/><br/>%s<br/><br/>%s" %
                 (ustr(message_dict['body']), _("Raw message:"),
                  ustr(message.__str__()).replace("\n", "<br/>"))),
                'subject':
                message_dict['subject'],
                'recipient_ids': [(6, 0, [p.id for p in recipients])]
            })
            email.send()
Exemple #6
0
    def message_route_process(self, message, message_dict, routes):
        rcpt_tos = ",".join([
            tools.decode_message_header(message, "Delivered-To"),
            tools.decode_message_header(message, "To"),
            tools.decode_message_header(message, "Cc"),
            tools.decode_message_header(message, "Resent-To"),
            tools.decode_message_header(message, "Resent-Cc"),
        ])
        rcpt_tos_websiteparts = [
            e.split("@")[1].lower() for e in tools.email_split(rcpt_tos)
        ]
        website = (self.env["website"].sudo().search([
            ("domain", "in", rcpt_tos_websiteparts)
        ]))
        if website:
            self = self.with_context(website_id=website[0].id)

        return super(MailThread,
                     self).message_route_process(message, message_dict, routes)
 def _alias_get_error_message(self, message, message_dict, alias):
     if alias.alias_contact == 'employees':
         email_from = tools.decode_message_header(message, 'From')
         email_address = tools.email_split(email_from)[0]
         employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return _('restricted to employees')
         return False
     return super(BaseModel, self)._alias_get_error_message(message, message_dict, alias)
Exemple #8
0
 def _alias_check_contact_on_record(self, record, message, message_dict, alias):
     if alias.alias_contact == 'employees':
         email_from = tools.decode_message_header(message, 'From')
         email_address = tools.email_split(email_from)[0]
         employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return _('restricted to employees')
         return True
     return super(AliasMixin, self)._alias_check_contact_on_record(record, message, message_dict, alias)
Exemple #9
0
    def message_route(
        self,
        message,
        message_dict,
        model=None,
        thread_id=None,
        custom_values=None,
    ):
        if not isinstance(message, Message):
            raise TypeError(
                "message must be an email.message.Message at this point")
        email_from = decode_message_header(message, "From")
        # Memberspace workflow
        if "<" in email_from:
            email_from = email_from.split("<")[1][:-1]
        user_send_email = self.env["res.partner"].search(
            [("email", "=", email_from)], limit=1)
        error_memberspace_aliases = self.env["memberspace.alias"]
        pass_memberspace_aliases = self.env["memberspace.alias"]
        # Check header with key = To
        alias_error, alias_pass = self.verify_memberspace_alias(
            message, "To", user_send_email, email_from)
        pass_memberspace_aliases |= alias_pass
        error_memberspace_aliases |= alias_error
        # Check header with key = Delivered-To
        alias_error, alias_pass = self.verify_memberspace_alias(
            message, "Delivered-To", user_send_email, email_from)
        pass_memberspace_aliases |= alias_pass
        error_memberspace_aliases |= alias_error
        # Check header with key = Cc
        alias_error, alias_pass = self.verify_memberspace_alias(
            message, "Cc", user_send_email, email_from)
        pass_memberspace_aliases |= alias_pass
        error_memberspace_aliases |= alias_error
        # Check header with key = Resent-To
        alias_error, alias_pass = self.verify_memberspace_alias(
            message, "Resent-To", user_send_email, email_from)
        pass_memberspace_aliases |= alias_pass
        error_memberspace_aliases |= alias_error
        # Check header with key = Resent-Cc
        alias_error, alias_pass = self.verify_memberspace_alias(
            message, "Resent-Cc", user_send_email, email_from)
        pass_memberspace_aliases |= alias_pass
        error_memberspace_aliases |= alias_error

        if not pass_memberspace_aliases:
            return []
        return super(MailThread, self).message_route(
            message,
            message_dict,
            model=model,
            thread_id=thread_id,
            custom_values=custom_values,
        )
Exemple #10
0
 def verify_memberspace_alias(self, message, header, user_sent, email_from):
     MemberSpaceAlias = self.env["memberspace.alias"]
     Alias = self.env["mail.alias"]
     alias_error = MemberSpaceAlias
     alias_pass = MemberSpaceAlias
     mail_tmpl = self.env.ref(
         "coop_memberspace.email_inform_cannot_send_to_memberspace_alias")
     if header not in message:
         return alias_error, alias_pass
     rcpt_tos = ",".join([decode_message_header(message, header)])
     local_parts = [
         e.split("@")[0].lower() for e in tools.email_split(rcpt_tos)
     ]
     aliases = Alias.search([("alias_name", "in", local_parts)])
     memberspace_aliases = MemberSpaceAlias.search([("alias_id", "in",
                                                     aliases.ids)])
     for memberspace_alias in memberspace_aliases:
         if not user_sent:
             alias_error |= memberspace_aliases
             break
         coordinators = memberspace_alias.shift_id.user_ids
         members = coordinators |\
             memberspace_alias.shift_id.registration_ids.filtered(
                 lambda r: r.is_current_participant
             ).mapped("partner_id")
         if (memberspace_alias.type == "team"
                 and user_sent not in coordinators):
             alias_error |= memberspace_alias
             if user_sent in members and mail_tmpl:
                 email_add = (memberspace_alias.alias_name + "@" +
                              memberspace_alias.alias_domain)
                 template_values = {
                     "email_to": email_from,
                     "email_from": self.env.user.company_id.email,
                     "email_cc": False,
                     "lang": user_sent.lang,
                     "auto_delete": True,
                     "partner_to": False,
                 }
                 mail_tmpl.write(template_values)
                 mail_tmpl.with_context(email_add=email_add).send_mail(
                     self.env.user.id, force_send=True)
             continue
         elif (memberspace_alias.type == "coordinator"
               and user_sent not in members):
             alias_error |= memberspace_alias
             continue
         alias_pass |= memberspace_alias
     if alias_error:
         new_header = ",".join(
             [ma.alias_name + "@" + ma.alias_domain for ma in alias_pass])
         message.replace_header(header, new_header)
     return alias_error, alias_pass
Exemple #11
0
    def message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None):
        """ Override to udpate mass mailing statistics based on bounce emails """
        bounce_alias = self.env['ir.config_parameter'].sudo().get_param("mail.bounce.alias")
        email_to = decode_message_header(message, 'To')
        email_to_localpart = (tools.email_split(email_to) or [''])[0].split('@', 1)[0].lower()

        if bounce_alias and bounce_alias in email_to_localpart:
            bounce_re = re.compile("%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias), re.UNICODE)
            bounce_match = bounce_re.search(email_to)
            if bounce_match:
                bounced_mail_id = bounce_match.group(1)
                self.env['mail.mail.statistics'].set_bounced(mail_mail_ids=[bounced_mail_id])

        return super(MailThread, self).message_route(message, message_dict, model, thread_id, custom_values)
Exemple #12
0
 def _alias_check_contact(self, message, message_dict, alias):
     if alias.alias_contact == 'employees' and self.ids:
         email_from = tools.decode_message_header(message, 'From')
         email_address = tools.email_split(email_from)[0]
         employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return {
                 'error_message': 'restricted to employees',
                 'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html,
             }
         return True
     return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
Exemple #13
0
    def message_route_verify(self,
                             message,
                             message_dict,
                             route,
                             update_author=True,
                             assert_model=True,
                             create_fallback=True,
                             allow_private=False,
                             drop_alias=False):
        res = super(MailThread,
                    self).message_route_verify(message,
                                               message_dict,
                                               route,
                                               update_author=update_author,
                                               assert_model=assert_model,
                                               create_fallback=create_fallback,
                                               allow_private=allow_private,
                                               drop_alias=drop_alias)

        if res:
            alias = route[4]
            email_from = decode_message_header(message, 'From')
            message_id = message.get('Message-Id')

            # Alias: check alias_contact settings for employees
            if alias and alias.alias_contact == 'employees':
                email_address = email_split(email_from)[0]
                employee = self.env['hr.employee'].search(
                    [('work_email', 'ilike', email_address)], limit=1)
                if not employee:
                    employee = self.env['hr.employee'].search(
                        [('user_id.email', 'ilike', email_address)], limit=1)
                if not employee:
                    mail_template = self.env.ref(
                        'hr.mail_template_data_unknown_employee_email_address')
                    self._routing_warn(
                        _('alias %s does not accept unknown employees') %
                        alias.alias_name, _('skipping'), message_id, route,
                        False)
                    self._routing_create_bounce_email(email_from,
                                                      mail_template.body_html,
                                                      message)
                    return False
        return res
Exemple #14
0
 def _alias_check_contact(self, message, message_dict, alias):
     if alias.alias_contact == 'employees' and self.ids:
         email_from = tools.decode_message_header(message, 'From')
         email_address = email_split(email_from)[0]
         employee = self.env['hr.employee'].search(
             [('work_email', 'ilike', email_address)], limit=1)
         if not employee:
             employee = self.env['hr.employee'].search(
                 [('user_id.email', 'ilike', email_address)], limit=1)
         if not employee:
             return {
                 'error_message':
                 'restricted to employees',
                 'error_template':
                 self.env.ref(
                     'hr.mail_template_data_unknown_employee_email_address'
                 ).body_html,
             }
         return True
     return super(MailAlias,
                  self)._alias_check_contact(message, message_dict, alias)
Exemple #15
0
    def message_process(self,
                        model,
                        message,
                        custom_values=None,
                        save_original=False,
                        strip_attachments=False,
                        thread_id=None):
        if isinstance(message, xmlrpclib.Binary):
            message = str(message.data)
        if isinstance(message, unicode):
            message = message.encode('utf-8')
        msg_txt = email.message_from_string(message)
        email_to = decode_message_header(msg_txt, 'To')
        if '.amazonses.com' in msg_txt['Message-Id']:
            new_message_id = self.env['mail.mail'].browse(
                int(email_to.split('@')[0].split('+')[1].split('-')
                    [0])).message_id
            msg_txt.replace_header('Message-Id', new_message_id)
        msg = self.message_parse(msg_txt, save_original=save_original)

        routes = self.message_route(msg_txt, msg, model, thread_id,
                                    custom_values)
        if 'bounce' in msg_txt['To']:
            message_id = self.env['mail.message'].search([
                ('message_id', '=', msg_txt['Message-Id'])
            ])
            if message_id:
                tracking_id = self.env['mail.tracking.email'].search([
                    ('mail_message_id', '=', message_id.id)
                ])
                tracking_id.write({'state': 'bounced'})
                tracking_id.event_create('hard_bounce', {
                    'bounce_type': 'hard_bounce',
                    'bounce_description': 'Bounced'
                })
        thread_id = self.message_route_process(msg_txt, msg, routes)
        return thread_id
Exemple #16
0
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):
        """ Override to udpate mass mailing statistics based on bounce emails """
        bounce_alias = self.env['ir.config_parameter'].sudo().get_param(
            "mail.bounce.alias")
        alias_domain = self.env['ir.config_parameter'].sudo().get_param(
            "mail.catchall.domain")
        # activate strict alias domain check for stable, will be falsy by default to be backward compatible
        alias_domain_check = self.env['ir.config_parameter'].sudo().get_param(
            "mail.catchall.domain.strict")
        email_to = decode_message_header(message, 'To')
        email_to_localparts = [
            e.split('@', 1)[0].lower()
            for e in (tools.email_split(email_to) or [''])
            if not alias_domain_check or (
                not alias_domain or e.endswith('@%s' % alias_domain))
        ]

        if bounce_alias and any(
                email.startswith(bounce_alias)
                for email in email_to_localparts):
            bounce_re = re.compile(
                "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias),
                re.UNICODE)
            bounce_match = bounce_re.search(email_to)
            if bounce_match:
                bounced_mail_id = bounce_match.group(1)
                self.env['mail.mail.statistics'].set_bounced(
                    mail_mail_ids=[bounced_mail_id])

        return super(MailThread,
                     self).message_route(message, message_dict, model,
                                         thread_id, custom_values)
Exemple #17
0
    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()
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):
        if not isinstance(message, Message):
            raise TypeError(
                'message must be an email.message.Message at this point')
        MailMessage = self.env['mail.message']
        Alias, dest_aliases = self.env['mail.alias'], self.env['mail.alias']
        bounce_alias = self.env['ir.config_parameter'].sudo().get_param(
            "mail.bounce.alias")
        fallback_model = model

        # get email.message.Message variables for future processing
        local_hostname = socket.gethostname()
        message_id = message.get('Message-Id')

        # compute references to find if message is a reply to an existing thread
        references = tools.decode_message_header(message, 'References')
        in_reply_to = tools.decode_message_header(message,
                                                  'In-Reply-To').strip()
        thread_references = references or in_reply_to
        reply_match, reply_model, reply_thread_id, reply_hostname, reply_private = tools.email_references(
            thread_references)

        # author and recipients
        email_from = tools.decode_message_header(message, 'From')
        email_from_localpart = (tools.email_split(email_from)
                                or [''])[0].split('@', 1)[0].lower()
        email_to = tools.decode_message_header(message, 'To')
        email_to_localpart = (tools.email_split(email_to)
                              or [''])[0].split('@', 1)[0].lower()

        # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
        # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
        rcpt_tos = ','.join([
            tools.decode_message_header(message, 'Delivered-To'),
            tools.decode_message_header(message, 'To'),
            tools.decode_message_header(message, 'Cc'),
            tools.decode_message_header(message, 'Resent-To'),
            tools.decode_message_header(message, 'Resent-Cc')
        ])
        rcpt_tos_localparts = [
            e.split('@')[0].lower() for e in tools.email_split(rcpt_tos)
        ]

        # 0. Verify whether this is a bounced email and use it to collect bounce data and update notifications for customers
        if bounce_alias and bounce_alias in email_to_localpart:
            # Bounce regex: typical form of bounce is bounce_alias+128-crm.lead-34@domain
            # group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
            bounce_re = re.compile(
                "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias),
                re.UNICODE)
            bounce_match = bounce_re.search(email_to)

            if bounce_match:
                bounced_mail_id, bounced_model, bounced_thread_id = bounce_match.group(
                    1), bounce_match.group(2), bounce_match.group(3)

                email_part = next(
                    (part for part in message.walk()
                     if part.get_content_type() == 'message/rfc822'), None)
                dsn_part = next(
                    (part for part in message.walk()
                     if part.get_content_type() == 'message/delivery-status'),
                    None)

                partners, partner_address = self.env['res.partner'], False
                if dsn_part and len(dsn_part.get_payload()) > 1:
                    dsn = dsn_part.get_payload()[1]
                    final_recipient_data = tools.decode_message_header(
                        dsn, 'Final-Recipient')
                    partner_address = final_recipient_data.split(';',
                                                                 1)[1].strip()
                    if partner_address:
                        partners = partners.sudo().search([('email', 'like',
                                                            partner_address)])
                        for partner in partners:
                            partner.message_receive_bounce(
                                partner_address,
                                partner,
                                mail_id=bounced_mail_id)

                mail_message = self.env['mail.message']
                if email_part:
                    email = email_part.get_payload()[0]
                    bounced_message_id = tools.mail_header_msgid_re.findall(
                        tools.decode_message_header(email, 'Message-Id'))
                    mail_message = MailMessage.sudo().search([
                        ('message_id', 'in', bounced_message_id)
                    ])

                if partners and mail_message:
                    notifications = self.env['mail.notification'].sudo(
                    ).search([('mail_message_id', '=', mail_message.id),
                              ('res_partner_id', 'in', partners.ids)])
                    notifications.write({'email_status': 'bounce'})

                if bounced_model in self.env and hasattr(
                        self.env[bounced_model],
                        'message_receive_bounce') and bounced_thread_id:
                    self.env[bounced_model].browse(
                        int(bounced_thread_id)).message_receive_bounce(
                            partner_address, partners, mail_id=bounced_mail_id)
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s: dest %s (partner %s)',
                    email_from, email_to, message_id, bounced_mail_id,
                    bounced_model, bounced_thread_id, partner_address,
                    partners)
                return []

        # 0. First check if this is a bounce message or not.
        #    See http://datatracker.ietf.org/doc/rfc3462/?include_text=1
        #    As all MTA does not respect this RFC (googlemail is one of them),
        #    we also need to verify if the message come from "mailer-daemon"
        if message.get_content_type(
        ) == 'multipart/report' or email_from_localpart == 'mailer-daemon':
            _logger.info(
                'Routing mail with Message-Id %s: not routing bounce email from %s to %s',
                message_id, email_from, email_to)
            try:
                self.env['mail.return_processing'].create({
                    'body':
                    message_dict.get('body'),
                    'subject':
                    message._payload[1]._payload[0]._headers[7][1],
                    'email_from':
                    message._payload[1]._payload[0]._headers[8][1],
                    'email_to':
                    message._payload[1]._payload[0]._headers[10][1],
                    'message_id':
                    message._payload[1]._payload[0]._headers[6][1],
                    'message_type':
                    message_dict.get('message_type'),
                    'date_time':
                    message_dict.get('date'),
                })
            except Exception as e:
                x_list, x_dict = message._payload[1]._payload[
                    0]._payload.split('\r\n'), {}
                for x in x_list[::-1]:
                    value = x.split(': ')
                    if len(value) == 2:
                        if value[0] in ['From', 'Message-Id', 'Subject', 'To']:
                            x_dict.update({value[0]: value[1]})
                    x_list.remove(x)
                self.env['mail.return_processing'].create({
                    'body':
                    message_dict.get('body'),
                    'subject':
                    x_dict.get('Subject'),
                    'email_from':
                    x_dict.get('From'),
                    'email_to':
                    x_dict.get('To'),
                    'message_id':
                    x_dict.get('Message-Id'),
                    'date_time':
                    fields.datetime.now(),
                })
            return []

        # 1. Check if message is a reply on a thread
        msg_references = [
            ref
            for ref in tools.mail_header_msgid_re.findall(thread_references)
            if 'reply_to' not in ref
        ]
        mail_messages = MailMessage.sudo().search(
            [('message_id', 'in', msg_references)], limit=1)
        is_a_reply = bool(mail_messages)

        # 1.1 Handle forward to an alias with a different model: do not consider it as a reply
        if reply_model and reply_thread_id:
            other_alias = Alias.search([
                '&', ('alias_name', '!=', False),
                ('alias_name', '=', email_to_localpart)
            ])
            if other_alias and other_alias.alias_model_id.model != reply_model:
                is_a_reply = False

        if is_a_reply:
            model, thread_id = mail_messages.model, mail_messages.res_id
            if not reply_private:  # TDE note: not sure why private mode as no alias search, copying existing behavior
                dest_aliases = Alias.search(
                    [('alias_name', 'in', rcpt_tos_localparts)], limit=1)

            route = self.message_route_verify(
                message,
                message_dict,
                (model, thread_id, custom_values, self._uid, dest_aliases),
                update_author=True,
                assert_model=reply_private,
                create_fallback=True,
                allow_private=reply_private,
                drop_alias=True)
            if route:
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
                    email_from, email_to, message_id, model, thread_id,
                    custom_values, self._uid)
                return [route]
            elif route is False:
                return []

        # 2. Look for a matching mail.alias entry
        if rcpt_tos_localparts:
            # no route found for a matching reference (or reply), so parent is invalid
            message_dict.pop('parent_id', None)
            dest_aliases = Alias.search([('alias_name', 'in',
                                          rcpt_tos_localparts)])
            if dest_aliases:
                routes = []
                for alias in dest_aliases:
                    user_id = alias.alias_user_id.id
                    if not user_id:
                        # TDE note: this could cause crashes, because no clue that the user
                        # that send the email has the right to create or modify a new document
                        # Fallback on user_id = uid
                        # Note: recognized partners will be added as followers anyway
                        # user_id = self._message_find_user_id(message)
                        user_id = self._uid
                        _logger.info('No matching user_id for the alias %s',
                                     alias.alias_name)
                    route = (alias.alias_model_id.model,
                             alias.alias_force_thread_id,
                             safe_eval(alias.alias_defaults), user_id, alias)
                    route = self.message_route_verify(message,
                                                      message_dict,
                                                      route,
                                                      update_author=True,
                                                      assert_model=True,
                                                      create_fallback=True)
                    if route:
                        _logger.info(
                            'Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
                            email_from, email_to, message_id, route)
                        routes.append(route)
                return routes

        # 5. Fallback to the provided parameters, if they work
        if fallback_model:
            # no route found for a matching reference (or reply), so parent is invalid
            message_dict.pop('parent_id', None)
            route = self.message_route_verify(
                message,
                message_dict,
                (fallback_model, thread_id, custom_values, self._uid, None),
                update_author=True,
                assert_model=True)
            if route:
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
                    email_from, email_to, message_id, fallback_model,
                    thread_id, custom_values, self._uid)
                return [route]

        # ValueError if no routes found and if no bounce occured
        raise ValueError(
            'No possible route found for incoming message from %s to %s (Message-Id %s:). '
            'Create an appropriate mail.alias or force the destination model.'
            % (email_from, email_to, message_id))
Exemple #19
0
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):
        """ Attempt to figure out the correct target model, thread_id,
        custom_values and user_id to use for an incoming message.
        Multiple values may be returned, if a message had multiple
        recipients matching existing mail.aliases, for example.

        The following heuristics are used, in this order:

         * if the message replies to an existing thread by having a Message-Id
           that matches an existing mail_message.message_id, we take the original
           message model/thread_id pair and ignore custom_value as no creation will
           take place
         * if the message replies to an existing thread by having In-Reply-To or
           References matching odoo model/thread_id Message-Id and if this thread
           has messages without message_id, take this model/thread_id pair and
           ignore custom_value as no creation will take place (6.1 compatibility)
         * look for a mail.alias entry matching the message recipients and use the
           corresponding model, thread_id, custom_values and user_id. This could
           lead to a thread update or creation depending on the alias
         * fallback on provided ``model``, ``thread_id`` and ``custom_values``
         * raise an exception as no route has been found

        :param string message: an email.message instance
        :param dict message_dict: dictionary holding parsed message variables
        :param string model: the fallback model to use if the message does not match
            any of the currently configured mail aliases (may be None if a matching
            alias is supposed to be present)
        :type dict custom_values: optional dictionary of default field values
            to pass to ``message_new`` if a new record needs to be created.
            Ignored if the thread record already exists, and also if a matching
            mail.alias was found (aliases define their own defaults)
        :param int thread_id: optional ID of the record/thread from ``model`` to
            which this mail should be attached. Only used if the message does not
            reply to an existing thread and does not match any mail alias.
        :return: list of routes [(model, thread_id, custom_values, user_id, alias)]

        :raises: ValueError, TypeError
        """
        if not isinstance(message, Message):
            raise TypeError(
                'message must be an email.message.Message at this point')
        MailMessage = self.env['mail.message']
        Alias, dest_aliases = self.env['mail.alias'], self.env['mail.alias']
        catchall_alias = self.env['ir.config_parameter'].sudo().get_param(
            "mail.catchall.alias")
        bounce_alias = self.env['ir.config_parameter'].sudo().get_param(
            "mail.bounce.alias")
        fallback_model = model

        # get email.message.Message variables for future processing
        local_hostname = socket.gethostname()
        message_id = message.get('Message-Id')
        message
        # compute references to find if message is a reply to an existing thread
        references = tools.decode_message_header(message, 'References')
        in_reply_to = tools.decode_message_header(message,
                                                  'In-Reply-To').strip()
        thread_references = references or in_reply_to
        reply_match, reply_model, reply_thread_id, reply_hostname, reply_private = tools.email_references(
            thread_references)

        # author and recipients
        email_from = tools.decode_message_header(message, 'From')
        email_from_localpart = (tools.email_split(email_from)
                                or [''])[0].split('@', 1)[0].lower()
        email_to = tools.decode_message_header(message, 'To')
        email_to_localpart = (tools.email_split(email_to)
                              or [''])[0].split('@', 1)[0].lower()

        # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
        # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
        rcpt_tos = ','.join([
            tools.decode_message_header(message, 'Delivered-To'),
            tools.decode_message_header(message, 'To'),
            tools.decode_message_header(message, 'Cc'),
            tools.decode_message_header(message, 'Resent-To'),
            tools.decode_message_header(message, 'Resent-Cc')
        ])
        rcpt_tos_localparts = [
            e.split('@')[0].lower() for e in tools.email_split(rcpt_tos)
        ]

        # 0. Verify whether this is a bounced email and use it to collect bounce data and update notifications for customers
        if bounce_alias and bounce_alias in email_to_localpart:
            # Bounce regex: typical form of bounce is bounce_alias+128-crm.lead-34@domain
            # group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
            bounce_re = re.compile(
                "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias),
                re.UNICODE)
            bounce_match = bounce_re.search(email_to)

            if bounce_match:
                bounced_mail_id, bounced_model, bounced_thread_id = bounce_match.group(
                    1), bounce_match.group(2), bounce_match.group(3)

                email_part = next(
                    (part for part in message.walk()
                     if part.get_content_type() == 'message/rfc822'), None)
                dsn_part = next(
                    (part for part in message.walk()
                     if part.get_content_type() == 'message/delivery-status'),
                    None)

                partners, partner_address = self.env['res.partner'], False
                if dsn_part and len(dsn_part.get_payload()) > 1:
                    dsn = dsn_part.get_payload()[1]
                    final_recipient_data = tools.decode_message_header(
                        dsn, 'Final-Recipient')
                    partner_address = final_recipient_data.split(';',
                                                                 1)[1].strip()
                    if partner_address:
                        partners = partners.sudo().search([('email', '=',
                                                            partner_address)])
                        for partner in partners:
                            partner.message_receive_bounce(
                                partner_address,
                                partner,
                                mail_id=bounced_mail_id)

                mail_message = self.env['mail.message']
                if email_part:
                    email = email_part.get_payload()[0]
                    bounced_message_id = tools.mail_header_msgid_re.findall(
                        tools.decode_message_header(email, 'Message-Id'))
                    mail_message = MailMessage.sudo().search([
                        ('message_id', 'in', bounced_message_id)
                    ])

                if partners and mail_message:
                    notifications = self.env['mail.notification'].sudo(
                    ).search([('mail_message_id', '=', mail_message.id),
                              ('res_partner_id', 'in', partners.ids)])
                    notifications.write({'email_status': 'bounce'})

                if bounced_model in self.env and hasattr(
                        self.env[bounced_model],
                        'message_receive_bounce') and bounced_thread_id:
                    self.env[bounced_model].browse(
                        int(bounced_thread_id)).message_receive_bounce(
                            partner_address, partners, mail_id=bounced_mail_id)

                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s: dest %s (partner %s)',
                    email_from, email_to, message_id, bounced_mail_id,
                    bounced_model, bounced_thread_id, partner_address,
                    partners)
                return []

        # 0. First check if this is a bounce message or not.
        #    See http://datatracker.ietf.org/doc/rfc3462/?include_text=1
        #    As all MTA does not respect this RFC (googlemail is one of them),
        #    we also need to verify if the message come from "mailer-daemon"
        if message.get_content_type(
        ) == 'multipart/report' or email_from_localpart == 'mailer-daemon':
            _logger.info(
                'Routing mail with Message-Id %s: not routing bounce email from %s to %s',
                message_id, email_from, email_to)
            return []

        # 1. Check if message is a reply on a thread
        msg_references = [
            ref
            for ref in tools.mail_header_msgid_re.findall(thread_references)
            if 'reply_to' not in ref
        ]
        mail_messages = MailMessage.sudo().search(
            [('message_id', 'in', msg_references)], limit=1)
        is_a_reply = bool(mail_messages)

        # 1.1 Handle forward to an alias with a different model: do not consider it as a reply
        if not reply_model or not reply_thread_id:
            other_alias = Alias.search([
                '&', ('alias_name', '!=', False),
                ('alias_name', '=', email_to_localpart)
            ])
            if other_alias and other_alias.alias_model_id.model != reply_model:
                is_a_reply = False

        if is_a_reply:
            model, thread_id = mail_messages.model, mail_messages.res_id
            if not reply_private:  # TDE note: not sure why private mode as no alias search, copying existing behavior
                dest_aliases = Alias.search(
                    [('alias_name', 'in', rcpt_tos_localparts)], limit=1)

            route = self.message_route_verify(
                message,
                message_dict,
                (model, thread_id, custom_values, self._uid, dest_aliases),
                update_author=True,
                assert_model=reply_private,
                create_fallback=True,
                allow_private=reply_private,
                drop_alias=True)
            if route:
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
                    email_from, email_to, message_id, model, thread_id,
                    custom_values, self._uid)
                return [route]
            elif route is False:
                return []

        # 2. Look for a matching mail.alias entry
        if rcpt_tos_localparts:
            # no route found for a matching reference (or reply), so parent is invalid
            message_dict.pop('parent_id', None)

            # check it does not directly contact catchall
            if catchall_alias and catchall_alias in email_to_localpart:
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: direct write to catchall, bounce',
                    email_from, email_to, message_id)
                body = self.env.ref('mail.mail_bounce_catchall').render(
                    {
                        'message': message,
                    }, engine='ir.qweb')
                self._routing_create_bounce_email(
                    email_from,
                    body,
                    message,
                    reply_to=self.env.user.company_id.email)
                return []

            dest_aliases = Alias.search([('alias_name', 'in',
                                          rcpt_tos_localparts)])
            if dest_aliases:
                routes = []
                for alias in dest_aliases:
                    user_id = alias.alias_user_id.id
                    if not user_id:
                        # TDE note: this could cause crashes, because no clue that the user
                        # that send the email has the right to create or modify a new document
                        # Fallback on user_id = uid
                        # Note: recognized partners will be added as followers anyway
                        # user_id = self._message_find_user_id(message)
                        user_id = self._uid
                        _logger.info('No matching user_id for the alias %s',
                                     alias.alias_name)
                    route = (alias.alias_model_id.model,
                             alias.alias_force_thread_id,
                             safe_eval(alias.alias_defaults), user_id, alias)
                    route = self.message_route_verify(message,
                                                      message_dict,
                                                      route,
                                                      update_author=True,
                                                      assert_model=True,
                                                      create_fallback=True)
                    if route:
                        _logger.info(
                            'Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
                            email_from, email_to, message_id, route)
                        routes.append(route)
                return routes

        # 5. Fallback to the provided parameters, if they work
        if fallback_model:
            # no route found for a matching reference (or reply), so parent is invalid
            message_dict.pop('parent_id', None)
            route = self.message_route_verify(
                message,
                message_dict,
                (fallback_model, thread_id, custom_values, self._uid, None),
                update_author=True,
                assert_model=True)
            if route:
                _logger.info(
                    'Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
                    email_from, email_to, message_id, fallback_model,
                    thread_id, custom_values, self._uid)
                return [route]

        # ValueError if no routes found and if no bounce occured
        raise ValueError(
            'No possible route found for incoming message from %s to %s (Message-Id %s:). '
            'Create an appropriate mail.alias or force the destination model.'
            % (email_from, email_to, message_id))
Exemple #20
0
 def test_02_message_route_verify(self):
     msg = email.message_from_string(MAIL_MESSAGE)
     route = ('res.users', 1, None, self.env.uid, '')
     self.env['mail.thread'].message_route_verify(msg, msg, route)
     email_from = decode_message_header(msg, 'From')
     self.assertEqual(email_from, '*****@*****.**')
    def message_parse(self, message, save_original=False):
        email_to = tools.decode_message_header(message, "To")
        email_to_localpart = (tools.email_split(email_to)
                              or [""])[0].split("@", 1)[0]

        config_params = self.env["ir.config_parameter"].sudo()

        # Check if the To part contains the prefix and a base32/64 encoded string
        # Remove the "24," part when migrating to Odoo 14.
        prefix_in_to = email_to_localpart and re.search(
            r".*" + MESSAGE_PREFIX + "(?P<odoo_id>.{24,32}$)",
            email_to_localpart)

        prioritize_replyto_over_headers = config_params.get_param(
            "email_headers.prioritize_replyto_over_headers", "True")
        prioritize_replyto_over_headers = (
            True if prioritize_replyto_over_headers != "False" else False)

        # If the msg prefix part is found in the To part, find the parent
        # message and inject the Message-Id to the In-Reply-To part and
        # remove References because it by default takes priority over
        # In-Reply-To. We want the unique Reply-To address have the priority.
        if prefix_in_to and prioritize_replyto_over_headers:
            message_id_encrypted = prefix_in_to.group("odoo_id")
            try:
                message_id = decode_msg_id(message_id_encrypted, self.env)
                parent_id = self.env["mail.message"].browse(message_id)
                if parent_id:
                    # See unit test test_reply_to_method_msg_id_priority
                    del message["References"]
                    del message["In-Reply-To"]
                    message["In-Reply-To"] = parent_id.message_id
                else:
                    _logger.warning(
                        "Received an invalid mail.message database id in incoming "
                        "email sent to {}. The email type (comment, note) might "
                        "be wrong.".format(email_to))
            except UnicodeDecodeError:
                _logger.warning(
                    "Unique Reply-To address of an incoming email couldn't be "
                    "decrypted. Falling back to default Odoo behavior.")

        res = super(MailThread, self).message_parse(message, save_original)

        strip_message_id = config_params.get_param(
            "email_headers.strip_mail_message_ids", "True")
        strip_message_id = True if strip_message_id != "False" else False

        if not strip_message_id == "True":
            return res

        # When Odoo compares message_id to the one stored in the database when determining
        #  whether or not the incoming message is a reply to another one, the message_id search
        #  parameter is stripped before the search. But Odoo does not do anything of the sort when
        #  a message is created, meaning if some email software (for example Outlook,
        #  for no particular reason) includes anything strippable at the start of the Message-Id,
        #  any replies to that message in the future will not find their way correctly, as the
        #  search yields nothing.
        #
        # Example of what happened before. The first one is the original Message-Id, and thus also
        #  the ID that gets stored on the mail.message as the `message_id`
        #   '\r\n <*****@*****.**>'
        #  But when trying to find this message, Odoo takes the above message_id and strips it,
        #  which results in:
        #   '<*****@*****.**>'
        #  And then the search is done for an exact match, which will fail.
        #
        # Odoo doesn't, so we must strip the message_ids before they are stored in the database
        mail_message_id = res.get("message_id", "")
        if mail_message_id:
            mail_message_id = mail_message_id.strip()
            res["message_id"] = mail_message_id
        return res
    def message_parse(self, message, save_original=False):
        """
        解析表示RFC-2822电子邮件的 email.message.message,并返回包含消息详细信息的通用dict。

        :param message: email to parse
        :type message: email.message.Message
        :param bool save_original: whether the returned dict should include
            an ``original`` attachment containing the source of the message
        :rtype: dict
        :return: A dict with the following structure, where each field may not
            be present if missing in original message::

            { 'message_id': msg_id,
              'subject': subject,
              'email_from': from,
              'to': to + delivered-to,
              'cc': cc,
              'recipients': delivered-to + to + cc + resent-to + resent-cc,
              'partner_ids': partners found based on recipients emails,
              'body': unified_body,
              'references': references,
              'in_reply_to': in-reply-to,
              'parent_id': parent mail.message based on in_reply_to or references,
              'is_internal': answer to an internal message (note),
              'date': date,
              'attachments': [('file1', 'bytes'),
                              ('file2', 'bytes')}
        """
        if not isinstance(message, EmailMessage):
            raise ValueError(
                _("Message should be a valid EmailMessage instance"))
        msg_dict = {"message_type": "email"}

        message_id = message.get("Message-Id")
        if not message_id:
            # 非常不寻常的情况,就是我们在这里应该容错
            message_id = "<%s@localhost>" % time.time()
            _logger.debug(
                "Parsing Message without message-id, generating a random one: %s",
                message_id,
            )
        msg_dict["message_id"] = message_id.strip()

        if message.get("Subject"):
            msg_dict["subject"] = tools.decode_message_header(
                message, "Subject")

        email_from = tools.decode_message_header(message, "From")
        email_cc = tools.decode_message_header(message, "cc")
        email_from_list = tools.email_split_and_format(email_from)
        email_cc_list = tools.email_split_and_format(email_cc)
        msg_dict["email_from"] = email_from_list[
            0] if email_from_list else email_from
        msg_dict["from"] = msg_dict[
            "email_from"]  # compatibility for message_new
        msg_dict["cc"] = ",".join(email_cc_list) if email_cc_list else email_cc
        # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
        # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
        msg_dict["recipients"] = ",".join(
            set(formatted_email for address in [
                tools.decode_message_header(message, "Delivered-To"),
                tools.decode_message_header(message, "To"),
                tools.decode_message_header(message, "Cc"),
                tools.decode_message_header(message, "Resent-To"),
                tools.decode_message_header(message, "Resent-Cc"),
            ] if address
                for formatted_email in tools.email_split_and_format(address)))
        msg_dict["to"] = ",".join(
            set(formatted_email for address in [
                tools.decode_message_header(message, "Delivered-To"),
                tools.decode_message_header(message, "To"),
            ] if address
                for formatted_email in tools.email_split_and_format(address)))
        partner_ids = [
            x.id for x in self._mail_find_partner_from_emails(
                tools.email_split(msg_dict["recipients"]), records=self) if x
        ]
        msg_dict["partner_ids"] = partner_ids
        # compute references to find if email_message is a reply to an existing thread
        msg_dict["references"] = tools.decode_message_header(
            message, "References")
        msg_dict["in_reply_to"] = tools.decode_message_header(
            message, "In-Reply-To").strip()

        if message.get("Date"):
            try:
                date_hdr = tools.decode_message_header(message, "Date")
                parsed_date = dateutil.parser.parse(date_hdr, fuzzy=True)
                if parsed_date.utcoffset() is None:
                    # naive datetime, so we arbitrarily decide to make it
                    # UTC, there's no better choice. Should not happen,
                    # as RFC2822 requires timezone offset in Date headers.
                    stored_date = parsed_date.replace(tzinfo=pytz.utc)
                else:
                    stored_date = parsed_date.astimezone(tz=pytz.utc)
            except Exception:
                _logger.info(
                    "Failed to parse Date header %r in incoming mail "
                    "with message-id %r, assuming current date/time.",
                    message.get("Date"),
                    message_id,
                )
                stored_date = datetime.datetime.now()
            msg_dict["date"] = stored_date.strftime(
                tools.DEFAULT_SERVER_DATETIME_FORMAT)

        parent_ids = False
        if msg_dict["in_reply_to"]:
            parent_ids = self.env["mail.message"].search(
                [("message_id", "=", msg_dict["in_reply_to"])], limit=1)
        if msg_dict["references"] and not parent_ids:
            references_msg_id_list = tools.mail_header_msgid_re.findall(
                msg_dict["references"])
            parent_ids = self.env["mail.message"].search(
                [("message_id", "in",
                  [x.strip() for x in references_msg_id_list])],
                limit=1,
            )
        if parent_ids:
            msg_dict["parent_id"] = parent_ids.id
            msg_dict["is_internal"] = (parent_ids.subtype_id
                                       and parent_ids.subtype_id.internal
                                       or False)

        msg_dict.update(
            self._message_parse_extract_payload(message,
                                                save_original=save_original))
        msg_dict.update(self._message_parse_extract_bounce(message, msg_dict))
        return msg_dict
    def message_route(self,
                      message,
                      message_dict,
                      model=None,
                      thread_id=None,
                      custom_values=None):

        # NOTE! If you're going to backport this module to Odoo 11 or Odoo 10,
        # you will have to create the mail_bounce_catchall email template
        # because it was introduced only in Odoo 12.

        if not isinstance(message, Message):
            raise TypeError("message must be an "
                            "email.message.Message at this point")

        try:
            route = super(MailThread,
                          self).message_route(message, message_dict, model,
                                              thread_id, custom_values)
        except ValueError:

            # If the headers that connect the incoming message to a thread in
            # Odoo have disappeared at some point and the message was sent to
            # the catchall address (with a sub-addressing suffix), we will
            # skip the default catchall check and perform it here for
            # mail.catchall.alias.custom. We do this because the alias check
            # if done AFTER the catchall check by default and it may cause
            # Odoo to send a bounce message to the sender who sent the email to
            # the correct thread-specific address.

            catchall_alias = (self.env["ir.config_parameter"].sudo().get_param(
                "mail.catchall.alias.custom"))

            email_to = tools.decode_message_header(message, "To")
            email_to_localpart = ((tools.email_split(email_to)
                                   or [""])[0].split("@", 1)[0].lower())

            message_id = message.get("Message-Id")
            email_from = tools.decode_message_header(message, "From")

            # check it does not directly contact catchall
            if catchall_alias and catchall_alias in email_to_localpart:
                _logger.info(
                    "Routing mail from %s to %s with Message-Id %s: "
                    "direct write to catchall, bounce",
                    email_from,
                    email_to,
                    message_id,
                )
                body = self.env.ref("mail.mail_bounce_catchall").render(
                    {"message": message}, engine="ir.qweb")
                self._routing_create_bounce_email(
                    email_from,
                    body,
                    message,
                    reply_to=self.env.user.company_id.email)
                return []
            else:
                raise

        return route