Code Monkey home page Code Monkey logo

aioimaplib's Introduction

About

Build status

image

This library is inspired by imaplib and imaplib2 from Piers Lauder, Nicolas Sebrecht, Sebastian Spaeth. Some utilities functions are taken from imaplib/imaplib2 thanks to them.

The aim is to port the imaplib with asyncio, to benefit from the sleep or treat model.

It runs with python 3.7, 3.8, 3.9, 3.10.

Example

import asyncio
from aioimaplib import aioimaplib


async def check_mailbox(host, user, password):
    imap_client = aioimaplib.IMAP4_SSL(host=host)
    await imap_client.wait_hello_from_server()

    await imap_client.login(user, password)

    res, data = await imap_client.select()
    print('there is %s messages INBOX' % data[0])

    await imap_client.logout()


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(check_mailbox('my.imap.server', 'user', 'pass'))

Beware that the IMAP4.close() function is an IMAP function that is closing the selected mailbox, thus passing from SELECTED state to AUTH state. It does not close the TCP connection. The way to close TCP connection properly is to logout.

IDLE command

The RFC2177 is implemented, to be able to wait for new mail messages without using CPU. The responses are pushed in an async queue, and it is possible to read them in real time. To leave the IDLE mode, it is necessary to send a "DONE" command to the server.

async def wait_for_new_message(host, user, password):
    imap_client = aioimaplib.IMAP4_SSL(host=host)
    await imap_client.wait_hello_from_server()

    await imap_client.login(user, password)
    await imap_client.select()

    idle = await imap_client.idle_start(timeout=10)
    while imap_client.has_pending_idle():
        msg = await imap_client.wait_server_push()
        print(msg)
        if msg == STOP_WAIT_SERVER_PUSH:
            imap_client.idle_done()
            await asyncio.wait_for(idle, 1)

    await imap_client.logout()

if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.run_until_complete(wait_for_new_message('my.imap.server', 'user', 'pass'))

Or in a more event based style (the IDLE command is closed at each message from server):

async def idle_loop(host, user, password):
   imap_client = aioimaplib.IMAP4_SSL(host=host, timeout=30)
   await imap_client.wait_hello_from_server()

   await imap_client.login(user, password)
   await imap_client.select()

   while True:
      print((await imap_client.uid('fetch', '1:*', 'FLAGS')))

      idle = await imap_client.idle_start(timeout=60)
      print((await imap_client.wait_server_push()))

      imap_client.idle_done()
      await asyncio.wait_for(idle, 30)

Threading

The IMAP4ClientProtocol class is not thread safe, it uses asyncio.Event and asyncio.Condition that are not thread safe, and state change for pending commands is not locked.

It is possible to use threads but each IMAP4ClientProtocol instance should run in the same thread:

image

Each color rectangle is an IMAP4ClientProtocol instance piece of code executed by the thread asyncio loop until it reaches a yield, waiting on I/O.

For example, it is possible to launch 4 mono-threaded mail-fetcher processes on a 4 cores server with supervisor, and use a distribution function like len(email) % (process_num) or whatever to share equally a mail account list between the 4 processes.

IMAP command concurrency

IMAP protocol allows to run some commands in parallel. Four rules are implemented to ensure responses consistency:

  1. if a sync command is running, the following requests (sync or async) must wait
  2. if an async command is running, same async commands (or with the same untagged response type) must wait
  3. async commands can be executed in parallel
  4. sync command must wait pending async commands to finish

Logging

As said in the logging howto the logger is defined with

logger = logging.getLogger(__name__)

Where name is 'aioimaplib.aioimaplib'. You can set the logger parameters, either by python API

aioimaplib_logger = logging.getLogger('aioimaplib.aioimaplib')
sh = logging.StreamHandler()
sh.setLevel(logging.DEBUG)
sh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s [%(module)s:%(lineno)d] %(message)s"))
aioimaplib_logger.addHandler(sh)

Or loading config file (for example with logging.config.dictConfig(yaml.load(file))) with this piece of yaml file

loggers:
...
  aioimaplib.aioimaplib:
    level: DEBUG
    handlers: [syslog]
    propagate: no
...

Authentication with OAuth2

Starting with the 01/01/23 Microsoft Outlook can only be accessed with OAuth2. You need to register you client to be used with oauth. Find more :https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth:here.

This might be also used with Google Mail, but it is not tested for it.

Tested with

  • dovecot 2.2.13 on debian Jessie
  • gmail with imap and SSL
  • outlook with SSL
  • yahoo with SSL
  • free.fr with SSL
  • orange.fr with SSL
  • mailden.net with SSL

