def __init__(self, account=None, mid=None, folder_name=None, received_date=None, flags=None, body_string=None, *args, **kwargs): """ Use .create() instead to handle common errors! (Can't abort object creation in a constructor.) """ _rqd = [account, mid, folder_name, received_date, flags, body_string] # for drafts - skip parsing if not any(_rqd): MailSyncBase.__init__(self, *args, **kwargs) return parsed = mime.from_string(body_string) mime_version = parsed.headers.get('Mime-Version') # NOTE: sometimes MIME-Version is set to "1.0 (1.0)", hence the # .startswith if mime_version is not None and not mime_version.startswith('1.0'): log.error('Unexpected MIME-Version: {0}'.format(mime_version)) self.data_sha256 = sha256(body_string).hexdigest() # clean_subject strips re:, fwd: etc. self.subject = parsed.clean_subject self.from_addr = parse_email_address_list( parsed.headers.get('From')) self.sender_addr = parse_email_address_list( parsed.headers.get('Sender')) self.reply_to = parse_email_address_list( parsed.headers.get('Reply-To')) self.to_addr = parse_email_address_list( parsed.headers.getall('To')) self.cc_addr = parse_email_address_list( parsed.headers.getall('Cc')) self.bcc_addr = parse_email_address_list( parsed.headers.getall('Bcc')) self.in_reply_to = parsed.headers.get('In-Reply-To') self.message_id_header = parsed.headers.get('Message-Id') self.received_date = received_date # Optional mailing list headers self.mailing_list_headers = parse_ml_headers(parsed.headers) # Custom Inbox header self.inbox_uid = parsed.headers.get('X-INBOX-ID') # In accordance with JWZ (http://www.jwz.org/doc/threading.html) self.references = parse_references( parsed.headers.get('References', ''), parsed.headers.get('In-Reply-To', '')) self.size = len(body_string) # includes headers text i = 0 # for walk_index from inbox.models.block import Part # Store all message headers as object with index 0 headers_part = Part() headers_part.namespace_id = account.namespace.id headers_part.message = self headers_part.walk_index = i headers_part.data = json.dumps(parsed.headers.items()) self.parts.append(headers_part) for mimepart in parsed.walk( with_self=parsed.content_type.is_singlepart()): i += 1 if mimepart.content_type.is_multipart(): log.warning("multipart sub-part found! on {}" .format(self.g_msgid)) continue # TODO should we store relations? new_part = Part() new_part.namespace_id = account.namespace.id new_part.message = self new_part.walk_index = i new_part.misc_keyval = mimepart.headers.items() # everything new_part.content_type = mimepart.content_type.value new_part.filename = _trim_filename( mimepart.content_type.params.get('name'), log=log) # TODO maybe also trim other headers? if mimepart.content_disposition[0] is not None: value, params = mimepart.content_disposition if value not in ['inline', 'attachment']: errmsg = """ Unknown Content-Disposition on message {0} found in {1}. Bad Content-Disposition was: '{2}' Parsed Content-Disposition was: '{3}'""".format( mid, folder_name, mimepart.content_disposition) log.error(errmsg) continue else: new_part.content_disposition = value if value == 'attachment': new_part.filename = _trim_filename( params.get('filename'), log=log) if mimepart.body is None: data_to_write = '' elif new_part.content_type.startswith('text'): data_to_write = mimepart.body.encode('utf-8', 'strict') # normalize mac/win/unix newlines data_to_write = data_to_write \ .replace('\r\n', '\n').replace('\r', '\n') else: data_to_write = mimepart.body if data_to_write is None: data_to_write = '' new_part.content_id = mimepart.headers.get('Content-Id') new_part.data = data_to_write self.parts.append(new_part) self.calculate_sanitized_body() MailSyncBase.__init__(self, *args, **kwargs)