Example #1
0
 def test_parse_multipart(self) -> None:
     header = b'subject: multipart test\n' \
              b'content-type: multipart/mixed;\n boundary="testbound"\n\n'
     part1 = b'\n' \
             b'part one!\n\n' \
             b'lorem ipsum etc.\n'
     part2 = b'content-type: text/html\n' \
             b'\n' \
             b'<html><body><h1>part two</h1></body></html>\n'
     body = b'preamble\n' \
            b'--testbound\n' + part1 + \
            b'--testbound\n' + part2 + \
            b'--testbound--\n' \
            b'epilogue\n'
     raw = header + body
     msg = MessageContent.parse(raw)
     self.assertEqual(raw, bytes(msg))
     self.assertEqual(header, bytes(msg.header))
     self.assertEqual({b'subject': ['multipart test'],
                       b'content-type': ['multipart/mixed; '
                                         'boundary="testbound"']},
                      msg.header.parsed)
     self.assertEqual(body, bytes(msg.body))
     self.assertTrue(msg.body.has_nested)
     self.assertEqual(2, len(msg.body.nested))
     self.assertEqual(part1, bytes(msg.body.nested[0]))
     self.assertEqual({}, msg.body.nested[0].header.parsed)
     self.assertEqual(part2, bytes(msg.body.nested[1]))
     self.assertEqual({b'content-type': ['text/html']},
                      msg.body.nested[1].header.parsed)
Example #2
0
 def get_actions(self, sender: str, recipient: str,
                 append_msg: AppendMessage) -> Sequence[Command]:
     actions: List[Command] = []
     try:
         content = MessageContent.parse(append_msg.literal)
         self._get_actions(actions, sender, recipient, append_msg, content)
     except StopRunning:
         pass
     return actions
Example #3
0
 def get_actions(self, sender: str, recipient: str,
                 append_msg: AppendMessage) -> Sequence[Command]:
     actions: List[Command] = []
     try:
         content = MessageContent.parse(append_msg.message)
         self._get_actions(actions, sender, recipient, append_msg, content)
     except StopRunning:
         pass
     return actions
Example #4
0
 async def load_content(self, requirement: FetchRequirement) \
         -> LoadedMessage:
     if self._key is None or self._maildir is None \
             or requirement.has_none(FetchRequirement.CONTENT):
         return LoadedMessage(self, requirement, None)
     try:
         maildir_msg = self._maildir.get_message(self._key)
     except (KeyError, FileNotFoundError):
         return LoadedMessage(self, requirement, None)
     else:
         content = MessageContent.parse(bytes(maildir_msg))
         return LoadedMessage(self, requirement, content)
Example #5
0
 async def add(self, append_msg: AppendMessage, recent: bool = False) \
         -> Message:
     redis = self._redis
     prefix = self._prefix
     is_deleted = Deleted in append_msg.flag_set
     is_unseen = Seen not in append_msg.flag_set
     msg_content = MessageContent.parse(append_msg.message)
     msg_flags = [flag.value for flag in append_msg.flag_set]
     msg_time = append_msg.when.isoformat().encode('ascii')
     while True:
         await redis.watch(prefix + b':max-mod', self._abort_key)
         max_uid, max_mod, abort = await redis.mget(
             prefix + b':max-uid', prefix + b':max-mod', self._abort_key)
         MailboxAbort.assertFalse(abort)
         new_uid = int(max_uid or 0) + 1
         new_mod = int(max_mod or 0) + 1
         msg_prefix = prefix + b':msg:%d' % new_uid
         multi = redis.multi_exec()
         multi.set(prefix + b':max-uid', new_uid)
         multi.set(prefix + b':max-mod', new_mod)
         multi.sadd(prefix + b':uids', new_uid)
         multi.zadd(prefix + b':mod-sequence', new_mod, new_uid)
         multi.zadd(prefix + b':sequence', new_uid, new_uid)
         if recent:
             multi.sadd(prefix + b':recent', new_uid)
         if is_deleted:
             multi.sadd(prefix + b':deleted', new_uid)
         if is_unseen:
             multi.zadd(prefix + b':unseen', new_uid, new_uid)
         if msg_flags:
             multi.sadd(msg_prefix + b':flags', *msg_flags)
         multi.set(msg_prefix + b':time', msg_time)
         multi.set(msg_prefix + b':header', bytes(msg_content.header))
         multi.set(msg_prefix + b':body', bytes(msg_content.body))
         try:
             await multi.execute()
         except MultiExecError:
             if await _check_errors(multi):
                 raise
         else:
             break
     return Message(new_uid, append_msg.flag_set, append_msg.when,
                    recent=recent, content=msg_content)