Develop

Developers are welcome! If you want to improve it, fix bugs, test it with other IMAP servers, give feedback, thank you for it.

To develop, just run

virtualenv --python=python3.4 venv
source venv/bin/activate
python setup.py develop
pip install -r dev-requirements.txt
pytest

To add an imaplib or imaplib2 command you can :

  • add the function to the testing imapserver with a new imaplib or imaplib2 server test, i.e. test_imapserver_imaplib.py or test_imapserver_imaplib2.py respectively;
  • then add the function to the aioimaplib doing almost the same test than above but the async way in test_aioimaplib.py.

Not unit tested

  • PREAUTH

TODO

  • 23/25 IMAP4rev1 commands are implemented from the main rfc3501. 'STARTTLS' and 'AUTHENTICATE'(except with XOAUTH2) are still missing.
  • 'COMPRESS' from rfc4978
  • 'SETACL' 'DELETEACL' 'GETACL' 'MYRIGHTS' 'LISTRIGHTS' from ACL rfc4314
  • 'GETQUOTA': 'GETQUOTAROOT': 'SETQUOTA' from quota rfc2087
  • 'SORT' and 'THREAD' from the rfc5256
  • 'ID' from the rfc2971
  • 'NAMESPACE' from rfc2342
  • 'CATENATE' from rfc4469
  • tests with other servers

If it goes wrong

Sometimes you break things and you don't understand what's going on (I always do). For this library I have two related tools:

  • ngrep on the imap test port: sudo ngrep -d lo port 12345
  • activate debug logs changing INFO to DEBUG at the top of the mock server and the aioimaplib

aioimaplib's People

Contributors

aliceh75 avatar azmeuk avatar bamthomas avatar brentwilkins avatar darkrain42 avatar felixschwarz avatar fenhl avatar jahs avatar jbouwh avatar juanbretti avatar julian-r avatar oleksandr-mazur avatar pblayo avatar scop avatar suut avatar thecode avatar vepiphyte avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aioimaplib's Issues

Not all commands use a timeout or can be cancelled

It seems that not all commands are consistently using the configured timeout, in particular I noticed that for UID only the UID FETCH command gets passed the timeout. Is there a reason for this? (I'm not super familiar with IMAP.)

As a workaround I apply await asyncio.wait_for(..., timeout) manually around the corresponding invocations. However, I noticed that this seems to fail to properly cancel the task. At least in the end I'm left with warnings like this one if timeout occur:

Task was destroyed but it is pending!
task: <Task pending coro=<Command.wait() done, defined at /Users/jgosmann/Library/Caches/pypoetry/virtualenvs/dmarc-metrics-exporter-n4MZO14v-py3.7/lib/python3.7/site-packages/aioimaplib/aioimaplib.py:209> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x103fbb510>()]>>

IMAP4 initialization api

Current connection code looks like:

# creates IMAP4 instace, ImapClientProtocol and task for connection
imap = IMAP4(host, port)
# waits for successful connection (state changes from AUTH to NONAUTH)
await imap.wait_hello_from_server()
# changes state to AUTH
await imap.login(username, password)  # or imap.authenticate

1. new version

# creates IMAP4 instace, does not start connection
imap = IMAP4(loop=myloop, timeout=10)
# creates ImapClientProtocol
# connects to (await loop.create_connection)
await imap.connect(host, port)
# changes state to AUTH
await imap.login(username, password)  # or imap.authenticate
  • makes connection initialization later
  • connect is more readable and intuitive than wait_hello_from_server
  • user can control when connection starts
  • user can reconnect after failed connection

2. new version more like imaplib.IMAP4

# creates class, does not start connection
imap = IMAP4(host, port)
# creates ImapClientProtocol
# connection (await loop.create_connection)
# changes state to AUTH
await imap.login(username, password)  # or imap.authenticate
  • more like imaplib.IMAP4
  • makes connection initialization later
  • disadvantage: need to insert code for checking connection to commands LOGIN, AUTHENTICATE, CAPABILITY or even all command

What do you think about these updates?

async/await keywords instead of async.coroutine & yield from.

Hey,

I am new to asynchronous python, I read your code & found you were using async.coroutine & yield from. I didn't know what are those decorators & keywords because I started coding in Python after 3.6.

I would like to suggest an enhancement to change async.coroutine & yield from to async/await keywords to improve readability for newer users & it'll modernize your code base.

Edit:
Besides all that, thanks a lot for working on this project, It is the only library I could find with proper implementation of imap with async.

Thanks :)

test_get_quotaroot fails

