From 953c0d00d5535565545230cdc208c9985026ab28 Mon Sep 17 00:00:00 2001 From: Bruno Thomas Date: Fri, 16 Apr 2021 00:01:29 +0200 Subject: [PATCH] makes all response lines of type bytes --- aioimaplib/aioimaplib.py | 112 ++++----- .../tests/test_acceptance_aioimaplib.py | 2 +- aioimaplib/tests/test_aioimaplib.py | 229 +++++++++--------- .../tests/test_imapserver_aioimaplib.py | 4 +- 4 files changed, 172 insertions(+), 175 deletions(-) diff --git a/aioimaplib/aioimaplib.py b/aioimaplib/aioimaplib.py index e6d766a..2175847 100644 --- a/aioimaplib/aioimaplib.py +++ b/aioimaplib/aioimaplib.py @@ -27,7 +27,7 @@ from copy import copy from datetime import datetime, timezone, timedelta from enum import Enum -from typing import Union, Any, Coroutine, Callable, Optional, Pattern +from typing import Union, Any, Coroutine, Callable, Optional, Pattern, List # to avoid imap servers to kill the connection after 30mn idling # cf https://www.imapwiki.org/ClientImplementation/Synchronization @@ -103,8 +103,8 @@ def get_running_loop() -> asyncio.AbstractEventLoop: return asyncio.get_running_loop() loop = asyncio.get_event_loop() - if not loop.is_running(): - raise RuntimeError("no running event loop") + #if not loop.is_running(): + # raise RuntimeError("no running event loop") return loop @@ -148,15 +148,17 @@ def __init__(self, name: str, tag: str, *args, prefix: str = None, untagged_resp self.prefix = prefix + ' ' if prefix else None self.untagged_resp_name = untagged_resp_name or name - self.response = None self._exception = None self._loop = loop if loop is not None else get_running_loop() self._event = asyncio.Event(loop=self._loop) self._timeout = timeout self._timer = asyncio.Handle(lambda: None, None, self._loop) # fake timer self._set_timer() - self._literal_data = None self._expected_size = 0 + + self._resp_literal_data = bytearray() + self._resp_result = 'Init' + self._resp_lines: List[bytes] = list() def __repr__(self) -> str: return '{tag} {prefix}{name}{space}{args}'.format( @@ -167,37 +169,37 @@ def __repr__(self) -> str: def __eq__(self, other): return other is not None and other.tag == self.tag and other.name == self.name and other.args == self.args - def close(self, line: str, result: str) -> None: + @property + def response(self): + return Response(self._resp_result, self._resp_lines) + + def close(self, line: bytes, result: str) -> None: self.append_to_resp(line, result=result) self._timer.cancel() self._event.set() def begin_literal_data(self, expected_size: int, literal_data: bytes = b'') -> bytes: self._expected_size = expected_size - self._literal_data = b'' return self.append_literal_data(literal_data) def wait_literal_data(self) -> bool: - return self._expected_size != 0 and len(self._literal_data) != self._expected_size + return self._expected_size != 0 and len(self._resp_literal_data) != self._expected_size def wait_data(self) -> bool: return self.wait_literal_data() def append_literal_data(self, data: bytes) -> bytes: - nb_bytes_to_add = self._expected_size - len(self._literal_data) - self._literal_data += data[0:nb_bytes_to_add] + nb_bytes_to_add = self._expected_size - len(self._resp_literal_data) + self._resp_literal_data.extend(data[0:nb_bytes_to_add]) if not self.wait_literal_data(): - self.append_to_resp(self._literal_data) + self.append_to_resp(self._resp_literal_data) self._end_literal_data() self._reset_timer() return data[nb_bytes_to_add:] - def append_to_resp(self, line: str, result: str = 'Pending') -> None: - if self.response is None: - self.response = Response(result, [line]) - else: - old = self.response - self.response = Response(result, old.lines + [line]) + def append_to_resp(self, line: bytes, result: str = 'Pending') -> None: + self._resp_result = result + self._resp_lines.append(line) self._reset_timer() async def wait(self) -> None: @@ -210,7 +212,7 @@ def flush(self) -> None: def _end_literal_data(self) -> None: self._expected_size = 0 - self._literal_data = None + self._resp_literal_data = bytearray() def _set_timer(self) -> None: if self._timeout is not None: @@ -218,7 +220,7 @@ def _set_timer(self) -> None: def _timeout_callback(self) -> None: self._exception = CommandTimeout(self) - self.close(str(self._exception), 'KO') + self.close(str(self._exception).encode(), 'KO') def _reset_timer(self) -> None: self._timer.cancel() @@ -226,7 +228,7 @@ def _reset_timer(self) -> None: class FetchCommand(Command): - FETCH_MESSAGE_DATA_RE = re.compile(r'[0-9]+ FETCH \(') + FETCH_MESSAGE_DATA_RE = re.compile(rb'[0-9]+ FETCH \(') def __init__(self, tag: str, *args, prefix: str = None, untagged_resp_name: str = None, loop: asyncio.AbstractEventLoop = None, timeout: float = None) -> None: @@ -234,18 +236,16 @@ def __init__(self, tag: str, *args, prefix: str = None, untagged_resp_name: str loop=loop, timeout=timeout) def wait_data(self) -> bool: - if self.response is None: - return False last_fetch_index = 0 - for index, line in enumerate(self.response.lines): - if isinstance(line, str) and self.FETCH_MESSAGE_DATA_RE.match(line): + for index, line in enumerate(self._resp_lines): + if isinstance(line, bytes) and self.FETCH_MESSAGE_DATA_RE.match(line): last_fetch_index = index - return not matched_parenthesis(''.join(filter(lambda l: isinstance(l, str), + return not matched_parenthesis(b''.join(filter(lambda l: isinstance(l, bytes), self.response.lines[last_fetch_index:]))) -def matched_parenthesis(string: str) -> bool: - return string.count('(') == string.count(')') +def matched_parenthesis(fetch_response: bytes) -> bool: + return fetch_response.count(b'(') == fetch_response.count(b')') class IdleCommand(Command): @@ -254,9 +254,9 @@ def __init__(self, tag: str, queue: asyncio.Queue, *args, prefix: str = None, un super().__init__('IDLE', tag, *args, prefix=prefix, untagged_resp_name=untagged_resp_name, loop=loop, timeout=timeout) self.queue = queue - self.buffer = list() + self.buffer: List[bytes] = list() - def append_to_resp(self, line: str, result: str = 'Pending') -> None: + def append_to_resp(self, line: bytes, result: str = 'Pending') -> None: if result != 'Pending': super().append_to_resp(line, result) else: @@ -309,8 +309,8 @@ async def wrapper(self, *args, **kargs) -> Optional[Response]: # cf https://tools.ietf.org/html/rfc3501#section-9 # untagged responses types literal_data_re = re.compile(rb'.*\{(?P\d+)\}$') -message_data_re = re.compile(r'[0-9]+ ((FETCH)|(EXPUNGE))') -tagged_status_response_re = re.compile(r'[A-Z0-9]+ ((OK)|(NO)|(BAD))') +message_data_re = re.compile(rb'[0-9]+ ((FETCH)|(EXPUNGE))') +tagged_status_response_re = re.compile(rb'[A-Z0-9]+ ((OK)|(NO)|(BAD))') class IMAP4ClientProtocol(asyncio.Protocol): @@ -352,7 +352,7 @@ def connection_lost(self, exc: Optional[Exception]) -> None: if self.conn_lost_cb is not None: self.conn_lost_cb(exc) - def _handle_responses(self, data: bytes, line_handler: Callable[[str, Command], None], current_cmd: Command = None) -> None: + def _handle_responses(self, data: bytes, line_handler: Callable[[bytes, Command], Optional[Command]], current_cmd: Command = None) -> None: if not data: if self.pending_sync_command is not None: self.pending_sync_command.flush() @@ -369,7 +369,7 @@ def _handle_responses(self, data: bytes, line_handler: Callable[[str, Command], if not separator: raise IncompleteRead(current_cmd, data) - cmd = line_handler(line.decode(), current_cmd) + cmd = line_handler(line, current_cmd) begin_literal = literal_data_re.match(line) if begin_literal: @@ -383,7 +383,7 @@ def _handle_responses(self, data: bytes, line_handler: Callable[[str, Command], else: self._handle_responses(tail, line_handler) - def _handle_line(self, line: str, current_cmd: Command) -> Optional[Command]: + def _handle_line(self, line: bytes, current_cmd: Command) -> Optional[Command]: if not line: return @@ -394,9 +394,9 @@ def _handle_line(self, line: str, current_cmd: Command) -> Optional[Command]: elif current_cmd is not None: current_cmd.append_to_resp(line) return current_cmd - elif line.startswith('*'): + elif line.startswith(b'*'): return self._untagged_response(line) - elif line.startswith('+'): + elif line.startswith(b'+'): self._continuation(line) else: log.info('unknown data received %s' % line) @@ -438,13 +438,13 @@ async def execute(self, command: Command) -> Response: return command.response @change_state - async def welcome(self, command) -> None: - if 'PREAUTH' in command: + async def welcome(self, command: bytes) -> None: + if b'PREAUTH' in command: self.state = AUTH - elif 'OK' in command: + elif b'OK' in command: self.state = NONAUTH else: - raise Error(command) + raise Error(command.decode()) await self.capability() @change_state @@ -455,8 +455,8 @@ async def login(self, user: str, password: str) -> Response: if 'OK' == response.result: self.state = AUTH for line in response.lines: - if 'CAPABILITY' in line: - self.capabilities = self.capabilities.union(set(line.replace('CAPABILITY', '').strip().split())) + if b'CAPABILITY' in line: + self.capabilities = self.capabilities.union(set(line.decode().replace('CAPABILITY', '').strip().split())) return response @change_state @@ -548,7 +548,7 @@ async def move(self, uid_set: str, mailbox: str, by_uid: bool = False) -> Respon async def capability(self) -> None: # that should be a Response (would avoid the Optional) response = await self.execute(Command('CAPABILITY', self.new_tag(), loop=self.loop)) - capability_list = response.lines[0].split() + capability_list = response.lines[0].decode().split() self.capabilities = set(capability_list) try: self.imap_version = list( @@ -597,8 +597,8 @@ async def wait(self, state_regexp: Pattern) -> None: async def wait_for_idle_response(self): await self._idle_event.wait() - def _untagged_response(self, line: str) -> Command: - line = line.replace('* ', '') + def _untagged_response(self, line: bytes) -> Command: + line = line.replace(b'* ', b'') if self.pending_sync_command is not None: self.pending_sync_command.append_to_resp(line) command = self.pending_sync_command @@ -607,8 +607,8 @@ def _untagged_response(self, line: str) -> Command: if match: cmd_name, text = match.group(1), match.string else: - cmd_name, _, text = line.partition(' ') - command = self.pending_async_commands.get(cmd_name.upper()) + cmd_name, _, text = line.partition(b' ') + command = self.pending_async_commands.get(cmd_name.decode().upper()) if command is not None: command.append_to_resp(text) else: @@ -620,18 +620,18 @@ def _untagged_response(self, line: str) -> Command: log.info('ignored untagged response : %s' % line) return command - def _response_done(self, line: str) -> None: + def _response_done(self, line: bytes) -> None: log.debug('tagged status %s' % line) - tag, _, response = line.partition(' ') + tag, _, response = line.partition(b' ') if self.pending_sync_command is not None: - if self.pending_sync_command.tag != tag: + if self.pending_sync_command.tag != tag.decode(): raise Abort('unexpected tagged response with pending sync command (%s) response: %s' % (self.pending_sync_command, response)) command = self.pending_sync_command self.pending_sync_command = None else: - cmds = self._find_pending_async_cmd_by_tag(tag) + cmds = self._find_pending_async_cmd_by_tag(tag.decode()) if len(cmds) == 0: raise Abort('unexpected tagged (%s) response: %s' % (tag, response)) elif len(cmds) > 1: @@ -639,10 +639,10 @@ def _response_done(self, line: str) -> None: command = cmds.pop() self.pending_async_commands.pop(command.untagged_resp_name) - response_result, _, response_text = response.partition(' ') - command.close(response_text, result=response_result) + response_result, _, response_text = response.partition(b' ') + command.close(response_text, result=response_result.decode()) - def _continuation(self, line: str) -> None: + def _continuation(self, line: bytes) -> None: if self.pending_sync_command is None: log.info('server says %s (ignored)' % line) elif self.pending_sync_command.name == 'APPEND': @@ -816,8 +816,8 @@ def has_capability(self, capability: str) -> bool: def extract_exists(response: Response) -> Optional[int]: for line in response.lines: - if 'EXISTS' in line: - return int(line.replace(' EXISTS', '')) + if b'EXISTS' in line: + return int(line.replace(b' EXISTS', b'').decode()) class IMAP4_SSL(IMAP4): diff --git a/aioimaplib/tests/test_acceptance_aioimaplib.py b/aioimaplib/tests/test_acceptance_aioimaplib.py index 982d2fd..443703b 100644 --- a/aioimaplib/tests/test_acceptance_aioimaplib.py +++ b/aioimaplib/tests/test_acceptance_aioimaplib.py @@ -42,4 +42,4 @@ async def test_file_with_attachement(self): result, data = await imap_client.fetch('1', '(RFC822)') self.assertEqual('OK', result) - self.assertEqual(['1 FETCH (RFC822 {418898}', mail.as_bytes(), ')', 'FETCH completed.'], data) + self.assertEqual([b'1 FETCH (RFC822 {418898}', mail.as_bytes(), b')', b'FETCH completed.'], data) diff --git a/aioimaplib/tests/test_aioimaplib.py b/aioimaplib/tests/test_aioimaplib.py index 4870505..1422b81 100644 --- a/aioimaplib/tests/test_aioimaplib.py +++ b/aioimaplib/tests/test_aioimaplib.py @@ -52,7 +52,7 @@ def test_split_responses_no_data(self): def test_split_responses_regular_lines(self): self.imap_protocol.data_received(b'* BYE Logging out\r\nCAPB2 OK LOGOUT completed\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* BYE Logging out', None), call('CAPB2 OK LOGOUT completed', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* BYE Logging out', None), call(b'CAPB2 OK LOGOUT completed', None)]) def test_split_responses_with_message_data(self): cmd = Command('FETCH', 'TAG') @@ -60,9 +60,9 @@ def test_split_responses_with_message_data(self): self.imap_protocol.data_received(b'* 1 FETCH (UID 1 RFC822 {26}\r\n...\r\n(mail content)\r\n...\r\n)\r\n' b'TAG OK FETCH completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 1 FETCH (UID 1 RFC822 {26}', None)]) - self.imap_protocol._handle_line.assert_has_calls([call(')', cmd)]) - self.imap_protocol._handle_line.assert_has_calls([call('TAG OK FETCH completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 1 FETCH (UID 1 RFC822 {26}', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b')', cmd)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'TAG OK FETCH completed.', None)]) self.assertEqual([b'...\r\n(mail content)\r\n...\r\n'], cmd.response.lines) def test_split_responses_with_two_messages_data(self): @@ -72,25 +72,25 @@ def test_split_responses_with_two_messages_data(self): b'* 4 FETCH (UID 4 RFC822 {6}\r\nmail 2)\r\n' b'TAG OK FETCH completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 3 FETCH (UID 3 RFC822 {6}', None), - call(')', cmd), - call('* 4 FETCH (UID 4 RFC822 {6}', None), - call(')', cmd), - call('TAG OK FETCH completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 3 FETCH (UID 3 RFC822 {6}', None), + call(b')', cmd), + call(b'* 4 FETCH (UID 4 RFC822 {6}', None), + call(b')', cmd), + call(b'TAG OK FETCH completed.', None)]) self.assertEqual([b'mail 1', b'mail 2'], cmd.response.lines) def test_split_responses_with_flag_fetch_message_data(self): self.imap_protocol.data_received(b'* 1 FETCH (UID 10 FLAGS (FOO))\r\n' b'* 1 FETCH (UID 15 FLAGS (BAR))\r\n' b'TAG OK STORE completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 1 FETCH (UID 10 FLAGS (FOO))', None), - call('* 1 FETCH (UID 15 FLAGS (BAR))', None), - call('TAG OK STORE completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 1 FETCH (UID 10 FLAGS (FOO))', None), + call(b'* 1 FETCH (UID 15 FLAGS (BAR))', None), + call(b'TAG OK STORE completed.', None)]) def test_split_responses_with_message_data_expunge(self): self.imap_protocol.data_received(b'* 123 EXPUNGE\r\nTAG OK SELECT completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 123 EXPUNGE', None), - call('TAG OK SELECT completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 123 EXPUNGE', None), + call(b'TAG OK SELECT completed.', None)]) def test_unconplete_line_with_litteral_fetch(self): cmd = Command('FETCH', 'TAG') @@ -98,10 +98,10 @@ def test_unconplete_line_with_litteral_fetch(self): self.imap_protocol.data_received(b'* 12 FETCH (BODY[HEADER] {4}\r\nyo\r\n)\r\n* 13 FETCH (BODY[') self.imap_protocol.data_received(b'HEADER] {5}\r\nyo2\r\n)\r\nTAG OK STORE completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 12 FETCH (BODY[HEADER] {4}', None), call(')', cmd)]) - self.imap_protocol._handle_line.assert_has_calls([call('* 13 FETCH (BODY[HEADER] {5}', None), - call(')', cmd), - call('TAG OK STORE completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 12 FETCH (BODY[HEADER] {4}', None), call(b')', cmd)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 13 FETCH (BODY[HEADER] {5}', None), + call(b')', cmd), + call(b'TAG OK STORE completed.', None)]) self.assertEqual([b'yo\r\n', b'yo2\r\n'], cmd.response.lines) def test_unconplete_lines_during_litteral(self): @@ -112,26 +112,26 @@ def test_unconplete_lines_during_litteral(self): self.imap_protocol.data_received(b'bar/') self.imap_protocol.data_received(b'baz\r\n* LIST () "/" qux\r\nTAG OK LIST completed\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* LIST () "/" {11}', None)]) - self.imap_protocol._handle_line.assert_has_calls([call('* LIST () "/" qux', None), - call('TAG OK LIST completed', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* LIST () "/" {11}', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* LIST () "/" qux', None), + call(b'TAG OK LIST completed', None)]) self.assertEqual([b'foo/bar/baz'], cmd.response.lines) def test_unconplete_line_during_litteral_no_cmd_found(self): self.imap_protocol.data_received(b'* LIST () "/" {7}\r\nfoo/') self.imap_protocol.data_received(b'bar\r\nTAG OK LIST completed\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* LIST () "/" {7}', None)]) - self.imap_protocol._handle_line.assert_has_calls([call('* LIST () "/" {7}', None), - call('', Command('NIL', 'unused')), - call('TAG OK LIST completed', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* LIST () "/" {7}', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* LIST () "/" {7}', None), + call(b'', Command('NIL', 'unused')), + call(b'TAG OK LIST completed', None)]) def test_line_with_litteral_no_cmd_found_no_AttributeError_thrown(self): self.imap_protocol.data_received(b'* 3 FETCH (UID 12 RFC822 {4}\r\nmail)\r\n' b'TAG OK FETCH completed.\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 3 FETCH (UID 12 RFC822 {4}', None), - call(')', Command('NIL', 'unused')), - call('TAG OK FETCH completed.', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 3 FETCH (UID 12 RFC822 {4}', None), + call(b')', Command('NIL', 'unused')), + call(b'TAG OK FETCH completed.', None)]) def test_line_with_attachment_litterals(self): cmd = Command('FETCH', 'TAG') @@ -142,11 +142,11 @@ def test_line_with_attachment_litterals(self): b' "" NIL "quoted-printable" 365 14 NIL ' b'("attachment" ("filename" {16}\r\nG\xe9n\xe9ration 3.ics)))\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 46 FETCH (UID 46 FLAGS () BODYSTRUCTURE (' - '("text" "calendar" ("charset" "UTF-8" "name" {16}', None), - call(') "" NIL "quoted-printable" 365 14 NIL ' - '("attachment" ("filename" {16}', cmd), - call(')))', cmd)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 46 FETCH (UID 46 FLAGS () BODYSTRUCTURE (' + b'("text" "calendar" ("charset" "UTF-8" "name" {16}', None), + call(b') "" NIL "quoted-printable" 365 14 NIL ' + b'("attachment" ("filename" {16}', cmd), + call(b')))', cmd)]) self.assertEqual([b'G\xe9n\xe9ration 3.ics', b'G\xe9n\xe9ration 3.ics'], cmd.response.lines) def test_uncomplete_line_followed_by_uncomplete_literal(self): @@ -157,8 +157,8 @@ def test_uncomplete_line_followed_by_uncomplete_literal(self): self.imap_protocol.data_received(b'FLAGS () UID 160016 BODY[] {10}\r\non the ') self.imap_protocol.data_received(b'dot)\r\nTAG OK FETCH completed\r\n') - self.imap_protocol._handle_line.assert_has_calls([call('* 2 FETCH (FLAGS () UID 160016 BODY[] {10}', None), - call(')', cmd), call('TAG OK FETCH completed', None)]) + self.imap_protocol._handle_line.assert_has_calls([call(b'* 2 FETCH (FLAGS () UID 160016 BODY[] {10}', None), + call(b')', cmd), call(b'TAG OK FETCH completed', None)]) self.assertEqual([b'on the dot'], cmd.response.lines) # cf 1st FETCH in https://tools.ietf.org/html/rfc3501#section-8 example @@ -167,15 +167,15 @@ def test_uncomplete_fetch_message_attributes_without_literal(self): self.imap_protocol._handle_line = MagicMock(return_value=cmd) line = b'* 12 FETCH (FLAGS (\Seen) BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 \r\n' - cmd.append_to_resp(line.decode()) + cmd.append_to_resp(line) self.imap_protocol.data_received(line) line = b'92))\r\nTAG OK FETCH completed\r\n' - cmd.append_to_resp(line.decode()) + cmd.append_to_resp(line) self.imap_protocol.data_received(line) self.imap_protocol._handle_line.assert_has_calls( - [call('* 12 FETCH (FLAGS (\Seen) BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 ', None), - call('92))', cmd), call('TAG OK FETCH completed', None)]) + [call(b'* 12 FETCH (FLAGS (\Seen) BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 ', None), + call(b'92))', cmd), call(b'TAG OK FETCH completed', None)]) def test_uncomplete_fetch_with_uncomplete_line(self): cmd = FetchCommand('TAG') @@ -185,8 +185,8 @@ def test_uncomplete_fetch_with_uncomplete_line(self): self.imap_protocol.data_received(b')\r\nTAG OK FETCH completed\r\n') self.imap_protocol._handle_line.assert_has_calls( - [call('* 21 FETCH (FLAGS (\Seen) BODY[] {16}', None), - call(')', cmd), call('TAG OK FETCH completed', None)]) + [call(b'* 21 FETCH (FLAGS (\Seen) BODY[] {16}', None), + call(b')', cmd), call(b'TAG OK FETCH completed', None)]) def test_command_repr(self): self.assertEqual('tag NAME', str(Command('NAME', 'tag'))) @@ -206,7 +206,7 @@ def test_when_idle_continuation_line_in_same_dataframe_as_status_update(self): self.imap_protocol.data_received(b'+ idling\r\n* 1 EXISTS\r\n* 1 RECENT\r\n') self.assertTrue(self.imap_protocol._idle_event.is_set()) - self.assertEqual(['1 EXISTS', '1 RECENT'], queue.get_nowait()) + self.assertEqual([b'1 EXISTS', b'1 RECENT'], queue.get_nowait()) class TestFetchWaitsForAllMessageAttributes(unittest.TestCase): @@ -215,35 +215,35 @@ def test_empty_fetch(self): def test_simple_fetch(self): fetch = FetchCommand('TAG') - fetch.append_to_resp('12 FETCH (FLAGS (\Seen))') + fetch.append_to_resp(b'12 FETCH (FLAGS (\Seen))') self.assertFalse(fetch.wait_data()) def test_simple_fetch_with_two_lines(self): fetch = FetchCommand('TAG') - fetch.append_to_resp('12 FETCH (FLAGS (\Seen) BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028') + fetch.append_to_resp(b'12 FETCH (FLAGS (\Seen) BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028') self.assertTrue(fetch.wait_data()) - fetch.append_to_resp('92))') + fetch.append_to_resp(b'92))') self.assertFalse(fetch.wait_data()) def test_fetch_with_litteral(self): fetch = FetchCommand('TAG') - fetch.append_to_resp('12 FETCH (FLAGS () BODY[] {13}') + fetch.append_to_resp(b'12 FETCH (FLAGS () BODY[] {13}') fetch.begin_literal_data(13, b'literal (data') - fetch.append_to_resp(')') + fetch.append_to_resp(b')') self.assertFalse(fetch.wait_data()) def test_fetch_only_the_last_message_data(self): fetch = FetchCommand('TAG') - fetch.append_to_resp('12 FETCH (FLAGS (\Seen)') # not closed on purpose + fetch.append_to_resp(b'12 FETCH (FLAGS (\Seen)') # not closed on purpose self.assertTrue(fetch.wait_data()) - fetch.append_to_resp('13 FETCH (FLAGS (\Seen)') + fetch.append_to_resp(b'13 FETCH (FLAGS (\Seen)') self.assertTrue(fetch.wait_data()) - fetch.append_to_resp(')') + fetch.append_to_resp(b')') self.assertFalse(fetch.wait_data()) @@ -324,8 +324,8 @@ async def test_command_timeout_while_receiving_data(self): class AioWithImapServer(WithImapServer): - async def login_user(self, login, password, select=False, lib=aioimaplib.IMAP4): - imap_client = lib(port=12345, loop=self.loop, timeout=3) + async def login_user(self, login, password, select=False, lib=aioimaplib.IMAP4, timeout=3): + imap_client = lib(port=12345, loop=self.loop, timeout=timeout) await asyncio.wait_for(imap_client.wait_hello_from_server(), 2) await imap_client.login(login, password) @@ -375,7 +375,7 @@ async def tearDown(self): async def test_capabilities_allowed_versions(self): with self.assertRaises(asyncio.TimeoutError): with self.assertRaises(aioimaplib.Error) as expected: - await self.login_user('user', 'pass') + await self.login_user('user', 'pass', timeout=1) self.assertEqual(expected.exception.args, ('server not IMAP4 compliant',)) @@ -403,7 +403,7 @@ async def test_login(self): self.assertEquals(aioimaplib.AUTH, imap_client.protocol.state) self.assertEqual('OK', result) - self.assertEqual('LOGIN completed', data[-1]) + self.assertEqual(b'LOGIN completed', data[-1]) self.assertTrue(imap_client.has_capability('IDLE')) self.assertTrue(imap_client.has_capability('UIDPLUS')) @@ -415,9 +415,7 @@ async def test_login_with_special_characters(self): self.assertEquals(aioimaplib.AUTH, imap_client.protocol.state) self.assertEqual('OK', result) - self.assertEqual('LOGIN completed', data[-1]) - self.assertTrue(imap_client.has_capability('IDLE')) - self.assertTrue(imap_client.has_capability('UIDPLUS')) + self.assertEqual(b'LOGIN completed', data[-1]) async def test_login_twice(self): with self.assertRaises(aioimaplib.Error) as expected: @@ -433,7 +431,7 @@ async def test_logout(self): result, data = await imap_client.logout() self.assertEqual('OK', result) - self.assertEqual(['BYE Logging out', 'LOGOUT completed'], data) + self.assertEqual([b'BYE Logging out', b'LOGOUT completed'], data) self.assertEquals(aioimaplib.LOGOUT, imap_client.protocol.state) async def test_select_no_messages(self): @@ -460,7 +458,7 @@ async def test_search_two_messages(self): result, data = await imap_client.search('ALL') self.assertEqual('OK', result) - self.assertEqual('1 2', data[0]) + self.assertEqual(b'1 2', data[0]) async def test_uid_with_illegal_command(self): imap_client = await self.login_user('user', 'pass', select=True) @@ -478,11 +476,11 @@ async def test_search_three_messages_by_uid(self): self.imapserver.receive(Mail.create(['user']), mailbox='OTHER_MAILBOX') # id=1 uid=1 self.imapserver.receive(Mail.create(['user'])) # id=2 uid=2 - self.assertEqual('1 2', (await imap_client.search('ALL')).lines[0]) - self.assertEqual('1 2', (await imap_client.uid_search('ALL')).lines[0]) + self.assertEqual(b'1 2', (await imap_client.search('ALL')).lines[0]) + self.assertEqual(b'1 2', (await imap_client.uid_search('ALL')).lines[0]) await imap_client.select('OTHER_MAILBOX') - self.assertEqual('1', (await imap_client.uid_search('ALL')).lines[0]) + self.assertEqual(b'1', (await imap_client.uid_search('ALL')).lines[0]) async def test_fetch(self): print('test loop %r' % self.loop) @@ -496,8 +494,8 @@ async def test_fetch(self): self.assertEqual('OK', result) self.assertEqual([ - '1 FETCH (RFC822 {%s}' % len(content), content, ')', - 'FETCH completed.' + b'1 FETCH (RFC822 {%d}' % len(content), content, b')', + b'FETCH completed.' ], data) async def test_fetch_by_uid_without_body(self): @@ -509,7 +507,7 @@ async def test_fetch_by_uid_without_body(self): response = (await imap_client.uid('fetch', '1', '(UID FLAGS)')) self.assertEqual('OK', response.result) - self.assertEquals('1 FETCH (UID 1 FLAGS ())', response.lines[0]) + self.assertEquals(b'1 FETCH (UID 1 FLAGS ())', response.lines[0]) async def test_fetch_by_uid(self): imap_client = await self.login_user('user', 'pass', select=True) @@ -527,10 +525,10 @@ async def test_idle(self): idle = await imap_client.idle_start(timeout=0.3) self.imapserver.receive(Mail.create(to=['user'], mail_from='me', subject='hello')) - self.assertEquals(['1 EXISTS', '1 RECENT'], (await imap_client.wait_server_push())) + self.assertEquals([b'1 EXISTS', b'1 RECENT'], (await imap_client.wait_server_push())) imap_client.idle_done() - self.assertEquals(('OK', ['IDLE terminated']), (await asyncio.wait_for(idle, 1))) + self.assertEquals(('OK', [b'IDLE terminated']), (await asyncio.wait_for(idle, 1))) self.assertTrue(imap_client._idle_waiter._cancelled) with self.assertRaises(asyncio.TimeoutError): @@ -549,7 +547,7 @@ async def test_idle_loop(self): imap_client.idle_done() await asyncio.wait_for(idle, 1) - self.assertEqual([['1 EXISTS', '1 RECENT'], STOP_WAIT_SERVER_PUSH], data) + self.assertEqual([[b'1 EXISTS', b'1 RECENT'], STOP_WAIT_SERVER_PUSH], data) async def test_idle_stop(self): imap_client = await self.login_user('user', 'pass', select=True) @@ -582,19 +580,19 @@ async def test_store_and_search_by_keyword(self): self.imapserver.receive(Mail.create(['user'])) self.imapserver.receive(Mail.create(['user'])) imap_client = await self.login_user('user', 'pass', select=True) - self.assertEqual('', (await imap_client.uid_search('KEYWORD FOO', charset=None)).lines[0]) + self.assertEqual(b'', (await imap_client.uid_search('KEYWORD FOO', charset=None)).lines[0]) self.assertEquals('OK', (await imap_client.uid('store', '1', '+FLAGS (FOO)')).result) - self.assertEqual('1', (await imap_client.uid_search('KEYWORD FOO', charset=None)).lines[0]) - self.assertEqual('2', (await imap_client.uid_search('UNKEYWORD FOO', charset=None)).lines[0]) + self.assertEqual(b'1', (await imap_client.uid_search('KEYWORD FOO', charset=None)).lines[0]) + self.assertEqual(b'2', (await imap_client.uid_search('UNKEYWORD FOO', charset=None)).lines[0]) async def test_expunge_messages(self): self.imapserver.receive(Mail.create(['user'])) self.imapserver.receive(Mail.create(['user'])) imap_client = await self.login_user('user', 'pass', select=True) - self.assertEquals(('OK', ['1 EXPUNGE', '2 EXPUNGE', 'EXPUNGE completed.']), (await imap_client.expunge())) + self.assertEquals(('OK', [b'1 EXPUNGE', b'2 EXPUNGE', b'EXPUNGE completed.']), (await imap_client.expunge())) self.assertEquals(0, extract_exists((await imap_client.select()))) @@ -650,7 +648,7 @@ async def test_concurrency_3_executing_async_commands_in_parallel(self): await asyncio.wait([store, copy, expunge]) self.assertEquals(0, extract_exists((await imap_client.select()))) self.assertEquals(1, extract_exists((await imap_client.select('MBOX')))) - self.assertEqual('1', (await imap_client.search('KEYWORD FOO', charset=None)).lines[0]) + self.assertEqual(b'1', (await imap_client.search('KEYWORD FOO', charset=None)).lines[0]) async def test_concurrency_4_sync_command_waits_for_async_commands_to_finish(self): self.imapserver.receive(Mail.create(['user'])) @@ -664,67 +662,67 @@ async def test_concurrency_4_sync_command_waits_for_async_commands_to_finish(sel async def test_noop(self): imap_client = await self.login_user('user', 'pass') - self.assertEquals(('OK', ['NOOP completed.']), (await imap_client.noop())) + self.assertEquals(('OK', [b'NOOP completed.']), (await imap_client.noop())) async def test_noop_with_untagged_data(self): imap_client = await self.login_user('user', 'pass') self.imapserver.receive(Mail.create(['user'])) - self.assertEquals(('OK', ['1 EXISTS', '1 RECENT', 'NOOP completed.']), (await imap_client.noop())) + self.assertEquals(('OK', [b'1 EXISTS', b'1 RECENT', b'NOOP completed.']), (await imap_client.noop())) async def test_check(self): imap_client = await self.login_user('user', 'pass', select=True) - self.assertEquals(('OK', ['CHECK completed.']), (await imap_client.check())) + self.assertEquals(('OK', [b'CHECK completed.']), (await imap_client.check())) async def test_close(self): imap_client = await self.login_user('user', 'pass', select=True) self.assertEquals(imapserver.SELECTED, self.imapserver.get_connection('user').state) - self.assertEquals(('OK', ['CLOSE completed.']), (await imap_client.close())) + self.assertEquals(('OK', [b'CLOSE completed.']), (await imap_client.close())) self.assertEquals(imapserver.AUTH, self.imapserver.get_connection('user').state) async def test_status(self): imap_client = await self.login_user('user', 'pass') - self.assertEquals('INBOX (MESSAGES 0 UIDNEXT 1)', + self.assertEquals(b'INBOX (MESSAGES 0 UIDNEXT 1)', (await imap_client.status('INBOX', '(MESSAGES UIDNEXT)')).lines[0]) async def test_subscribe_unsubscribe_lsub(self): imap_client = await self.login_user('user', 'pass') - self.assertEquals(('OK', ['SUBSCRIBE completed.']), (await imap_client.subscribe('#fr.soc.feminisme'))) - self.assertEquals(('OK', ['() "." #fr.soc.feminisme', 'LSUB completed.']), + self.assertEquals(('OK', [b'SUBSCRIBE completed.']), (await imap_client.subscribe('#fr.soc.feminisme'))) + self.assertEquals(('OK', [b'() "." #fr.soc.feminisme', b'LSUB completed.']), (await imap_client.lsub('#fr.', 'soc.*'))) - self.assertEquals(('OK', ['UNSUBSCRIBE completed.']), (await imap_client.unsubscribe('#fr.soc.feminisme'))) - self.assertEquals(('OK', ['LSUB completed.']), (await imap_client.lsub('#fr', '.*'))) + self.assertEquals(('OK', [b'UNSUBSCRIBE completed.']), (await imap_client.unsubscribe('#fr.soc.feminisme'))) + self.assertEquals(('OK', [b'LSUB completed.']), (await imap_client.lsub('#fr', '.*'))) async def test_create_delete_mailbox(self): imap_client = await self.login_user('user', 'pass') self.assertEquals('NO', (await imap_client.status('MBOX', '(MESSAGES)')).result) - self.assertEquals(('OK', ['CREATE completed.']), (await imap_client.create('MBOX'))) + self.assertEquals(('OK', [b'CREATE completed.']), (await imap_client.create('MBOX'))) self.assertEquals('OK', (await imap_client.status('MBOX', '(MESSAGES)')).result) - self.assertEquals(('OK', ['DELETE completed.']), (await imap_client.delete('MBOX'))) + self.assertEquals(('OK', [b'DELETE completed.']), (await imap_client.delete('MBOX'))) self.assertEquals('NO', (await imap_client.status('MBOX', '(MESSAGES)')).result) async def test_rename_mailbox(self): imap_client = await self.login_user('user', 'pass') self.assertEquals('NO', (await imap_client.status('MBOX', '(MESSAGES)')).result) - self.assertEquals(('OK', ['RENAME completed.']), (await imap_client.rename('INBOX', 'MBOX'))) + self.assertEquals(('OK', [b'RENAME completed.']), (await imap_client.rename('INBOX', 'MBOX'))) self.assertEquals('OK', (await imap_client.status('MBOX', '(MESSAGES)')).result) async def test_list(self): imap_client = await self.login_user('user', 'pass') - self.assertEquals(('OK', ['() "/" Drafts', '() "/" INBOX', '() "/" Sent', '() "/" Trash', - 'LIST completed.']), (await imap_client.list('""', '.*'))) + self.assertEquals(('OK', [b'() "/" Drafts', b'() "/" INBOX', b'() "/" Sent', b'() "/" Trash', + b'LIST completed.']), (await imap_client.list('""', '.*'))) await imap_client.create('MYBOX') - self.assertEquals(('OK', ['() "/" Drafts', '() "/" INBOX', '() "/" MYBOX', '() "/" Sent', '() "/" Trash', - 'LIST completed.']), + self.assertEquals(('OK', [b'() "/" Drafts', b'() "/" INBOX', b'() "/" MYBOX', b'() "/" Sent', b'() "/" Trash', + b'LIST completed.']), (await imap_client.list('""', '.*'))) async def test_append(self): @@ -735,7 +733,7 @@ async def test_append(self): response = await imap_client.append(msg.as_bytes(), mailbox='INBOX', flags='FOO BAR', date=datetime.now(tz=utc), ) self.assertEquals('OK', response.result) - self.assertTrue('1] APPEND completed' in response.lines[0]) + self.assertTrue(b'1] APPEND completed' in response.lines[0]) self.assertEquals(1, extract_exists((await imap_client.examine('INBOX')))) @@ -745,15 +743,15 @@ async def test_rfc5032_within(self): self.imapserver.receive(Mail.create(['user'])) # 3 imap_client = await self.login_user('user', 'pass', select=True) - self.assertEquals('1', (await imap_client.search('OLDER', '84700')).lines[0]) - self.assertEquals('2 3', (await imap_client.search('YOUNGER', '84700')).lines[0]) + self.assertEquals(b'1', (await imap_client.search('OLDER', '84700')).lines[0]) + self.assertEquals(b'2 3', (await imap_client.search('YOUNGER', '84700')).lines[0]) async def test_rfc4315_uidplus_expunge(self): self.imapserver.receive(Mail.create(['user'])) self.imapserver.receive(Mail.create(['user'])) imap_client = await self.login_user('user', 'pass', select=True) - self.assertEquals(('OK', ['1 EXPUNGE', 'UID EXPUNGE completed.']), (await imap_client.uid('expunge', '1:1'))) + self.assertEquals(('OK', [b'1 EXPUNGE', b'UID EXPUNGE completed.']), (await imap_client.uid('expunge', '1:1'))) self.assertEquals(1, extract_exists((await imap_client.select()))) @@ -762,7 +760,7 @@ async def test_rfc6851_move(self): imap_client = await self.login_user('user', 'pass', select=True) uidvalidity = self.imapserver.get_connection('user').uidvalidity - self.assertEqual(('OK', ['OK [COPYUID %d 1:1 1:1]' % uidvalidity, '1 EXPUNGE', 'Done']), + self.assertEqual(('OK', [b'OK [COPYUID %d 1:1 1:1]' % uidvalidity, b'1 EXPUNGE', b'Done']), (await imap_client.move('1:1', 'Trash'))) self.assertEquals(0, extract_exists((await imap_client.select()))) @@ -773,7 +771,7 @@ async def test_rfc6851_uidmove(self): imap_client = await self.login_user('user', 'pass', select=True) uidvalidity = self.imapserver.get_connection('user').uidvalidity - self.assertEqual(('OK', ['OK [COPYUID %d 1:1 1:1]' % uidvalidity, '1 EXPUNGE', 'Done']), + self.assertEqual(('OK', [b'OK [COPYUID %d 1:1 1:1]' % uidvalidity, b'1 EXPUNGE', b'Done']), (await imap_client.uid('move', '1:1', 'Trash'))) self.assertEquals(0, extract_exists((await imap_client.select()))) @@ -782,19 +780,19 @@ async def test_rfc6851_uidmove(self): async def test_rfc5161_enable(self): imap_client = await self.login_user('user', 'pass') - self.assertEqual(('OK', ['X-GOOD-IDEA CONDSTORE enabled']), + self.assertEqual(('OK', [b'X-GOOD-IDEA CONDSTORE enabled']), (await imap_client.enable('X-GOOD-IDEA CONDSTORE'))) async def test_rfc2342_namespace(self): imap_client = await self.login_user('user', 'pass') response = await imap_client.namespace() - self.assertEqual(('OK', ['(("" "/")) NIL NIL', 'NAMESPACE command completed']), response) + self.assertEqual(('OK', [b'(("" "/")) NIL NIL', b'NAMESPACE command completed']), response) async def test_rfc2971_id(self): imap_client = await self.login_user('user', 'pass') response = await imap_client.id() - self.assertEqual(('OK', ['ID command completed']), response) + self.assertEqual(('OK', [b'ID command completed']), response) async def test_race_idle_done_and_server_push(self): imap_client = await self.login_user('user', 'pass', select=True) @@ -809,7 +807,7 @@ async def test_race_idle_done_and_server_push(self): await asyncio.wait_for(idle, 1) r = await imap_client.wait_server_push() - self.assertEqual(['1 EXISTS', '1 RECENT'], r) + self.assertEqual([b'1 EXISTS', b'1 RECENT'], r) self.assertTrue(imap_client.protocol.idle_queue.empty()) @@ -903,55 +901,54 @@ async def test_idle_start__exits_queueget_without_timeout_error(self): r = await asyncio.wait_for(push_task, 0) self.assertEqual(STOP_WAIT_SERVER_PUSH, r) - @asyncio.coroutine - def test_idle_start__exits_queueget_with_keepalive_without_timeout_error(self): - imap_client = yield from self.login_user('user', 'pass', select=True) + async def test_idle_start__exits_queueget_with_keepalive_without_timeout_error(self): + imap_client = await self.login_user('user', 'pass', select=True) # Idle long enough for the server to issue a keep-alive server_idle_timeout = imapserver.ImapProtocol.IDLE_STILL_HERE_PERIOD_SECONDS idle_timeout = server_idle_timeout + 1 - idle = yield from imap_client.idle_start(idle_timeout) + idle = await imap_client.idle_start(idle_timeout) push_task = asyncio.ensure_future(imap_client.wait_server_push(server_idle_timeout - 1)) # Advance time until we've received a keep-alive from server - yield from self.advance(server_idle_timeout) + await self.advance(server_idle_timeout) # The original push task timed out with self.assertRaises(asyncio.TimeoutError): - yield from asyncio.wait_for(push_task, 0.1) + await asyncio.wait_for(push_task, 0.1) # Read the keepalive from the server - r = yield from imap_client.wait_server_push(0.1) - self.assertEqual(["OK Still here"], r) + r = await imap_client.wait_server_push(0.1) + self.assertEqual([b'OK Still here'], r) # Advance the clock to the client timeout (idle waiter triggers) - yield from self.advance(1) + await self.advance(1) imap_client.idle_done() - r = yield from asyncio.wait_for(idle, 1) + r = await asyncio.wait_for(idle, 1) self.assertEqual("OK", r.result) self.assertFalse(imap_client.protocol._idle_event.is_set()) # Start another idle period - idle = yield from imap_client.idle_start(idle_timeout) - yield from self.advance(1) + idle = await imap_client.idle_start(idle_timeout) + await self.advance(1) # Read 'stop_wait_server_push' push_task = asyncio.ensure_future(imap_client.wait_server_push(0.1)) - yield from self.advance(1) - r = yield from asyncio.wait_for(push_task, None) + await self.advance(1) + r = await asyncio.wait_for(push_task, None) self.assertEqual(STOP_WAIT_SERVER_PUSH, r) # There shouldn't be anything left in the queue (no '+ idling') with self.assertRaises(asyncio.TimeoutError): push_task = asyncio.ensure_future(imap_client.wait_server_push(0.1)) - yield from self.advance(1) - yield from asyncio.wait_for(push_task, 0.1) + await self.advance(1) + await asyncio.wait_for(push_task, 0.1) imap_client.idle_done() - yield from asyncio.wait_for(idle, 1) + await asyncio.wait_for(idle, 1) class TestAioimaplibCallback(AioWithImapServer, asynctest.TestCase): diff --git a/aioimaplib/tests/test_imapserver_aioimaplib.py b/aioimaplib/tests/test_imapserver_aioimaplib.py index 79aaeda..d1819d1 100644 --- a/aioimaplib/tests/test_imapserver_aioimaplib.py +++ b/aioimaplib/tests/test_imapserver_aioimaplib.py @@ -42,7 +42,7 @@ async def test_append_too_long(self): Command('APPEND', imap_client.protocol.new_tag(), *args, loop=self.loop) ) self.assertEquals('BAD', response.result) - self.assertTrue('expected CRLF but got' in response.lines[0]) + self.assertTrue(b'expected CRLF but got' in response.lines[0]) async def test_append_too_short(self): imap_client = await self.login_user('user@mail', 'pass') @@ -56,4 +56,4 @@ async def test_append_too_short(self): Command('APPEND', imap_client.protocol.new_tag(), *args, loop=self.loop) ) self.assertEquals('BAD', response.result) - self.assertTrue('expected 30 but was' in response.lines[0]) + self.assertTrue(b'expected 30 but was' in response.lines[0])