Example #6
0
 def test_parse(self) -> None:
     header = b'from: [email protected] \n' \
              b'to: [email protected],\n [email protected]\n' \
              b'to:  [email protected]\r\n' \
              b'subject: hello world \xff\r\n' \
              b'test:\n more stuff\n\n'
     body = b'abc\n'
     raw = header + body
     msg = MessageContent.parse(raw)
     self.assertEqual(raw, bytes(msg))
     self.assertEqual(header, bytes(msg.header))
     self.assertEqual({b'from': ['*****@*****.**'],
                       b'to': ['[email protected], [email protected]',
                               '*****@*****.**'],
                       b'subject': ['hello world \ufffd'],
                       b'test': [' more stuff']}, msg.header.parsed)
     self.assertEqual('hello world \ufffd', msg.header.parsed.subject)
     self.assertEqual(body, bytes(msg.body))
     self.assertFalse(msg.body.has_nested)
Example #7
0
 def test_parse_rfc822(self) -> None:
     header = b'subject: rfc822 test\n' \
              b'content-type: message/rfc822\n\n'
     sub_header = b'content-type: text/html\n\n'
     sub_body = b'<html><body><h1>part two</h1></body></html>'
     body = sub_header + sub_body
     raw = header + body
     msg = MessageContent.parse(raw)
     self.assertEqual(raw, bytes(msg))
     self.assertEqual(header, bytes(msg.header))
     self.assertEqual({b'subject': ['rfc822 test'],
                       b'content-type': ['message/rfc822']},
                      msg.header.parsed)
     self.assertEqual(body, bytes(msg.body))
     self.assertTrue(msg.body.has_nested)
     self.assertEqual(1, len(msg.body.nested))
     self.assertEqual(body, bytes(msg.body.nested[0]))
     self.assertEqual(sub_header, bytes(msg.body.nested[0].header))
     self.assertEqual(sub_body, bytes(msg.body.nested[0].body))
     self.assertEqual({b'content-type': ['text/html']},
                      msg.body.nested[0].header.parsed)
Example #8
0
 async def save(self, message: bytes) -> SavedMessage:
     redis = self._redis
     ns_keys = self._ns_keys
     content = MessageContent.parse(message)
     new_email_id = ObjectId.random_email_id()
     msg_hash = HashStream(hashlib.sha1()).digest(content)
     thread_keys = ThreadKey.get_all(content.header)
     thread_key_keys = [
         b'\0'.join(thread_key) for thread_key in thread_keys
     ]
     await redis.unwatch()
     multi = redis.multi_exec()
     multi.hsetnx(ns_keys.email_ids, msg_hash, new_email_id.value)
     multi.hget(ns_keys.email_ids, msg_hash)
     if thread_key_keys:
         multi.hmget(ns_keys.thread_ids, *thread_key_keys)
     else:
         multi.hmget(ns_keys.thread_ids, b'')
     _, email_id, thread_ids = await multi.execute()
     thread_id_b = next(
         (thread_id for thread_id in thread_ids if thread_id is not None),
         None)
     if thread_id_b is None:
         thread_id = ObjectId.random_thread_id()
     else:
         thread_id = ObjectId(thread_id_b)
     ct_keys = ContentKeys(ns_keys, email_id)
     multi = redis.multi_exec()
     multi.hset(ct_keys.data, b'full', message)
     multi.hset(ct_keys.data, b'full-json', json.dumps(content.json))
     multi.hset(ct_keys.data, b'header', bytes(content.header))
     multi.hset(ct_keys.data, b'header-json',
                json.dumps(content.header.json))
     multi.expire(ct_keys.data, self._cleanup.content_expire)
     for thread_key_key in thread_key_keys:
         multi.hsetnx(ns_keys.thread_ids, thread_key_key, thread_id.value)
     await multi.execute()
     return SavedMessage(ObjectId(email_id), thread_id, None)
Example #9
0
 async def save(self, message: bytes) -> SavedMessage:
     content = MessageContent.parse(message)
     email_id = self._content_cache.add(content)
     thread_id = self._thread_cache.add(content)
     return SavedMessage(email_id, thread_id, content)
Example #10
0
 def test_base64_cte(self) -> None:
     data = b'Content-Transfer-Encoding: base64\n\n' + _b64_body
     msg = MessageContent.parse(data)
     decoded = MessageDecoder.of(msg.header).decode(msg.body)
     self.assertEqual(b'Testing\x01\x00\nBase 64 \n', bytes(decoded))
Example #11
0
 def test_quopri_cte(self) -> None:
     data = b'Content-Transfer-Encoding: quoted-printable\n\n' + _qp_body
     msg = MessageContent.parse(data)
     decoded = MessageDecoder.of(msg.header).decode(msg.body)
     self.assertEqual(b'Testing\x01Quoted=Printable\n', bytes(decoded))
Example #12
0
 def test_7bit_cte(self) -> None:
     data = b'\n' + _7bit_body
     msg = MessageContent.parse(data)
     decoded = MessageDecoder.of(msg.header).decode(msg.body)
     self.assertEqual(b'Testing 7bit\n', bytes(decoded))