After downloading aioimaplib 1.0.0 I get

============================= test session starts ==============================
platform linux -- Python 3.10.5, pytest-7.1.1, pluggy-1.0.0
rootdir: /build/source
collected 153 items

aioimaplib/tests/test_acceptance_aioimaplib.py .                         [  0%]
aioimaplib/tests/test_aioimaplib.py .................................... [ 24%]
.........F........................................                       [ 56%]
aioimaplib/tests/test_imapserver.py ..............                       [ 66%]
aioimaplib/tests/test_imapserver_aioimaplib.py ..                        [ 67%]
aioimaplib/tests/test_imapserver_imaplib.py ............................ [ 85%]
.......                                                                  [ 90%]
aioimaplib/tests/test_imapserver_imaplib2.py ..                          [ 91%]
aioimaplib/tests/test_ssl_cert.py ....                                   [ 94%]
aioimaplib/tests/test_utils.py .........                                 [100%]

=================================== FAILURES ===================================
______________________ TestAioimaplib.test_get_quotaroot _______________________

self = <aioimaplib.tests.test_aioimaplib.TestAioimaplib testMethod=test_get_quotaroot>

    async def test_get_quotaroot(self):
        imap_client = await self.login_user('user', 'pass')
        self.imapserver.receive(Mail.create(['user']))

        response = await imap_client.getquotaroot('INBOX')

>       self.assertEqual(response.lines, [b'INBOX (STORAGE 292 5000)', b'GETQUOTAROOT completed.'])
E       AssertionError: Lists differ: [b'INBOX (STORAGE 294 5000)', b'GETQUOTAROOT completed.'] != [b'INBOX (STORAGE 292 5000)', b'GETQUOTAROOT completed.']
E
E       First differing element 0:
E       b'INBOX (STORAGE 294 5000)'
E       b'INBOX (STORAGE 292 5000)'
E
E       - [b'INBOX (STORAGE 294 5000)', b'GETQUOTAROOT completed.']
E       ?                     ^
E
E       + [b'INBOX (STORAGE 292 5000)', b'GETQUOTAROOT completed.']
E       ?                     ^

aioimaplib/tests/test_aioimaplib.py:734: AssertionError

Published IDLE example failing with Python 3.9

Using 0.8.0:

Traceback (most recent call last):
  File "/app/slack_responding/test.py", line 26, in <module>
    loop.run_until_complete(
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/app/slack_responding/test.py", line 8, in wait_for_new_message
    await imap_client.wait_hello_from_server()
  File "/app/.venv/lib/python3.9/site-packages/aioimaplib/aioimaplib.py", line 694, in wait_hello_from_server
    await asyncio.wait_for(self.protocol.wait('AUTH|NONAUTH'), self.timeout)
  File "/usr/lib/python3.9/asyncio/tasks.py", line 481, in wait_for
    return fut.result()
  File "/app/.venv/lib/python3.9/site-packages/aioimaplib/aioimaplib.py", line 594, in wait
    with await self.state_condition:
TypeError: object Condition can't be used in 'await' expression

The example in the README.rst shows a different behaviour from what happens on my system

The example in the README.rs file does this:

res, data = await imap_client.select()
print('there is %s messages INBOX' % data[0])

which would be the same behaviour of imaplib. But when I execute this code I get b'FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Forwarded $Junk $la' instead of the number of emails. I get a list that looks like this:

[b'FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Forwarded $Junk $la'
 b'bel4 $label5 $label2 $label3 $label1 Junk $Forwarded $MDNSent NonJunk)',
 b'OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Forwar'
 b'ded $Junk $label4 $label5 $label2 $label3 $label1 Junk $Forwarded $MDNSent N'
 b'onJunk \\*)]',
 b'OK [URLMECH INTERNAL]',
 b'4046 EXISTS',
 b'0 RECENT',
 b'OK [UNSEEN 1507]',
 b'OK [UIDVALIDITY 1226754362]',
 b'OK [UIDNEXT 127124]',
 b'[READ-WRITE] SELECT completed']

If I execute a select() with the python built-in imaplib with the same credentials, then I get expected behaviour. I checked different imap servers and this always happens. Why is that?

Check for BYE response

This can happen as a response to pretty much any command, especially when using XOAUTH for authentication. For example, on MS Exchange Online:

myprefix LIST "" "*"
* BYE Session invalidated - AccessTokenExpired

Ideally it's handled in a way so the user's code can detect this and e.g. reconnect with a new token.

SCRAM-SHA-1(-PLUS) + SCRAM-SHA-256(-PLUS) + SCRAM-SHA-512(-PLUS) + SCRAM-SHA3-512(-PLUS) supports

Dear @bamthomas,

In first, I wish you a Happy New Year!

Can you add supports of:

  • SCRAM-SHA-1
  • SCRAM-SHA-1-PLUS
  • SCRAM-SHA-256
  • SCRAM-SHA-256-PLUS
  • SCRAM-SHA-512
  • SCRAM-SHA-512-PLUS
  • SCRAM-SHA3-512
  • SCRAM-SHA3-512-PLUS

You can add too:

  • SCRAM-SHA-224
  • SCRAM-SHA-224-PLUS
  • SCRAM-SHA-384
  • SCRAM-SHA-384-PLUS

"When using the SASL SCRAM mechanism, the SCRAM-SHA-256-PLUS variant SHOULD be preferred over the SCRAM-SHA-256 variant, and SHA-256 variants [RFC7677] SHOULD be preferred over SHA-1 variants [RFC5802]".

https://xmpp.org/extensions/inbox/hash-recommendations.html

-PLUS variants:

IMAP:

LDAP:

  • RFC5803: Lightweight Directory Access Protocol (LDAP) Schema for Storing Salted: Challenge Response Authentication Mechanism (SCRAM) Secrets: https://tools.ietf.org/html/rfc5803

HTTP:

2FA:

IANA:

Linked to:

Office365 append pending forever

Hi there! 👋

I need help, but I'll probably be vague, and I know this is probably not the best place to ask this. I'm already using the package, so I'll give it a try here. 😅

I'm having an issue when appending a message on the outlook.office365.com IMAP server. What happened is that it basically keeps on the appending command for a long time (I've removed the timeout, to see if it was just taking too long, but it doesn't finish).

The snippet I'm using is the following:

await imap_client.store(uid, "+FLAGS", "(\\Deleted)")
await imap_client.expunge()
await imap_client.append(message.as_bytes())

I'm trying to modify a body of a message, so I need to get the message, modify it, remove it from the server and add a new one. The log of the first two commands above are fine:

Response(result='OK', lines=[b'4 FETCH (FLAGS (\\Deleted \\Recent))', b'STORE completed.'])
Response(result='OK', lines=[b'4 EXPUNGE', b'EXPUNGE completed.'])

But again, the append keeps forever. 😓

I hope someone can help me out. :)
Thanks for the package!

Error fetch uid param

Error in line 611 return (yield from self.protocol.fetch(message_set, message_parts, self.timeout))
Third param is by_uid. timeout is forth param

imapserver.py example

Hello,
I would have liked to use the "imapserver.py" script in a unit test.
Being a commercial application, I preferred not to embed GPL code. Would it be possible to have a less restrictive license for this file?
Thank you very much for your work.
Alexandre

Common base class for exceptions

Would it be possible to rearrange the exceptions so they all share a base class? This could make it simpler to catch all aioimaplib related exceptions.

I am trying to do this with an IDLE loop, in order to reconnect if the connection is lost.

Avoid Response to have mixed types str/bytes

For the user convenience we decided at the beginning to convert all command response lines to type string.

The problem is that for binary data (attachments...) it is not possible. As a result, Response.lines can have both str and bytes content.

It is causing functions to accept both string and bytes, and to bring unnecessary complexity.

So we should always use bytes.

3.9 support?

Currently running the example with python 3.9.4 errors are thrown (presumably due to changes in asyncio):

Task exception was never retrieved
future: <Task finished name='Task-4' coro=<IMAP4ClientProtocol.welcome() done, defined at /home/james/dls/aioimaplib/aioimaplib/aioimaplib.py:298> exception=TypeError("object Condition can't be used in 'await' expression")>
Traceback (most recent call last):
  File "/home/james/dls/aioimaplib/aioimaplib/aioimaplib.py", line 300, in wrapper
    with await self.state_condition:
TypeError: object Condition can't be used in 'await' expression
Traceback (most recent call last):

I intend to look into this, although my experience with async is limited.

Syntax error on Python 3.7

Importing aioimaplib fails on Python 3.7 because async is a keyword now.

>>> import aioimaplib
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/site-packages/aioimaplib/__init__.py", line 18, in <module>
    from .aioimaplib import *
  File "/usr/local/lib/python3.7/site-packages/aioimaplib/aioimaplib.py", line 51
    'CAPABILITY':   Cmd('CAPABILITY',   (NONAUTH, AUTH, SELECTED),  Exec.async),
                                                                             ^
SyntaxError: invalid syntax

How use proxy?

Hi! I want to connect to server using proxy.

How use proxies with aioimaplib?

For example:
IP:PORT:LOGIN:PASSWORD

Not properly buffering newlines

I've run into an annoying bug that I'm not sure how to fix. Sometimes the data fed to _handle_responses is like so: ...QBo+puGrxMuda6Cz23F6J7p\r\n\r\n)\r\nP. The stray P at the end of the data is the start of the TAG for the next command, in this case self.tagpre is PHMD.

The problem is that this trailing byte is buffered and sent to _handle_line all by itself, which causes it to be rejected. Then when the next chunk of data from the server is received it is rejected because the tag is found to start with HMD, not PHMD.

Perhaps using StreamReaderProtocol rather than Protocol would be a better fit?

Don't check for capabilities in protocol

Hi,
I think that user should be responsible to check for capabilities supported by IMAP server and use only them.
Library should not restrict user this way.

  1. If capability is not supported, server returns BAD or NO response.
  2. With checking in protocol, user could not use this lib with server which supports capability, but doesn't advertise it, for example because it is only partially implemented.
  3. It is additional check in every request, protocol should be fast. Of course it isn't significant, but it's a point.
  4. It is even more work for correctly checking for advanced capabilities like CONDSTORE, QRESYNC, LIST-STATUS.

发送ID命令的id方法不能正常使用

举个例子,假设正常ID命令内容为ID ("name" "testname" "version" "1.1.0" "vendor" "abcd") ,使用id方法后的内容为ID ( "name" "testname" "version" "1.1.0" "vendor" "abcd" ) ,在“(”后与“)”前各多了一个空格,导致命令错误

Py 3.10: ssl.SSLError: Cannot create a client socket with a PROTOCOL_TLS_SERVER context

Reported at the Home Assistant project:

2022-07-06 11:27:01 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 1089, in create_connection
    transport, protocol = await self._create_connection_transport(
  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 1119, in _create_connection_transport
    await waiter
  File "/usr/local/lib/python3.10/asyncio/sslproto.py", line 637, in _on_handshake_complete
    raise handshake_exc
  File "/usr/local/lib/python3.10/asyncio/sslproto.py", line 682, in _process_write_backlog
    ssldata = self._sslpipe.do_handshake(
  File "/usr/local/lib/python3.10/asyncio/sslproto.py", line 116, in do_handshake
    self._sslobj = self._context.wrap_bio(
  File "/usr/local/lib/python3.10/ssl.py", line 527, in wrap_bio
    return self.sslobject_class._create(
  File "/usr/local/lib/python3.10/ssl.py", line 866, in _create
    sslobj = context._wrap_bio(
ssl.SSLError: Cannot create a client socket with a PROTOCOL_TLS_SERVER context (_ssl.c:801)
No error when I disable imap sensor in config and restart

This seems to be related to more strict handling in Python 3.10 (which the Home Assistant project has been switched to).

It happens when setting the default SSL context:

ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)

According to documentation, however, this is used to create a server side sockets:

image

ref: https://docs.python.org/3/library/ssl.html#ssl.Purpose.CLIENT_AUTH

I guess it needs to be using:
CleanShot 2022-07-06 at 08 43 21

ref: https://docs.python.org/3/library/ssl.html#ssl.Purpose.SERVER_AUTH

Downstream issue: home-assistant/core#74487

Python 3.9 - Task exception was never retrieved

I just pulled the newest version of this library and I'm still getting the following error when using Python 3.9

Task exception was never retrieved
future: <Task finished name='Task-2' coro=<BaseEventLoop.create_connection() done, defined at /usr/lib/python3.9/asyncio/base_events.py:967> exception=gaierror(-2, 'Name or service not known')>

This is the same as already mentioned in #59 (comment).
To reproduce this, I simply ran the imap_fetch.py example provided.

aioimaplib.py”, line 560, in capability, not following RFC rules

There seems to be in bug in the aioimaplib library, it expects the “IMAPrev1” to be the first capability (index 0):

version = capability_list[0].upper()

However, for example imap.telnet.be returns capabilities in roughly alphabetical order:
$ telnet imap.telenet.be 143
Trying 2a02:1800:100:3::3...
Connected to imap.telenet.be.
Escape character is '^]'.

  • OK IMAP4rev1 proxy server ready
    a capability
  • CAPABILITY ACL BINARY CATENATE CHILDREN CONDSTORE ENABLE ESEARCH ID IDLE IMAP4rev1 LIST-EXTENDED LITERAL+ MULTIAPPEND NAMESPACE QRESYNC QUOTA RIGHTS=ektx SASL-IR SEARCHRES UIDPLUS UNSELECT WITHIN ESORT I18NLEVEL=1 SORT THREAD=ORDEREDSUBJECT LIST-STATUS XLIST STARTTLS LOGINDISABLED
    a OK `completed

which seems to be acceptable by the IMAP RFC 3501:
The server MUST send a single untagged CAPABILITY response with “IMAP4rev1” as one of the listed capabilities before the (tagged) OK response.

['+ idling from'] from imap_client.wait_server_push()

Hi

In a coroutine, I have

imap_client = aioimaplib.IMAP4_SSL(host=host, timeout=30)
yield from imap_client.wait_hello_from_server()

   yield from imap_client.login(user, password)
   yield from imap_client.select()

   while True:
      #print((yield from imap_client.uid('fetch', '1:*', 'FLAGS')))

      idle = yield from imap_client.idle_start(timeout=6)
      print((yield from imap_client.wait_server_push()))

      imap_client.idle_done()
      yield from asyncio.wait_for(idle, 30)

sometimes I got the following debug:

[…]
2018-01-18 14:23:15,478 DEBUG [aioimaplib:360] Sending : b'DONE\r\n'
2018-01-18 14:23:15,514 DEBUG [aioimaplib:295] Received : b'GJDH3 OK Idle completed.\r\n'
2018-01-18 14:23:15,514 DEBUG [aioimaplib:581] tagged status GJDH3 OK Idle completed.
2018-01-18 14:23:15,514 DEBUG [aioimaplib:360] Sending : b'GJDH4 IDLE\r\n'
2018-01-18 14:23:15,550 DEBUG [aioimaplib:295] Received : b'+ idling\r\n'
2018-01-18 14:23:15,550 DEBUG [aioimaplib:610] continuation line appended to pending sync command GJDH4 IDLE : + idling
stop_wait_server_push
2018-01-18 14:23:21,515 DEBUG [aioimaplib:360] Sending : b'DONE\r\n'
2018-01-18 14:23:21,641 DEBUG [aioimaplib:295] Received : b'* 493 EXISTS\r\n* 1 RECENT\r\n'
2018-01-18 14:23:21,674 DEBUG [aioimaplib:295] Received : b'GJDH4 OK Idle completed.\r\n'
2018-01-18 14:23:21,675 DEBUG [aioimaplib:581] tagged status GJDH4 OK Idle completed.
2018-01-18 14:23:21,675 DEBUG [aioimaplib:360] Sending : b'GJDH5 IDLE\r\n'
2018-01-18 14:23:21,710 DEBUG [aioimaplib:295] Received : b'+ idling\r\n'
2018-01-18 14:23:21,711 DEBUG [aioimaplib:610] continuation line appended to pending sync command GJDH5 IDLE : + idling
['+ idling']
2018-01-18 14:23:21,711 DEBUG [aioimaplib:360] Sending : b'DONE\r\n'
2018-01-18 14:23:21,746 DEBUG [aioimaplib:295] Received : b'GJDH5 OK Idle completed.\r\n'
2018-01-18 14:23:21,747 DEBUG [aioimaplib:581] tagged status GJDH5 OK Idle completed.
2018-01-18 14:23:21,747 DEBUG [aioimaplib:360] Sending : b'GJDH6 IDLE\r\n'
2018-01-18 14:23:21,783 DEBUG [aioimaplib:295] Received : b'+ idling\r\n'
2018-01-18 14:23:21,783 DEBUG [aioimaplib:610] continuation line appended to pending sync command GJDH6 IDLE : + idling
stop_wait_server_push
[…]

I don’t understand why I have this line ['+ idling']

I’m using dovecot as the imap server.

Release Bump to 1.0.1

I am currently stuck with this issue and since you've already merged the fix, all it needs for my local package to update would be a new release. Would it be possible for you to bump to 1.0.1 please?

Speed of aioimaplib

Sorry, this isnt really an issue but could not find any where to ask this. How fast is aiomaplib compared to the standard imaplib? How fast would aioimaplib be able to scrape (get the content of) lets say, 100,000 emails?

Lost server status update in IdleCommand response

Hi,
I use the idle command to get notified for new messages. I restart the idle command every x minutes. When there is server status update after the 'DONE' is send, it is appended to the response to the 'IDLE' command.

[aioimaplib.py:289 data_received] Received : b'+ idling\r\n* 418 FETCH (FLAGS ())\r\n* 420 FETCH (FLAGS (\Seen))\r\n* 421 FETCH (FLAGS (\Seen))\r\n* 451 FETCH (FLAGS ())\r\n'

All these lines are added to the IdleCommand buffer and the answer is consumed from die aioimaplib somewhere. The changed Flags are beeing lost.

Here is a Solution:
We should change line 220 from
self.queue.put_nowait(copy(self.buffer))
to
for item in self.buffer:
self.queue.put_nowait(copy(item))

so each item from the buffer (e.g. each line of the response) is added individually to queue.

Because I'm not so familiar with the code I'm not sure if this change breaks something. Can someone please comment on this?

Thanks!

Test failure on aarch64

When I run the test suite (python setup.py test) on a 64-bit ARM machine running NixOS and Python 3.6, I get the following error:

======================================================================
FAIL: test_idle_start__exits_queueget_without_timeout_error (aioimaplib.tests.test_aioimaplib.TestAioimaplibClocked)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/q4mxm1959nr9bh6vmbml6f5vr0phfgh8-python3.6-asynctest-0.11.1/lib/python3.6/site-packages/asynctest/case.py", line 294, in run
    self._run_test_method(testMethod)
  File "/nix/store/q4mxm1959nr9bh6vmbml6f5vr0phfgh8-python3.6-asynctest-0.11.1/lib/python3.6/site-packages/asynctest/case.py", line 351, in _run_test_method
    self.loop.run_until_complete(result)
  File "/nix/store/q4mxm1959nr9bh6vmbml6f5vr0phfgh8-python3.6-asynctest-0.11.1/lib/python3.6/site-packages/asynctest/case.py", line 221, in wrapper
    return method(*args, **kwargs)
  File "/nix/store/4yfkb61l14x37pkqdmg4ck61xh5wg49z-python3-3.6.4/lib/python3.6/asyncio/base_events.py", line 467, in run_until_complete
    return future.result()
  File "/build/source/aioimaplib/tests/test_aioimaplib.py", line 852, in test_idle_start__exits_queueget_without_timeout_error
    self.assertEqual(STOP_WAIT_SERVER_PUSH, r)
AssertionError: 'stop_wait_server_push' != ['OK Still here', 'OK Still here', 'OK St[1263 chars]ere']
-------------------- >> begin captured logging << --------------------
asyncio: DEBUG: Using selector: EpollSelector
--------------------- >> end captured logging << ---------------------

Do you know the reason for this?

See also https://hydra.nixos.org/build/68772894/log.

APPEND doesn't working

Hi, i have some troubles with APPEND command.
It receives message that server ready to receive literal data and do nothing.
This is part of asyncio debug log:

DEBUG:aioimaplib.aioimaplib:Sending : b'LION1 LOGIN xxx "xxx"\r\n'
DEBUG:asyncio:poll 59997.714 ms took 57.081 ms: 1 events
DEBUG:aioimaplib.aioimaplib:Received : b'* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST BINARY UIDPLUS ENABLE ID IDLE MOVE\r\nLION1 OK LOGIN Completed.\r\n'
DEBUG:aioimaplib.aioimaplib:tagged status LION1 OK LOGIN Completed.
DEBUG:aioimaplib.aioimaplib:state -> AUTH
DEBUG:aioimaplib.aioimaplib:Sending : b'LION2 APPEND INBOX {17602}\r\n'
DEBUG:asyncio:poll 59998.721 ms took 9.665 ms: 1 events
DEBUG:aioimaplib.aioimaplib:Received : b'+ go ahead with 17602 octets\r\n'
DEBUG:aioimaplib.aioimaplib:continuation line appended to pending sync command LION2 APPEND INBOX {17602} : + go ahead with 17602 octets
INFO:asyncio:poll 59987.636 ms took 59991.798 ms: timeout

fetch error

I want fetch email with this parts '(UID FLAGS INTERNALDATE)'
status, data = await self.imap_client.fetch('3', '(UID FLAGS INTERNALDATE)')
I get wrong response Response(result='OK', lines=['64', 'Success'])

I think this heppens because of
fetch_message_with_literal_data_re = re.compile(rb'\* [0-9]+ FETCH [\w \\\[\]\(\)]+ \{(?P<size>\d+)\}\r\n')

I don't have size field in my response
b'* 64 FETCH (UID 328 INTERNALDATE "14-Oct-2012 17:49:28 +0000" FLAGS (\\Seen))\r\nCGOJ66 OK Success\r\n'

How to handle literals in FETCH response

Thanks for this library - I've had some good success with it so far.

However, if I have an email with a double quote in the subject, then the FETCH response for the message is split over multiple lines, and uses a literal line for the subject.

Is there a recommended way to handle this? If I do a FETCH for all messages, then the situation seems even trickier to handle, as there seems to be no easy way to tell which line belongs to which message.

The standard lib imaplib seems to bundle the responses into tuples of envelopes and data but we only get the response lines (unless I've missed something).

Fix compatibility with Python 3.10+

It would be nice if it worked on modern Python versions as well :)

Task exception was never retrieved
future: <Task finished name='Task-2' coro=<BaseEventLoop.create_connection() done, defined at /home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py:972> exception=ConnectionResetError() created at /home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/aioimaplib/aioimaplib.py:688>
source_traceback: Object created at (most recent call last):
  File "/home/adrian/dev/imapwatch/./imapwatch.py", line 131, in <module>
    main()
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/adrian/dev/imapwatch/./imapwatch.py", line 127, in main
    asyncio.run(async_main(mailbox, host, user, password, test_mode, include_seen), debug=True)
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 633, in run_until_complete
    self.run_forever()
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 600, in run_forever
    self._run_once()
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 1888, in _run_once
    handle._run()
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/adrian/dev/imapwatch/./imapwatch.py", line 111, in async_main
    await idle_loop(mailbox, host, user, password, test_mode, include_seen)
  File "/home/adrian/dev/imapwatch/./imapwatch.py", line 75, in idle_loop
    imap_client = IMAP4_SSL(host=host, timeout=30)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/aioimaplib/aioimaplib.py", line 829, in __init__
    super().__init__(host, port, loop, timeout, None, ssl_context)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/aioimaplib/aioimaplib.py", line 682, in __init__
    self.create_client(host, port, loop, conn_lost_cb, ssl_context)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/aioimaplib/aioimaplib.py", line 835, in create_client
    super().create_client(host, port, loop, conn_lost_cb, ssl_context)
  File "/home/adrian/dev/imapwatch/.venv/lib/python3.10/site-packages/aioimaplib/aioimaplib.py", line 688, in create_client
    local_loop.create_task(local_loop.create_connection(lambda: self.protocol, host, port, ssl=ssl_context))
Traceback (most recent call last):
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 1089, in create_connection
    transport, protocol = await self._create_connection_transport(
  File "/home/adrian/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 1119, in _create_connection_transport
    await waiter
ConnectionResetError

Test failures on Python 3.6.5

Since updating from Python 3.6.4 to 3.6.5, I'm getting test failures:

======================================================================
FAIL: test_append (aioimaplib.tests.test_aioimaplib.TestAioimaplib)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 297, in run
    self._run_test_method(testMethod)
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 354, in _run_test_method
    self.loop.run_until_complete(result)
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 224, in wrapper
    return method(*args, **kwargs)
  File "/nix/store/33v7rg3qmwfj3smvhna5634n7x286wl6-python3-3.6.5/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
  File "/build/source/aioimaplib/tests/test_aioimaplib.py", line 724, in test_append
    self.assertEquals('OK', response.result)
AssertionError: 'OK' != 'BAD'
- OK
+ BAD

-------------------- >> begin captured logging << --------------------
asyncio: DEBUG: Using selector: EpollSelector
--------------------- >> end captured logging << ---------------------

======================================================================
FAIL: test_idle_start__exits_queueget_without_timeout_error (aioimaplib.tests.test_aioimaplib.TestAioimaplibClocked)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 297, in run
    self._run_test_method(testMethod)
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 354, in _run_test_method
    self.loop.run_until_complete(result)
  File "/nix/store/h58bwyphmcawijhx0387af6bvnk9ggcb-python3.6-asynctest-0.12.0/lib/python3.6/site-packages/asynctest/case.py", line 224, in wrapper
    return method(*args, **kwargs)
  File "/nix/store/33v7rg3qmwfj3smvhna5634n7x286wl6-python3-3.6.5/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
  File "/build/source/aioimaplib/tests/test_aioimaplib.py", line 872, in test_idle_start__exits_queueget_without_timeout_error
    self.assertEqual(STOP_WAIT_SERVER_PUSH, r)
AssertionError: 'stop_wait_server_push' != ['OK Still here']
-------------------- >> begin captured logging << --------------------
asyncio: DEBUG: Using selector: EpollSelector
--------------------- >> end captured logging << ---------------------

Add an example in the readme on how to check inbox and receive email ?

Hello

first thanks for this library, as it seems to be the only one for imap and asyncio :)
I'm pretty noob to the IMAP protocol and what i find missing is a bit of examples in the README.md on how to do some basic tasks (like actually receiving an email)

for example the IDLE example does show me that I've "received something" but I've no clue how to treat it :)
having some more examples in the readme would compensate not having a formal documentation

thanks again

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.