1# Copyright (c) 2015, Menno Smits
2# Released subject to the New BSD License
3# Please see http://en.wikipedia.org/wiki/BSD_licenses
4
5from __future__ import unicode_literals
6
7import functools
8import imaplib
9import itertools
10import select
11import socket
12import sys
13import re
14from collections import namedtuple
15from datetime import datetime, date
16from operator import itemgetter
17from logging import LoggerAdapter, getLogger
18
19from six import moves, iteritems, text_type, integer_types, PY3, binary_type, iterbytes
20
21from . import exceptions
22from . import imap4
23from . import response_lexer
24from . import tls
25from .datetime_util import datetime_to_INTERNALDATE, format_criteria_date
26from .imap_utf7 import encode as encode_utf7, decode as decode_utf7
27from .response_parser import parse_response, parse_message_list, parse_fetch_response
28from .util import to_bytes, to_unicode, assert_imap_protocol, chunk
29
30xrange = moves.xrange
31
32try:
33    from select import poll
34
35    POLL_SUPPORT = True
36except:
37    # Fallback to select() on systems that don't support poll()
38    POLL_SUPPORT = False
39
40if PY3:
41    long = int  # long is just int in python3
42
43
44logger = getLogger(__name__)
45
46__all__ = [
47    "IMAPClient",
48    "SocketTimeout",
49    "DELETED",
50    "SEEN",
51    "ANSWERED",
52    "FLAGGED",
53    "DRAFT",
54    "RECENT",
55]
56
57
58# We also offer the gmail-specific XLIST command...
59if "XLIST" not in imaplib.Commands:
60    imaplib.Commands["XLIST"] = ("NONAUTH", "AUTH", "SELECTED")
61
62# ...and IDLE
63if "IDLE" not in imaplib.Commands:
64    imaplib.Commands["IDLE"] = ("NONAUTH", "AUTH", "SELECTED")
65
66# ..and STARTTLS
67if "STARTTLS" not in imaplib.Commands:
68    imaplib.Commands["STARTTLS"] = ("NONAUTH",)
69
70# ...and ID. RFC2971 says that this command is valid in all states,
71# but not that some servers (*cough* FastMail *cough*) don't seem to
72# accept it in state NONAUTH.
73if "ID" not in imaplib.Commands:
74    imaplib.Commands["ID"] = ("NONAUTH", "AUTH", "SELECTED")
75
76# ... and UNSELECT. RFC3691 does not specify the state but there is no
77# reason to use the command without AUTH state and a mailbox selected.
78if "UNSELECT" not in imaplib.Commands:
79    imaplib.Commands["UNSELECT"] = ("AUTH", "SELECTED")
80
81# .. and ENABLE.
82if "ENABLE" not in imaplib.Commands:
83    imaplib.Commands["ENABLE"] = ("AUTH",)
84
85# .. and MOVE for RFC6851.
86if "MOVE" not in imaplib.Commands:
87    imaplib.Commands["MOVE"] = ("AUTH", "SELECTED")
88
89# System flags
90DELETED = br"\Deleted"
91SEEN = br"\Seen"
92ANSWERED = br"\Answered"
93FLAGGED = br"\Flagged"
94DRAFT = br"\Draft"
95RECENT = br"\Recent"  # This flag is read-only
96
97# Special folders, see RFC6154
98# \Flagged is omitted because it is the same as the flag defined above
99ALL = br"\All"
100ARCHIVE = br"\Archive"
101DRAFTS = br"\Drafts"
102JUNK = br"\Junk"
103SENT = br"\Sent"
104TRASH = br"\Trash"
105
106# Personal namespaces that are common among providers
107# used as a fallback when the server does not support the NAMESPACE capability
108_POPULAR_PERSONAL_NAMESPACES = (("", ""), ("INBOX.", "."))
109
110# Names of special folders that are common among providers
111_POPULAR_SPECIAL_FOLDERS = {
112    SENT: ("Sent", "Sent Items", "Sent items"),
113    DRAFTS: ("Drafts",),
114    ARCHIVE: ("Archive",),
115    TRASH: ("Trash", "Deleted Items", "Deleted Messages", "Deleted"),
116    JUNK: ("Junk", "Spam"),
117}
118
119_RE_SELECT_RESPONSE = re.compile(br"\[(?P<key>[A-Z-]+)( \((?P<data>.*)\))?\]")
120
121
122class Namespace(tuple):
123    def __new__(cls, personal, other, shared):
124        return tuple.__new__(cls, (personal, other, shared))
125
126    personal = property(itemgetter(0))
127    other = property(itemgetter(1))
128    shared = property(itemgetter(2))
129
130
131class SocketTimeout(namedtuple("SocketTimeout", "connect read")):
132    """Represents timeout configuration for an IMAP connection.
133
134    :ivar connect: maximum time to wait for a connection attempt to remote server
135    :ivar read: maximum time to wait for performing a read/write operation
136
137    As an example, ``SocketTimeout(connect=15, read=60)`` will make the socket
138    timeout if the connection takes more than 15 seconds to establish but
139    read/write operations can take up to 60 seconds once the connection is done.
140    """
141
142
143class MailboxQuotaRoots(namedtuple("MailboxQuotaRoots", "mailbox quota_roots")):
144    """Quota roots associated with a mailbox.
145
146    Represents the response of a GETQUOTAROOT command.
147
148    :ivar mailbox: the mailbox
149    :ivar quota_roots: list of quota roots associated with the mailbox
150    """
151
152
153class Quota(namedtuple("Quota", "quota_root resource usage limit")):
154    """Resource quota.
155
156    Represents the response of a GETQUOTA command.
157
158    :ivar quota_roots: the quota roots for which the limit apply
159    :ivar resource: the resource being limited (STORAGE, MESSAGES...)
160    :ivar usage: the current usage of the resource
161    :ivar limit: the maximum allowed usage of the resource
162    """
163
164
165def require_capability(capability):
166    """Decorator raising CapabilityError when a capability is not available."""
167
168    def actual_decorator(func):
169        @functools.wraps(func)
170        def wrapper(client, *args, **kwargs):
171            if not client.has_capability(capability):
172                raise exceptions.CapabilityError(
173                    "Server does not support {} capability".format(capability)
174                )
175            return func(client, *args, **kwargs)
176
177        return wrapper
178
179    return actual_decorator
180
181
182class IMAPClient(object):
183    """A connection to the IMAP server specified by *host* is made when
184    this class is instantiated.
185
186    *port* defaults to 993, or 143 if *ssl* is ``False``.
187
188    If *use_uid* is ``True`` unique message UIDs be used for all calls
189    that accept message ids (defaults to ``True``).
190
191    If *ssl* is ``True`` (the default) a secure connection will be made.
192    Otherwise an insecure connection over plain text will be
193    established.
194
195    If *ssl* is ``True`` the optional *ssl_context* argument can be
196    used to provide an ``ssl.SSLContext`` instance used to
197    control SSL/TLS connection parameters. If this is not provided a
198    sensible default context will be used.
199
200    If *stream* is ``True`` then *host* is used as the command to run
201    to establish a connection to the IMAP server (defaults to
202    ``False``). This is useful for exotic connection or authentication
203    setups.
204
205    Use *timeout* to specify a timeout for the socket connected to the
206    IMAP server. The timeout can be either a float number, or an instance
207    of :py:class:`imapclient.SocketTimeout`.
208
209    * If a single float number is passed, the same timeout delay applies
210      during the  initial connection to the server and for all future socket
211      reads and writes.
212
213    * In case of a ``SocketTimeout``, connection timeout and
214      read/write operations can have distinct timeouts.
215
216    * The default is ``None``, where no timeout is used.
217
218    The *normalise_times* attribute specifies whether datetimes
219    returned by ``fetch()`` are normalised to the local system time
220    and include no timezone information (native), or are datetimes
221    that include timezone information (aware). By default
222    *normalise_times* is True (times are normalised to the local
223    system time). This attribute can be changed between ``fetch()``
224    calls if required.
225
226    Can be used as a context manager to automatically close opened connections:
227
228    >>> with IMAPClient(host="imap.foo.org") as client:
229    ...     client.login("bar@foo.org", "passwd")
230
231    """
232
233    # Those exceptions are kept for backward-compatibility, since
234    # previous versions included these attributes as references to
235    # imaplib original exceptions
236    Error = exceptions.IMAPClientError
237    AbortError = exceptions.IMAPClientAbortError
238    ReadOnlyError = exceptions.IMAPClientReadOnlyError
239
240    def __init__(
241        self,
242        host,
243        port=None,
244        use_uid=True,
245        ssl=True,
246        stream=False,
247        ssl_context=None,
248        timeout=None,
249    ):
250        if stream:
251            if port is not None:
252                raise ValueError("can't set 'port' when 'stream' True")
253            if ssl:
254                raise ValueError("can't use 'ssl' when 'stream' is True")
255        elif port is None:
256            port = ssl and 993 or 143
257
258        if ssl and port == 143:
259            logger.warning(
260                "Attempting to establish an encrypted connection "
261                "to a port (143) often used for unencrypted "
262                "connections"
263            )
264
265        self.host = host
266        self.port = port
267        self.ssl = ssl
268        self.ssl_context = ssl_context
269        self.stream = stream
270        self.use_uid = use_uid
271        self.folder_encode = True
272        self.normalise_times = True
273
274        # If the user gives a single timeout value, assume it is the same for
275        # connection and read/write operations
276        if not isinstance(timeout, SocketTimeout):
277            timeout = SocketTimeout(timeout, timeout)
278
279        self._timeout = timeout
280        self._starttls_done = False
281        self._cached_capabilities = None
282        self._idle_tag = None
283
284        self._imap = self._create_IMAP4()
285        logger.debug(
286            "Connected to host %s over %s",
287            self.host,
288            "SSL/TLS" if ssl else "plain text",
289        )
290
291        self._set_read_timeout()
292        # Small hack to make imaplib log everything to its own logger
293        imaplib_logger = IMAPlibLoggerAdapter(getLogger("imapclient.imaplib"), dict())
294        self._imap.debug = 5
295        self._imap._mesg = imaplib_logger.debug
296
297    def __enter__(self):
298        return self
299
300    def __exit__(self, exc_type, exc_val, exc_tb):
301        """Logout and closes the connection when exiting the context manager.
302
303        All exceptions during logout and connection shutdown are caught because
304        an error here usually means the connection was already closed.
305        """
306        try:
307            self.logout()
308        except Exception:
309            try:
310                self.shutdown()
311            except Exception as e:
312                logger.info("Could not close the connection cleanly: %s", e)
313
314    def _create_IMAP4(self):
315        if self.stream:
316            return imaplib.IMAP4_stream(self.host)
317
318        connect_timeout = getattr(self._timeout, "connect", None)
319
320        if self.ssl:
321            return tls.IMAP4_TLS(
322                self.host,
323                self.port,
324                self.ssl_context,
325                connect_timeout,
326            )
327
328        return imap4.IMAP4WithTimeout(self.host, self.port, connect_timeout)
329
330    def _set_read_timeout(self):
331        if self._timeout is not None:
332            self._sock.settimeout(self._timeout.read)
333
334    @property
335    def _sock(self):
336        # In py2, imaplib has sslobj (for SSL connections), and sock for non-SSL.
337        # In the py3 version it's just sock.
338        return getattr(self._imap, "sslobj", self._imap.sock)
339
340    @require_capability("STARTTLS")
341    def starttls(self, ssl_context=None):
342        """Switch to an SSL encrypted connection by sending a STARTTLS command.
343
344        The *ssl_context* argument is optional and should be a
345        :py:class:`ssl.SSLContext` object. If no SSL context is given, a SSL
346        context with reasonable default settings will be used.
347
348        You can enable checking of the hostname in the certificate presented
349        by the server  against the hostname which was used for connecting, by
350        setting the *check_hostname* attribute of the SSL context to ``True``.
351        The default SSL context has this setting enabled.
352
353        Raises :py:exc:`Error` if the SSL connection could not be established.
354
355        Raises :py:exc:`AbortError` if the server does not support STARTTLS
356        or an SSL connection is already established.
357        """
358        if self.ssl or self._starttls_done:
359            raise exceptions.IMAPClientAbortError("TLS session already established")
360
361        typ, data = self._imap._simple_command("STARTTLS")
362        self._checkok("starttls", typ, data)
363
364        self._starttls_done = True
365
366        self._imap.sock = tls.wrap_socket(self._imap.sock, ssl_context, self.host)
367        self._imap.file = self._imap.sock.makefile("rb")
368        return data[0]
369
370    def login(self, username, password):
371        """Login using *username* and *password*, returning the
372        server response.
373        """
374        try:
375            rv = self._command_and_check(
376                "login",
377                to_unicode(username),
378                to_unicode(password),
379                unpack=True,
380            )
381        except exceptions.IMAPClientError as e:
382            raise exceptions.LoginError(str(e))
383
384        logger.debug("Logged in as %s", username)
385        return rv
386
387    def oauth2_login(self, user, access_token, mech="XOAUTH2", vendor=None):
388        """Authenticate using the OAUTH2 or XOAUTH2 methods.
389
390        Gmail and Yahoo both support the 'XOAUTH2' mechanism, but Yahoo requires
391        the 'vendor' portion in the payload.
392        """
393        auth_string = "user=%s\1auth=Bearer %s\1" % (user, access_token)
394        if vendor:
395            auth_string += "vendor=%s\1" % vendor
396        auth_string += "\1"
397        try:
398            return self._command_and_check("authenticate", mech, lambda x: auth_string)
399        except exceptions.IMAPClientError as e:
400            raise exceptions.LoginError(str(e))
401
402    def oauthbearer_login(self, identity, access_token):
403        """Authenticate using the OAUTHBEARER method.
404
405        This is supported by Gmail and is meant to supersede the non-standard
406        'OAUTH2' and 'XOAUTH2' mechanisms.
407        """
408        # https://tools.ietf.org/html/rfc5801#section-4
409        # Technically this is the authorization_identity, but at least for Gmail it's
410        # mandatory and practically behaves like the regular username/identity.
411        if identity:
412            gs2_header = "n,a=%s," % identity.replace("=", "=3D").replace(",", "=2C")
413        else:
414            gs2_header = "n,,"
415        # https://tools.ietf.org/html/rfc6750#section-2.1
416        http_authz = "Bearer %s" % access_token
417        # https://tools.ietf.org/html/rfc7628#section-3.1
418        auth_string = "%s\1auth=%s\1\1" % (gs2_header, http_authz)
419        try:
420            return self._command_and_check(
421                "authenticate", "OAUTHBEARER", lambda x: auth_string
422            )
423        except exceptions.IMAPClientError as e:
424            raise exceptions.LoginError(str(e))
425
426    def plain_login(self, identity, password, authorization_identity=None):
427        """Authenticate using the PLAIN method (requires server support)."""
428        if not authorization_identity:
429            authorization_identity = ""
430        auth_string = "%s\0%s\0%s" % (authorization_identity, identity, password)
431        try:
432            return self._command_and_check(
433                "authenticate", "PLAIN", lambda _: auth_string, unpack=True
434            )
435        except exceptions.IMAPClientError as e:
436            raise exceptions.LoginError(str(e))
437
438    def sasl_login(self, mech_name, mech_callable):
439        """Authenticate using a provided SASL mechanism (requires server support).
440
441        The *mech_callable* will be called with one parameter (the server
442        challenge as bytes) and must return the corresponding client response
443        (as bytes, or as string which will be automatically encoded).
444
445        It will be called as many times as the server produces challenges,
446        which will depend on the specific SASL mechanism. (If the mechanism is
447        defined as "client-first", the server will nevertheless produce a
448        zero-length challenge.)
449
450        For example, PLAIN has just one step with empty challenge, so a handler
451        might look like this:
452
453            plain_mech = lambda _: "\\0%s\\0%s" % (username, password)
454
455            imap.sasl_login("PLAIN", plain_mech)
456
457        A more complex but still stateless handler might look like this:
458
459            def example_mech(challenge):
460                if challenge == b"Username:"
461                    return username.encode("utf-8")
462                elif challenge == b"Password:"
463                    return password.encode("utf-8")
464                else:
465                    return b""
466
467            imap.sasl_login("EXAMPLE", example_mech)
468
469        A stateful handler might look like this:
470
471            class ScramSha256SaslMechanism():
472                def __init__(self, username, password):
473                    ...
474
475                def __call__(self, challenge):
476                    self.step += 1
477                    if self.step == 1:
478                        response = ...
479                    elif self.step == 2:
480                        response = ...
481                    return response
482
483            scram_mech = ScramSha256SaslMechanism(username, password)
484
485            imap.sasl_login("SCRAM-SHA-256", scram_mech)
486        """
487        try:
488            return self._command_and_check(
489                "authenticate", mech_name, mech_callable, unpack=True
490            )
491        except exceptions.IMAPClientError as e:
492            raise exceptions.LoginError(str(e))
493
494    def logout(self):
495        """Logout, returning the server response."""
496        typ, data = self._imap.logout()
497        self._check_resp("BYE", "logout", typ, data)
498        logger.debug("Logged out, connection closed")
499        return data[0]
500
501    def shutdown(self):
502        """Close the connection to the IMAP server (without logging out)
503
504        In most cases, :py:meth:`.logout` should be used instead of
505        this. The logout method also shutdown down the connection.
506        """
507        self._imap.shutdown()
508        logger.info("Connection closed")
509
510    @require_capability("ENABLE")
511    def enable(self, *capabilities):
512        """Activate one or more server side capability extensions.
513
514        Most capabilities do not need to be enabled. This is only
515        required for extensions which introduce backwards incompatible
516        behaviour. Two capabilities which may require enable are
517        ``CONDSTORE`` and ``UTF8=ACCEPT``.
518
519        A list of the requested extensions that were successfully
520        enabled on the server is returned.
521
522        Once enabled each extension remains active until the IMAP
523        connection is closed.
524
525        See :rfc:`5161` for more details.
526        """
527        if self._imap.state != "AUTH":
528            raise exceptions.IllegalStateError(
529                "ENABLE command illegal in state %s" % self._imap.state
530            )
531
532        resp = self._raw_command_untagged(
533            b"ENABLE",
534            [to_bytes(c) for c in capabilities],
535            uid=False,
536            response_name="ENABLED",
537            unpack=True,
538        )
539        if not resp:
540            return []
541        return resp.split()
542
543    @require_capability("ID")
544    def id_(self, parameters=None):
545        """Issue the ID command, returning a dict of server implementation
546        fields.
547
548        *parameters* should be specified as a dictionary of field/value pairs,
549        for example: ``{"name": "IMAPClient", "version": "0.12"}``
550        """
551        if parameters is None:
552            args = "NIL"
553        else:
554            if not isinstance(parameters, dict):
555                raise TypeError("'parameters' should be a dictionary")
556            args = seq_to_parenstr(
557                _quote(v) for v in itertools.chain.from_iterable(parameters.items())
558            )
559
560        typ, data = self._imap._simple_command("ID", args)
561        self._checkok("id", typ, data)
562        typ, data = self._imap._untagged_response(typ, data, "ID")
563        return parse_response(data)
564
565    def capabilities(self):
566        """Returns the server capability list.
567
568        If the session is authenticated and the server has returned an
569        untagged CAPABILITY response at authentication time, this
570        response will be returned. Otherwise, the CAPABILITY command
571        will be issued to the server, with the results cached for
572        future calls.
573
574        If the session is not yet authenticated, the capabilities
575        requested at connection time will be returned.
576        """
577        # Ensure cached capabilities aren't used post-STARTTLS. As per
578        # https://tools.ietf.org/html/rfc2595#section-3.1
579        if self._starttls_done and self._imap.state == "NONAUTH":
580            self._cached_capabilities = None
581            return self._do_capabilites()
582
583        # If a capability response has been cached, use that.
584        if self._cached_capabilities:
585            return self._cached_capabilities
586
587        # If the server returned an untagged CAPABILITY response
588        # (during authentication), cache it and return that.
589        untagged = _dict_bytes_normaliser(self._imap.untagged_responses)
590        response = untagged.pop("CAPABILITY", None)
591        if response:
592            self._cached_capabilities = self._normalise_capabilites(response[0])
593            return self._cached_capabilities
594
595        # If authenticated, but don't have a capability response, ask for one
596        if self._imap.state in ("SELECTED", "AUTH"):
597            self._cached_capabilities = self._do_capabilites()
598            return self._cached_capabilities
599
600        # Return capabilities that imaplib requested at connection
601        # time (pre-auth)
602        return tuple(to_bytes(c) for c in self._imap.capabilities)
603
604    def _do_capabilites(self):
605        raw_response = self._command_and_check("capability", unpack=True)
606        return self._normalise_capabilites(raw_response)
607
608    def _normalise_capabilites(self, raw_response):
609        raw_response = to_bytes(raw_response)
610        return tuple(raw_response.upper().split())
611
612    def has_capability(self, capability):
613        """Return ``True`` if the IMAP server has the given *capability*."""
614        # FIXME: this will not detect capabilities that are backwards
615        # compatible with the current level. For instance the SORT
616        # capabilities may in the future be named SORT2 which is
617        # still compatible with the current standard and will not
618        # be detected by this method.
619        return to_bytes(capability).upper() in self.capabilities()
620
621    @require_capability("NAMESPACE")
622    def namespace(self):
623        """Return the namespace for the account as a (personal, other,
624        shared) tuple.
625
626        Each element may be None if no namespace of that type exists,
627        or a sequence of (prefix, separator) pairs.
628
629        For convenience the tuple elements may be accessed
630        positionally or using attributes named *personal*, *other* and
631        *shared*.
632
633        See :rfc:`2342` for more details.
634        """
635        data = self._command_and_check("namespace")
636        parts = []
637        for item in parse_response(data):
638            if item is None:
639                parts.append(item)
640            else:
641                converted = []
642                for prefix, separator in item:
643                    if self.folder_encode:
644                        prefix = decode_utf7(prefix)
645                    converted.append((prefix, to_unicode(separator)))
646                parts.append(tuple(converted))
647        return Namespace(*parts)
648
649    def list_folders(self, directory="", pattern="*"):
650        """Get a listing of folders on the server as a list of
651        ``(flags, delimiter, name)`` tuples.
652
653        Specifying *directory* will limit returned folders to the
654        given base directory. The directory and any child directories
655        will returned.
656
657        Specifying *pattern* will limit returned folders to those with
658        matching names. The wildcards are supported in
659        *pattern*. ``*`` matches zero or more of any character and
660        ``%`` matches 0 or more characters except the folder
661        delimiter.
662
663        Calling list_folders with no arguments will recursively list
664        all folders available for the logged in user.
665
666        Folder names are always returned as unicode strings, and
667        decoded from modified UTF-7, except if folder_decode is not
668        set.
669        """
670        return self._do_list("LIST", directory, pattern)
671
672    @require_capability("XLIST")
673    def xlist_folders(self, directory="", pattern="*"):
674        """Execute the XLIST command, returning ``(flags, delimiter,
675        name)`` tuples.
676
677        This method returns special flags for each folder and a
678        localized name for certain folders (e.g. the name of the
679        inbox may be localized and the flags can be used to
680        determine the actual inbox, even if the name has been
681        localized.
682
683        A ``XLIST`` response could look something like::
684
685            [((b'\\HasNoChildren', b'\\Inbox'), b'/', u'Inbox'),
686             ((b'\\Noselect', b'\\HasChildren'), b'/', u'[Gmail]'),
687             ((b'\\HasNoChildren', b'\\AllMail'), b'/', u'[Gmail]/All Mail'),
688             ((b'\\HasNoChildren', b'\\Drafts'), b'/', u'[Gmail]/Drafts'),
689             ((b'\\HasNoChildren', b'\\Important'), b'/', u'[Gmail]/Important'),
690             ((b'\\HasNoChildren', b'\\Sent'), b'/', u'[Gmail]/Sent Mail'),
691             ((b'\\HasNoChildren', b'\\Spam'), b'/', u'[Gmail]/Spam'),
692             ((b'\\HasNoChildren', b'\\Starred'), b'/', u'[Gmail]/Starred'),
693             ((b'\\HasNoChildren', b'\\Trash'), b'/', u'[Gmail]/Trash')]
694
695        This is a *deprecated* Gmail-specific IMAP extension (See
696        https://developers.google.com/gmail/imap_extensions#xlist_is_deprecated
697        for more information).
698
699        The *directory* and *pattern* arguments are as per
700        list_folders().
701        """
702        return self._do_list("XLIST", directory, pattern)
703
704    def list_sub_folders(self, directory="", pattern="*"):
705        """Return a list of subscribed folders on the server as
706        ``(flags, delimiter, name)`` tuples.
707
708        The default behaviour will list all subscribed folders. The
709        *directory* and *pattern* arguments are as per list_folders().
710        """
711        return self._do_list("LSUB", directory, pattern)
712
713    def _do_list(self, cmd, directory, pattern):
714        directory = self._normalise_folder(directory)
715        pattern = self._normalise_folder(pattern)
716        typ, dat = self._imap._simple_command(cmd, directory, pattern)
717        self._checkok(cmd, typ, dat)
718        typ, dat = self._imap._untagged_response(typ, dat, cmd)
719        return self._proc_folder_list(dat)
720
721    def _proc_folder_list(self, folder_data):
722        # Filter out empty strings and None's.
723        # This also deals with the special case of - no 'untagged'
724        # responses (ie, no folders). This comes back as [None].
725        folder_data = [item for item in folder_data if item not in (b"", None)]
726
727        ret = []
728        parsed = parse_response(folder_data)
729        for flags, delim, name in chunk(parsed, size=3):
730            if isinstance(name, (int, long)):
731                # Some IMAP implementations return integer folder names
732                # with quotes. These get parsed to ints so convert them
733                # back to strings.
734                name = text_type(name)
735            elif self.folder_encode:
736                name = decode_utf7(name)
737
738            ret.append((flags, delim, name))
739        return ret
740
741    def find_special_folder(self, folder_flag):
742        """Try to locate a special folder, like the Sent or Trash folder.
743
744        >>> server.find_special_folder(imapclient.SENT)
745        'INBOX.Sent'
746
747        This function tries its best to find the correct folder (if any) but
748        uses heuristics when the server is unable to precisely tell where
749        special folders are located.
750
751        Returns the name of the folder if found, or None otherwise.
752        """
753        # Detect folder by looking for known attributes
754        # TODO: avoid listing all folders by using extended LIST (RFC6154)
755        if self.has_capability("SPECIAL-USE"):
756            for folder in self.list_folders():
757                if folder and len(folder[0]) > 0 and folder_flag in folder[0]:
758                    return folder[2]
759
760        # Detect folder by looking for common names
761        # We only look for folders in the "personal" namespace of the user
762        if self.has_capability("NAMESPACE"):
763            personal_namespaces = self.namespace().personal
764        else:
765            personal_namespaces = _POPULAR_PERSONAL_NAMESPACES
766
767        for personal_namespace in personal_namespaces:
768            for pattern in _POPULAR_SPECIAL_FOLDERS.get(folder_flag, tuple()):
769                pattern = personal_namespace[0] + pattern
770                sent_folders = self.list_folders(pattern=pattern)
771                if sent_folders:
772                    return sent_folders[0][2]
773
774        return None
775
776    def select_folder(self, folder, readonly=False):
777        """Set the current folder on the server.
778
779        Future calls to methods such as search and fetch will act on
780        the selected folder.
781
782        Returns a dictionary containing the ``SELECT`` response. At least
783        the ``b'EXISTS'``, ``b'FLAGS'`` and ``b'RECENT'`` keys are guaranteed
784        to exist. An example::
785
786            {b'EXISTS': 3,
787             b'FLAGS': (b'\\Answered', b'\\Flagged', b'\\Deleted', ... ),
788             b'RECENT': 0,
789             b'PERMANENTFLAGS': (b'\\Answered', b'\\Flagged', b'\\Deleted', ... ),
790             b'READ-WRITE': True,
791             b'UIDNEXT': 11,
792             b'UIDVALIDITY': 1239278212}
793        """
794        self._command_and_check("select", self._normalise_folder(folder), readonly)
795        return self._process_select_response(self._imap.untagged_responses)
796
797    @require_capability("UNSELECT")
798    def unselect_folder(self):
799        r"""Unselect the current folder and release associated resources.
800
801        Unlike ``close_folder``, the ``UNSELECT`` command does not expunge
802        the mailbox, keeping messages with \Deleted flag set for example.
803
804        Returns the UNSELECT response string returned by the server.
805        """
806        logger.debug("< UNSELECT")
807        # IMAP4 class has no `unselect` method so we can't use `_command_and_check` there
808        _typ, data = self._imap._simple_command("UNSELECT")
809        return data[0]
810
811    def _process_select_response(self, resp):
812        untagged = _dict_bytes_normaliser(resp)
813        out = {}
814
815        # imaplib doesn't parse these correctly (broken regex) so replace
816        # with the raw values out of the OK section
817        for line in untagged.get("OK", []):
818            match = _RE_SELECT_RESPONSE.match(line)
819            if match:
820                key = match.group("key")
821                if key == b"PERMANENTFLAGS":
822                    out[key] = tuple(match.group("data").split())
823
824        for key, value in iteritems(untagged):
825            key = key.upper()
826            if key in (b"OK", b"PERMANENTFLAGS"):
827                continue  # already handled above
828            elif key in (
829                b"EXISTS",
830                b"RECENT",
831                b"UIDNEXT",
832                b"UIDVALIDITY",
833                b"HIGHESTMODSEQ",
834            ):
835                value = int(value[0])
836            elif key == b"READ-WRITE":
837                value = True
838            elif key == b"FLAGS":
839                value = tuple(value[0][1:-1].split())
840            out[key] = value
841        return out
842
843    def noop(self):
844        """Execute the NOOP command.
845
846        This command returns immediately, returning any server side
847        status updates. It can also be used to reset any auto-logout
848        timers.
849
850        The return value is the server command response message
851        followed by a list of status responses. For example::
852
853            (b'NOOP completed.',
854             [(4, b'EXISTS'),
855              (3, b'FETCH', (b'FLAGS', (b'bar', b'sne'))),
856              (6, b'FETCH', (b'FLAGS', (b'sne',)))])
857
858        """
859        tag = self._imap._command("NOOP")
860        return self._consume_until_tagged_response(tag, "NOOP")
861
862    @require_capability("IDLE")
863    def idle(self):
864        """Put the server into IDLE mode.
865
866        In this mode the server will return unsolicited responses
867        about changes to the selected mailbox. This method returns
868        immediately. Use ``idle_check()`` to look for IDLE responses
869        and ``idle_done()`` to stop IDLE mode.
870
871        .. note::
872
873            Any other commands issued while the server is in IDLE
874            mode will fail.
875
876        See :rfc:`2177` for more information about the IDLE extension.
877        """
878        self._idle_tag = self._imap._command("IDLE")
879        resp = self._imap._get_response()
880        if resp is not None:
881            raise exceptions.IMAPClientError("Unexpected IDLE response: %s" % resp)
882
883    def _poll_socket(self, sock, timeout=None):
884        """
885        Polls the socket for events telling us it's available to read.
886        This implementation is more scalable because it ALLOWS your process
887        to have more than 1024 file descriptors.
888        """
889        poller = select.poll()
890        poller.register(sock.fileno(), select.POLLIN)
891        timeout = timeout * 1000 if timeout is not None else None
892        return poller.poll(timeout)
893
894    def _select_poll_socket(self, sock, timeout=None):
895        """
896        Polls the socket for events telling us it's available to read.
897        This implementation is a fallback because it FAILS if your process
898        has more than 1024 file descriptors.
899        We still need this for Windows and some other niche systems.
900        """
901        return select.select([sock], [], [], timeout)[0]
902
903    @require_capability("IDLE")
904    def idle_check(self, timeout=None):
905        """Check for any IDLE responses sent by the server.
906
907        This method should only be called if the server is in IDLE
908        mode (see ``idle()``).
909
910        By default, this method will block until an IDLE response is
911        received. If *timeout* is provided, the call will block for at
912        most this many seconds while waiting for an IDLE response.
913
914        The return value is a list of received IDLE responses. These
915        will be parsed with values converted to appropriate types. For
916        example::
917
918            [(b'OK', b'Still here'),
919             (1, b'EXISTS'),
920             (1, b'FETCH', (b'FLAGS', (b'\\NotJunk',)))]
921        """
922        sock = self._sock
923
924        # make the socket non-blocking so the timeout can be
925        # implemented for this call
926        sock.settimeout(None)
927        sock.setblocking(0)
928
929        if POLL_SUPPORT:
930            poll_func = self._poll_socket
931        else:
932            poll_func = self._select_poll_socket
933
934        try:
935            resps = []
936            events = poll_func(sock, timeout)
937            if events:
938                while True:
939                    try:
940                        line = self._imap._get_line()
941                    except (socket.timeout, socket.error):
942                        break
943                    except IMAPClient.AbortError:
944                        # An imaplib.IMAP4.abort with "EOF" is raised
945                        # under Python 3
946                        err = sys.exc_info()[1]
947                        if "EOF" in err.args[0]:
948                            break
949                        else:
950                            raise
951                    else:
952                        resps.append(_parse_untagged_response(line))
953            return resps
954        finally:
955            sock.setblocking(1)
956            self._set_read_timeout()
957
958    @require_capability("IDLE")
959    def idle_done(self):
960        """Take the server out of IDLE mode.
961
962        This method should only be called if the server is already in
963        IDLE mode.
964
965        The return value is of the form ``(command_text,
966        idle_responses)`` where *command_text* is the text sent by the
967        server when the IDLE command finished (eg. ``b'Idle
968        terminated'``) and *idle_responses* is a list of parsed idle
969        responses received since the last call to ``idle_check()`` (if
970        any). These are returned in parsed form as per
971        ``idle_check()``.
972        """
973        logger.debug("< DONE")
974        self._imap.send(b"DONE\r\n")
975        return self._consume_until_tagged_response(self._idle_tag, "IDLE")
976
977    def folder_status(self, folder, what=None):
978        """Return the status of *folder*.
979
980        *what* should be a sequence of status items to query. This
981        defaults to ``('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY',
982        'UNSEEN')``.
983
984        Returns a dictionary of the status items for the folder with
985        keys matching *what*.
986        """
987        if what is None:
988            what = ("MESSAGES", "RECENT", "UIDNEXT", "UIDVALIDITY", "UNSEEN")
989        else:
990            what = normalise_text_list(what)
991        what_ = "(%s)" % (" ".join(what))
992
993        fname = self._normalise_folder(folder)
994        data = self._command_and_check("status", fname, what_)
995        response = parse_response(data)
996        status_items = response[-1]
997        return dict(as_pairs(status_items))
998
999    def close_folder(self):
1000        """Close the currently selected folder, returning the server
1001        response string.
1002        """
1003        return self._command_and_check("close", unpack=True)
1004
1005    def create_folder(self, folder):
1006        """Create *folder* on the server returning the server response string."""
1007        return self._command_and_check(
1008            "create", self._normalise_folder(folder), unpack=True
1009        )
1010
1011    def rename_folder(self, old_name, new_name):
1012        """Change the name of a folder on the server."""
1013        return self._command_and_check(
1014            "rename",
1015            self._normalise_folder(old_name),
1016            self._normalise_folder(new_name),
1017            unpack=True,
1018        )
1019
1020    def delete_folder(self, folder):
1021        """Delete *folder* on the server returning the server response string."""
1022        return self._command_and_check(
1023            "delete", self._normalise_folder(folder), unpack=True
1024        )
1025
1026    def folder_exists(self, folder):
1027        """Return ``True`` if *folder* exists on the server."""
1028        return len(self.list_folders("", folder)) > 0
1029
1030    def subscribe_folder(self, folder):
1031        """Subscribe to *folder*, returning the server response string."""
1032        return self._command_and_check("subscribe", self._normalise_folder(folder))
1033
1034    def unsubscribe_folder(self, folder):
1035        """Unsubscribe to *folder*, returning the server response string."""
1036        return self._command_and_check("unsubscribe", self._normalise_folder(folder))
1037
1038    def search(self, criteria="ALL", charset=None):
1039        """Return a list of messages ids from the currently selected
1040        folder matching *criteria*.
1041
1042        *criteria* should be a sequence of one or more criteria
1043        items. Each criteria item may be either unicode or
1044        bytes. Example values::
1045
1046            [u'UNSEEN']
1047            [u'SMALLER', 500]
1048            [b'NOT', b'DELETED']
1049            [u'TEXT', u'foo bar', u'FLAGGED', u'SUBJECT', u'baz']
1050            [u'SINCE', date(2005, 4, 3)]
1051
1052        IMAPClient will perform conversion and quoting as
1053        required. The caller shouldn't do this.
1054
1055        It is also possible (but not recommended) to pass the combined
1056        criteria as a single string. In this case IMAPClient won't
1057        perform quoting, allowing lower-level specification of
1058        criteria. Examples of this style::
1059
1060            u'UNSEEN'
1061            u'SMALLER 500'
1062            b'NOT DELETED'
1063            u'TEXT "foo bar" FLAGGED SUBJECT "baz"'
1064            b'SINCE 03-Apr-2005'
1065
1066        To support complex search expressions, criteria lists can be
1067        nested. IMAPClient will insert parentheses in the right
1068        places. The following will match messages that are both not
1069        flagged and do not have "foo" in the subject:
1070
1071            ['NOT', ['SUBJECT', 'foo', 'FLAGGED']]
1072
1073        *charset* specifies the character set of the criteria. It
1074        defaults to US-ASCII as this is the only charset that a server
1075        is required to support by the RFC. UTF-8 is commonly supported
1076        however.
1077
1078        Any criteria specified using unicode will be encoded as per
1079        *charset*. Specifying a unicode criteria that can not be
1080        encoded using *charset* will result in an error.
1081
1082        Any criteria specified using bytes will be sent as-is but
1083        should use an encoding that matches *charset* (the character
1084        set given is still passed on to the server).
1085
1086        See :rfc:`3501#section-6.4.4` for more details.
1087
1088        Note that criteria arguments that are 8-bit will be
1089        transparently sent by IMAPClient as IMAP literals to ensure
1090        adherence to IMAP standards.
1091
1092        The returned list of message ids will have a special *modseq*
1093        attribute. This is set if the server included a MODSEQ value
1094        to the search response (i.e. if a MODSEQ criteria was included
1095        in the search).
1096
1097        """
1098        return self._search(criteria, charset)
1099
1100    @require_capability("X-GM-EXT-1")
1101    def gmail_search(self, query, charset="UTF-8"):
1102        """Search using Gmail's X-GM-RAW attribute.
1103
1104        *query* should be a valid Gmail search query string. For
1105        example: ``has:attachment in:unread``. The search string may
1106        be unicode and will be encoded using the specified *charset*
1107        (defaulting to UTF-8).
1108
1109        This method only works for IMAP servers that support X-GM-RAW,
1110        which is only likely to be Gmail.
1111
1112        See https://developers.google.com/gmail/imap_extensions#extension_of_the_search_command_x-gm-raw
1113        for more info.
1114        """
1115        return self._search([b"X-GM-RAW", query], charset)
1116
1117    def _search(self, criteria, charset):
1118        args = []
1119        if charset:
1120            args.extend([b"CHARSET", to_bytes(charset)])
1121        args.extend(_normalise_search_criteria(criteria, charset))
1122
1123        try:
1124            data = self._raw_command_untagged(b"SEARCH", args)
1125        except imaplib.IMAP4.error as e:
1126            # Make BAD IMAP responses easier to understand to the user, with a link to the docs
1127            m = re.match(r"SEARCH command error: BAD \[(.+)\]", str(e))
1128            if m:
1129                raise exceptions.InvalidCriteriaError(
1130                    "{original_msg}\n\n"
1131                    "This error may have been caused by a syntax error in the criteria: "
1132                    "{criteria}\nPlease refer to the documentation for more information "
1133                    "about search criteria syntax..\n"
1134                    "https://imapclient.readthedocs.io/en/master/#imapclient.IMAPClient.search".format(
1135                        original_msg=m.group(1),
1136                        criteria='"%s"' % criteria
1137                        if not isinstance(criteria, list)
1138                        else criteria,
1139                    )
1140                )
1141
1142            # If the exception is not from a BAD IMAP response, re-raise as-is
1143            raise
1144
1145        return parse_message_list(data)
1146
1147    @require_capability("SORT")
1148    def sort(self, sort_criteria, criteria="ALL", charset="UTF-8"):
1149        """Return a list of message ids from the currently selected
1150        folder, sorted by *sort_criteria* and optionally filtered by
1151        *criteria*.
1152
1153        *sort_criteria* may be specified as a sequence of strings or a
1154        single string. IMAPClient will take care any required
1155        conversions. Valid *sort_criteria* values::
1156
1157            ['ARRIVAL']
1158            ['SUBJECT', 'ARRIVAL']
1159            'ARRIVAL'
1160            'REVERSE SIZE'
1161
1162        The *criteria* and *charset* arguments are as per
1163        :py:meth:`.search`.
1164
1165        See :rfc:`5256` for full details.
1166
1167        Note that SORT is an extension to the IMAP4 standard so it may
1168        not be supported by all IMAP servers.
1169        """
1170        args = [
1171            _normalise_sort_criteria(sort_criteria),
1172            to_bytes(charset),
1173        ]
1174        args.extend(_normalise_search_criteria(criteria, charset))
1175        ids = self._raw_command_untagged(b"SORT", args, unpack=True)
1176        return [long(i) for i in ids.split()]
1177
1178    def thread(self, algorithm="REFERENCES", criteria="ALL", charset="UTF-8"):
1179        """Return a list of messages threads from the currently
1180        selected folder which match *criteria*.
1181
1182        Each returned thread is a list of messages ids. An example
1183        return value containing three message threads::
1184
1185            ((1, 2), (3,), (4, 5, 6))
1186
1187        The optional *algorithm* argument specifies the threading
1188        algorithm to use.
1189
1190        The *criteria* and *charset* arguments are as per
1191        :py:meth:`.search`.
1192
1193        See :rfc:`5256` for more details.
1194        """
1195        algorithm = to_bytes(algorithm)
1196        if not self.has_capability(b"THREAD=" + algorithm):
1197            raise exceptions.CapabilityError(
1198                "The server does not support %s threading algorithm" % algorithm
1199            )
1200
1201        args = [algorithm, to_bytes(charset)] + _normalise_search_criteria(
1202            criteria, charset
1203        )
1204        data = self._raw_command_untagged(b"THREAD", args)
1205        return parse_response(data)
1206
1207    def get_flags(self, messages):
1208        """Return the flags set for each message in *messages* from
1209        the currently selected folder.
1210
1211        The return value is a dictionary structured like this: ``{
1212        msgid1: (flag1, flag2, ... ), }``.
1213        """
1214        response = self.fetch(messages, ["FLAGS"])
1215        return self._filter_fetch_dict(response, b"FLAGS")
1216
1217    def add_flags(self, messages, flags, silent=False):
1218        """Add *flags* to *messages* in the currently selected folder.
1219
1220        *flags* should be a sequence of strings.
1221
1222        Returns the flags set for each modified message (see
1223        *get_flags*), or None if *silent* is true.
1224        """
1225        return self._store(b"+FLAGS", messages, flags, b"FLAGS", silent=silent)
1226
1227    def remove_flags(self, messages, flags, silent=False):
1228        """Remove one or more *flags* from *messages* in the currently
1229        selected folder.
1230
1231        *flags* should be a sequence of strings.
1232
1233        Returns the flags set for each modified message (see
1234        *get_flags*), or None if *silent* is true.
1235        """
1236        return self._store(b"-FLAGS", messages, flags, b"FLAGS", silent=silent)
1237
1238    def set_flags(self, messages, flags, silent=False):
1239        """Set the *flags* for *messages* in the currently selected
1240        folder.
1241
1242        *flags* should be a sequence of strings.
1243
1244        Returns the flags set for each modified message (see
1245        *get_flags*), or None if *silent* is true.
1246        """
1247        return self._store(b"FLAGS", messages, flags, b"FLAGS", silent=silent)
1248
1249    def get_gmail_labels(self, messages):
1250        """Return the label set for each message in *messages* in the
1251        currently selected folder.
1252
1253        The return value is a dictionary structured like this: ``{
1254        msgid1: (label1, label2, ... ), }``.
1255
1256        This only works with IMAP servers that support the X-GM-LABELS
1257        attribute (eg. Gmail).
1258        """
1259        response = self.fetch(messages, [b"X-GM-LABELS"])
1260        response = self._filter_fetch_dict(response, b"X-GM-LABELS")
1261        return {
1262            msg: utf7_decode_sequence(labels) for msg, labels in iteritems(response)
1263        }
1264
1265    def add_gmail_labels(self, messages, labels, silent=False):
1266        """Add *labels* to *messages* in the currently selected folder.
1267
1268        *labels* should be a sequence of strings.
1269
1270        Returns the label set for each modified message (see
1271        *get_gmail_labels*), or None if *silent* is true.
1272
1273        This only works with IMAP servers that support the X-GM-LABELS
1274        attribute (eg. Gmail).
1275        """
1276        return self._gm_label_store(b"+X-GM-LABELS", messages, labels, silent=silent)
1277
1278    def remove_gmail_labels(self, messages, labels, silent=False):
1279        """Remove one or more *labels* from *messages* in the
1280        currently selected folder, or None if *silent* is true.
1281
1282        *labels* should be a sequence of strings.
1283
1284        Returns the label set for each modified message (see
1285        *get_gmail_labels*).
1286
1287        This only works with IMAP servers that support the X-GM-LABELS
1288        attribute (eg. Gmail).
1289        """
1290        return self._gm_label_store(b"-X-GM-LABELS", messages, labels, silent=silent)
1291
1292    def set_gmail_labels(self, messages, labels, silent=False):
1293        """Set the *labels* for *messages* in the currently selected
1294        folder.
1295
1296        *labels* should be a sequence of strings.
1297
1298        Returns the label set for each modified message (see
1299        *get_gmail_labels*), or None if *silent* is true.
1300
1301        This only works with IMAP servers that support the X-GM-LABELS
1302        attribute (eg. Gmail).
1303        """
1304        return self._gm_label_store(b"X-GM-LABELS", messages, labels, silent=silent)
1305
1306    def delete_messages(self, messages, silent=False):
1307        """Delete one or more *messages* from the currently selected
1308        folder.
1309
1310        Returns the flags set for each modified message (see
1311        *get_flags*).
1312        """
1313        return self.add_flags(messages, DELETED, silent=silent)
1314
1315    def fetch(self, messages, data, modifiers=None):
1316        """Retrieve selected *data* associated with one or more
1317        *messages* in the currently selected folder.
1318
1319        *data* should be specified as a sequence of strings, one item
1320        per data selector, for example ``['INTERNALDATE',
1321        'RFC822']``.
1322
1323        *modifiers* are required for some extensions to the IMAP
1324        protocol (eg. :rfc:`4551`). These should be a sequence of strings
1325        if specified, for example ``['CHANGEDSINCE 123']``.
1326
1327        A dictionary is returned, indexed by message number. Each item
1328        in this dictionary is also a dictionary, with an entry
1329        corresponding to each item in *data*. Returned values will be
1330        appropriately typed. For example, integer values will be returned as
1331        Python integers, timestamps will be returned as datetime
1332        instances and ENVELOPE responses will be returned as
1333        :py:class:`Envelope <imapclient.response_types.Envelope>` instances.
1334
1335        String data will generally be returned as bytes (Python 3) or
1336        str (Python 2).
1337
1338        In addition to an element for each *data* item, the dict
1339        returned for each message also contains a *SEQ* key containing
1340        the sequence number for the message. This allows for mapping
1341        between the UID and sequence number (when the *use_uid*
1342        property is ``True``).
1343
1344        Example::
1345
1346            >> c.fetch([3293, 3230], ['INTERNALDATE', 'FLAGS'])
1347            {3230: {b'FLAGS': (b'\\Seen',),
1348                    b'INTERNALDATE': datetime.datetime(2011, 1, 30, 13, 32, 9),
1349                    b'SEQ': 84},
1350             3293: {b'FLAGS': (),
1351                    b'INTERNALDATE': datetime.datetime(2011, 2, 24, 19, 30, 36),
1352                    b'SEQ': 110}}
1353
1354        """
1355        if not messages:
1356            return {}
1357
1358        args = [
1359            "FETCH",
1360            join_message_ids(messages),
1361            seq_to_parenstr_upper(data),
1362            seq_to_parenstr_upper(modifiers) if modifiers else None,
1363        ]
1364        if self.use_uid:
1365            args.insert(0, "UID")
1366        tag = self._imap._command(*args)
1367        typ, data = self._imap._command_complete("FETCH", tag)
1368        self._checkok("fetch", typ, data)
1369        typ, data = self._imap._untagged_response(typ, data, "FETCH")
1370        return parse_fetch_response(data, self.normalise_times, self.use_uid)
1371
1372    def append(self, folder, msg, flags=(), msg_time=None):
1373        """Append a message to *folder*.
1374
1375        *msg* should be a string contains the full message including
1376        headers.
1377
1378        *flags* should be a sequence of message flags to set. If not
1379        specified no flags will be set.
1380
1381        *msg_time* is an optional datetime instance specifying the
1382        date and time to set on the message. The server will set a
1383        time if it isn't specified. If *msg_time* contains timezone
1384        information (tzinfo), this will be honoured. Otherwise the
1385        local machine's time zone sent to the server.
1386
1387        Returns the APPEND response as returned by the server.
1388        """
1389        if msg_time:
1390            time_val = '"%s"' % datetime_to_INTERNALDATE(msg_time)
1391            if PY3:
1392                time_val = to_unicode(time_val)
1393            else:
1394                time_val = to_bytes(time_val)
1395        else:
1396            time_val = None
1397        return self._command_and_check(
1398            "append",
1399            self._normalise_folder(folder),
1400            seq_to_parenstr(flags),
1401            time_val,
1402            to_bytes(msg),
1403            unpack=True,
1404        )
1405
1406    @require_capability("MULTIAPPEND")
1407    def multiappend(self, folder, msgs):
1408        """Append messages to *folder* using the MULTIAPPEND feature from :rfc:`3502`.
1409
1410        *msgs* should be a list of strings containing the full message including
1411        headers.
1412
1413        Returns the APPEND response from the server.
1414        """
1415        msgs = [_literal(to_bytes(m)) for m in msgs]
1416
1417        return self._raw_command(
1418            b"APPEND",
1419            [self._normalise_folder(folder)] + msgs,
1420            uid=False,
1421        )
1422
1423    def copy(self, messages, folder):
1424        """Copy one or more messages from the current folder to
1425        *folder*. Returns the COPY response string returned by the
1426        server.
1427        """
1428        return self._command_and_check(
1429            "copy",
1430            join_message_ids(messages),
1431            self._normalise_folder(folder),
1432            uid=True,
1433            unpack=True,
1434        )
1435
1436    @require_capability("MOVE")
1437    def move(self, messages, folder):
1438        """Atomically move messages to another folder.
1439
1440        Requires the MOVE capability, see :rfc:`6851`.
1441
1442        :param messages: List of message UIDs to move.
1443        :param folder: The destination folder name.
1444        """
1445        return self._command_and_check(
1446            "move",
1447            join_message_ids(messages),
1448            self._normalise_folder(folder),
1449            uid=True,
1450            unpack=True,
1451        )
1452
1453    def expunge(self, messages=None):
1454        """When, no *messages* are specified, remove all messages
1455        from the currently selected folder that have the
1456        ``\\Deleted`` flag set.
1457
1458        The return value is the server response message
1459        followed by a list of expunge responses. For example::
1460
1461            ('Expunge completed.',
1462             [(2, 'EXPUNGE'),
1463              (1, 'EXPUNGE'),
1464              (0, 'RECENT')])
1465
1466        In this case, the responses indicate that the message with
1467        sequence numbers 2 and 1 where deleted, leaving no recent
1468        messages in the folder.
1469
1470        See :rfc:`3501#section-6.4.3` section 6.4.3 and
1471        :rfc:`3501#section-7.4.1` section 7.4.1 for more details.
1472
1473        When *messages* are specified, remove the specified messages
1474        from the selected folder, provided those messages also have
1475        the ``\\Deleted`` flag set. The return value is ``None`` in
1476        this case.
1477
1478        Expunging messages by id(s) requires that *use_uid* is
1479        ``True`` for the client.
1480
1481        See :rfc:`4315#section-2.1` section 2.1 for more details.
1482        """
1483        if messages:
1484            if not self.use_uid:
1485                raise ValueError("cannot EXPUNGE by ID when not using uids")
1486            return self._command_and_check(
1487                "EXPUNGE", join_message_ids(messages), uid=True
1488            )
1489        tag = self._imap._command("EXPUNGE")
1490        return self._consume_until_tagged_response(tag, "EXPUNGE")
1491
1492    @require_capability("ACL")
1493    def getacl(self, folder):
1494        """Returns a list of ``(who, acl)`` tuples describing the
1495        access controls for *folder*.
1496        """
1497        data = self._command_and_check("getacl", self._normalise_folder(folder))
1498        parts = list(response_lexer.TokenSource(data))
1499        parts = parts[1:]  # First item is folder name
1500        return [(parts[i], parts[i + 1]) for i in xrange(0, len(parts), 2)]
1501
1502    @require_capability("ACL")
1503    def setacl(self, folder, who, what):
1504        """Set an ACL (*what*) for user (*who*) for a folder.
1505
1506        Set *what* to an empty string to remove an ACL. Returns the
1507        server response string.
1508        """
1509        return self._command_and_check(
1510            "setacl", self._normalise_folder(folder), who, what, unpack=True
1511        )
1512
1513    @require_capability("QUOTA")
1514    def get_quota(self, mailbox="INBOX"):
1515        """Get the quotas associated with a mailbox.
1516
1517        Returns a list of Quota objects.
1518        """
1519        return self.get_quota_root(mailbox)[1]
1520
1521    @require_capability("QUOTA")
1522    def _get_quota(self, quota_root=""):
1523        """Get the quotas associated with a quota root.
1524
1525        This method is not private but put behind an underscore to show that
1526        it is a low-level function. Users probably want to use `get_quota`
1527        instead.
1528
1529        Returns a list of Quota objects.
1530        """
1531        return _parse_quota(self._command_and_check("getquota", _quote(quota_root)))
1532
1533    @require_capability("QUOTA")
1534    def get_quota_root(self, mailbox):
1535        """Get the quota roots for a mailbox.
1536
1537        The IMAP server responds with the quota root and the quotas associated
1538        so there is usually no need to call `get_quota` after.
1539
1540        See :rfc:`2087` for more details.
1541
1542        Return a tuple of MailboxQuotaRoots and list of Quota associated
1543        """
1544        quota_root_rep = self._raw_command_untagged(
1545            b"GETQUOTAROOT", to_bytes(mailbox), uid=False, response_name="QUOTAROOT"
1546        )
1547        quota_rep = pop_with_default(self._imap.untagged_responses, "QUOTA", [])
1548        quota_root_rep = parse_response(quota_root_rep)
1549        quota_root = MailboxQuotaRoots(
1550            to_unicode(quota_root_rep[0]), [to_unicode(q) for q in quota_root_rep[1:]]
1551        )
1552        return quota_root, _parse_quota(quota_rep)
1553
1554    @require_capability("QUOTA")
1555    def set_quota(self, quotas):
1556        """Set one or more quotas on resources.
1557
1558        :param quotas: list of Quota objects
1559        """
1560        if not quotas:
1561            return
1562
1563        quota_root = None
1564        set_quota_args = list()
1565
1566        for quota in quotas:
1567            if quota_root is None:
1568                quota_root = quota.quota_root
1569            elif quota_root != quota.quota_root:
1570                raise ValueError("set_quota only accepts a single quota root")
1571
1572            set_quota_args.append("{} {}".format(quota.resource, quota.limit))
1573
1574        set_quota_args = " ".join(set_quota_args)
1575        args = [to_bytes(_quote(quota_root)), to_bytes("({})".format(set_quota_args))]
1576
1577        response = self._raw_command_untagged(
1578            b"SETQUOTA", args, uid=False, response_name="QUOTA"
1579        )
1580        return _parse_quota(response)
1581
1582    def _check_resp(self, expected, command, typ, data):
1583        """Check command responses for errors.
1584
1585        Raises IMAPClient.Error if the command fails.
1586        """
1587        if typ != expected:
1588            raise exceptions.IMAPClientError(
1589                "%s failed: %s" % (command, to_unicode(data[0]))
1590            )
1591
1592    def _consume_until_tagged_response(self, tag, command):
1593        tagged_commands = self._imap.tagged_commands
1594        resps = []
1595        while True:
1596            line = self._imap._get_response()
1597            if tagged_commands[tag]:
1598                break
1599            resps.append(_parse_untagged_response(line))
1600        typ, data = tagged_commands.pop(tag)
1601        self._checkok(command, typ, data)
1602        return data[0], resps
1603
1604    def _raw_command_untagged(
1605        self, command, args, response_name=None, unpack=False, uid=True
1606    ):
1607        # TODO: eventually this should replace _command_and_check (call it _command)
1608        typ, data = self._raw_command(command, args, uid=uid)
1609        if response_name is None:
1610            response_name = command
1611        typ, data = self._imap._untagged_response(typ, data, to_unicode(response_name))
1612        self._checkok(to_unicode(command), typ, data)
1613        if unpack:
1614            return data[0]
1615        return data
1616
1617    def _raw_command(self, command, args, uid=True):
1618        """Run the specific command with the arguments given. 8-bit arguments
1619        are sent as literals. The return value is (typ, data).
1620
1621        This sidesteps much of imaplib's command sending
1622        infrastructure because imaplib can't send more than one
1623        literal.
1624
1625        *command* should be specified as bytes.
1626        *args* should be specified as a list of bytes.
1627        """
1628        command = command.upper()
1629
1630        if isinstance(args, tuple):
1631            args = list(args)
1632        if not isinstance(args, list):
1633            args = [args]
1634
1635        tag = self._imap._new_tag()
1636        prefix = [to_bytes(tag)]
1637        if uid and self.use_uid:
1638            prefix.append(b"UID")
1639        prefix.append(command)
1640
1641        line = []
1642        for item, is_last in _iter_with_last(prefix + args):
1643            if not isinstance(item, bytes):
1644                raise ValueError("command args must be passed as bytes")
1645
1646            if _is8bit(item):
1647                # If a line was already started send it
1648                if line:
1649                    out = b" ".join(line)
1650                    logger.debug("> %s", out)
1651                    self._imap.send(out)
1652                    line = []
1653
1654                # Now send the (unquoted) literal
1655                if isinstance(item, _quoted):
1656                    item = item.original
1657                self._send_literal(tag, item)
1658                if not is_last:
1659                    self._imap.send(b" ")
1660            else:
1661                line.append(item)
1662
1663        if line:
1664            out = b" ".join(line)
1665            logger.debug("> %s", out)
1666            self._imap.send(out)
1667
1668        self._imap.send(b"\r\n")
1669
1670        return self._imap._command_complete(to_unicode(command), tag)
1671
1672    def _send_literal(self, tag, item):
1673        """Send a single literal for the command with *tag*."""
1674        if b"LITERAL+" in self._cached_capabilities:
1675            out = b" {" + str(len(item)).encode("ascii") + b"+}\r\n" + item
1676            logger.debug("> %s", debug_trunc(out, 64))
1677            self._imap.send(out)
1678            return
1679
1680        out = b" {" + str(len(item)).encode("ascii") + b"}\r\n"
1681        logger.debug("> %s", out)
1682        self._imap.send(out)
1683
1684        # Wait for continuation response
1685        while self._imap._get_response():
1686            tagged_resp = self._imap.tagged_commands.get(tag)
1687            if tagged_resp:
1688                raise exceptions.IMAPClientAbortError(
1689                    "unexpected response while waiting for continuation response: "
1690                    + repr(tagged_resp)
1691                )
1692
1693        logger.debug("   (literal) > %s", debug_trunc(item, 256))
1694        self._imap.send(item)
1695
1696    def _command_and_check(self, command, *args, **kwargs):
1697        unpack = pop_with_default(kwargs, "unpack", False)
1698        uid = pop_with_default(kwargs, "uid", False)
1699        assert not kwargs, "unexpected keyword args: " + ", ".join(kwargs)
1700
1701        if uid and self.use_uid:
1702            if PY3:
1703                command = to_unicode(command)  # imaplib must die
1704            typ, data = self._imap.uid(command, *args)
1705        else:
1706            meth = getattr(self._imap, to_unicode(command))
1707            typ, data = meth(*args)
1708        self._checkok(command, typ, data)
1709        if unpack:
1710            return data[0]
1711        return data
1712
1713    def _checkok(self, command, typ, data):
1714        self._check_resp("OK", command, typ, data)
1715
1716    def _gm_label_store(self, cmd, messages, labels, silent):
1717        response = self._store(
1718            cmd, messages, self._normalise_labels(labels), b"X-GM-LABELS", silent=silent
1719        )
1720        return (
1721            {msg: utf7_decode_sequence(labels) for msg, labels in iteritems(response)}
1722            if response
1723            else None
1724        )
1725
1726    def _store(self, cmd, messages, flags, fetch_key, silent):
1727        """Worker function for the various flag manipulation methods.
1728
1729        *cmd* is the STORE command to use (eg. '+FLAGS').
1730        """
1731        if not messages:
1732            return {}
1733        if silent:
1734            cmd += b".SILENT"
1735
1736        data = self._command_and_check(
1737            "store", join_message_ids(messages), cmd, seq_to_parenstr(flags), uid=True
1738        )
1739        if silent:
1740            return None
1741        return self._filter_fetch_dict(parse_fetch_response(data), fetch_key)
1742
1743    def _filter_fetch_dict(self, fetch_dict, key):
1744        return dict((msgid, data[key]) for msgid, data in iteritems(fetch_dict))
1745
1746    def _normalise_folder(self, folder_name):
1747        if isinstance(folder_name, binary_type):
1748            folder_name = folder_name.decode("ascii")
1749        if self.folder_encode:
1750            folder_name = encode_utf7(folder_name)
1751        return _quote(folder_name)
1752
1753    def _normalise_labels(self, labels):
1754        if isinstance(labels, (text_type, binary_type)):
1755            labels = (labels,)
1756        return [_quote(encode_utf7(l)) for l in labels]
1757
1758    @property
1759    def welcome(self):
1760        """access the server greeting message"""
1761        try:
1762            return self._imap.welcome
1763        except AttributeError:
1764            pass
1765
1766
1767def _quote(arg):
1768    if isinstance(arg, text_type):
1769        arg = arg.replace("\\", "\\\\")
1770        arg = arg.replace('"', '\\"')
1771        q = '"'
1772    else:
1773        arg = arg.replace(b"\\", b"\\\\")
1774        arg = arg.replace(b'"', b'\\"')
1775        q = b'"'
1776    return q + arg + q
1777
1778
1779def _normalise_search_criteria(criteria, charset=None):
1780    if not criteria:
1781        raise exceptions.InvalidCriteriaError("no criteria specified")
1782    if not charset:
1783        charset = "us-ascii"
1784
1785    if isinstance(criteria, (text_type, binary_type)):
1786        return [to_bytes(criteria, charset)]
1787
1788    out = []
1789    for item in criteria:
1790        if isinstance(item, int):
1791            out.append(str(item).encode("ascii"))
1792        elif isinstance(item, (datetime, date)):
1793            out.append(format_criteria_date(item))
1794        elif isinstance(item, (list, tuple)):
1795            # Process nested criteria list and wrap in parens.
1796            inner = _normalise_search_criteria(item)
1797            inner[0] = b"(" + inner[0]
1798            inner[-1] = inner[-1] + b")"
1799            out.extend(inner)  # flatten
1800        else:
1801            out.append(_quoted.maybe(to_bytes(item, charset)))
1802    return out
1803
1804
1805def _normalise_sort_criteria(criteria, charset=None):
1806    if isinstance(criteria, (text_type, binary_type)):
1807        criteria = [criteria]
1808    return b"(" + b" ".join(to_bytes(item).upper() for item in criteria) + b")"
1809
1810
1811class _literal(bytes):
1812    """Hold message data that should always be sent as a literal."""
1813
1814    pass
1815
1816
1817class _quoted(binary_type):
1818    """
1819    This class holds a quoted bytes value which provides access to the
1820    unquoted value via the *original* attribute.
1821
1822    They should be created via the *maybe* classmethod.
1823    """
1824
1825    @classmethod
1826    def maybe(cls, original):
1827        """Maybe quote a bytes value.
1828
1829        If the input requires no quoting it is returned unchanged.
1830
1831        If quoting is required a *_quoted* instance is returned. This
1832        holds the quoted version of the input while also providing
1833        access to the original unquoted source.
1834        """
1835        quoted = original.replace(b"\\", b"\\\\")
1836        quoted = quoted.replace(b'"', b'\\"')
1837        if quoted != original or b" " in quoted or not quoted:
1838            out = cls(b'"' + quoted + b'"')
1839            out.original = original
1840            return out
1841        return original
1842
1843
1844# normalise_text_list, seq_to_parentstr etc have to return unicode
1845# because imaplib handles flags and sort criteria assuming these are
1846# passed as unicode
1847def normalise_text_list(items):
1848    return list(_normalise_text_list(items))
1849
1850
1851def seq_to_parenstr(items):
1852    return _join_and_paren(_normalise_text_list(items))
1853
1854
1855def seq_to_parenstr_upper(items):
1856    return _join_and_paren(item.upper() for item in _normalise_text_list(items))
1857
1858
1859def _join_and_paren(items):
1860    return "(" + " ".join(items) + ")"
1861
1862
1863def _normalise_text_list(items):
1864    if isinstance(items, (text_type, binary_type)):
1865        items = (items,)
1866    return (to_unicode(c) for c in items)
1867
1868
1869def join_message_ids(messages):
1870    """Convert a sequence of messages ids or a single integer message id
1871    into an id byte string for use with IMAP commands
1872    """
1873    if isinstance(messages, (text_type, binary_type, integer_types)):
1874        messages = (to_bytes(messages),)
1875    return b",".join(_maybe_int_to_bytes(m) for m in messages)
1876
1877
1878def _maybe_int_to_bytes(val):
1879    if isinstance(val, integer_types):
1880        return str(val).encode("us-ascii") if PY3 else str(val)
1881    return to_bytes(val)
1882
1883
1884def _parse_untagged_response(text):
1885    assert_imap_protocol(text.startswith(b"* "))
1886    text = text[2:]
1887    if text.startswith((b"OK ", b"NO ")):
1888        return tuple(text.split(b" ", 1))
1889    return parse_response([text])
1890
1891
1892def pop_with_default(dct, key, default):
1893    if key in dct:
1894        return dct.pop(key)
1895    return default
1896
1897
1898def as_pairs(items):
1899    i = 0
1900    last_item = None
1901    for item in items:
1902        if i % 2:
1903            yield last_item, item
1904        else:
1905            last_item = item
1906        i += 1
1907
1908
1909def as_triplets(items):
1910    a = iter(items)
1911    return zip(a, a, a)
1912
1913
1914def _is8bit(data):
1915    return isinstance(data, _literal) or any(b > 127 for b in iterbytes(data))
1916
1917
1918def _iter_with_last(items):
1919    last_i = len(items) - 1
1920    for i, item in enumerate(items):
1921        yield item, i == last_i
1922
1923
1924_not_present = object()
1925
1926
1927class _dict_bytes_normaliser(object):
1928    """Wrap a dict with unicode/bytes keys and normalise the keys to
1929    bytes.
1930    """
1931
1932    def __init__(self, d):
1933        self._d = d
1934
1935    def iteritems(self):
1936        for key, value in iteritems(self._d):
1937            yield to_bytes(key), value
1938
1939    # For Python 3 compatibility.
1940    items = iteritems
1941
1942    def __contains__(self, ink):
1943        for k in self._gen_keys(ink):
1944            if k in self._d:
1945                return True
1946        return False
1947
1948    def get(self, ink, default=_not_present):
1949        for k in self._gen_keys(ink):
1950            try:
1951                return self._d[k]
1952            except KeyError:
1953                pass
1954        if default == _not_present:
1955            raise KeyError(ink)
1956        return default
1957
1958    def pop(self, ink, default=_not_present):
1959        for k in self._gen_keys(ink):
1960            try:
1961                return self._d.pop(k)
1962            except KeyError:
1963                pass
1964        if default == _not_present:
1965            raise KeyError(ink)
1966        return default
1967
1968    def _gen_keys(self, k):
1969        yield k
1970        if isinstance(k, binary_type):
1971            yield to_unicode(k)
1972        else:
1973            yield to_bytes(k)
1974
1975
1976def debug_trunc(v, maxlen):
1977    if len(v) < maxlen:
1978        return repr(v)
1979    hl = maxlen // 2
1980    return repr(v[:hl]) + "..." + repr(v[-hl:])
1981
1982
1983def utf7_decode_sequence(seq):
1984    return [decode_utf7(s) for s in seq]
1985
1986
1987def _parse_quota(quota_rep):
1988    quota_rep = parse_response(quota_rep)
1989    rv = list()
1990    for quota_root, quota_resource_infos in as_pairs(quota_rep):
1991        for quota_resource_info in as_triplets(quota_resource_infos):
1992            rv.append(
1993                Quota(
1994                    quota_root=to_unicode(quota_root),
1995                    resource=to_unicode(quota_resource_info[0]),
1996                    usage=quota_resource_info[1],
1997                    limit=quota_resource_info[2],
1998                )
1999            )
2000    return rv
2001
2002
2003class IMAPlibLoggerAdapter(LoggerAdapter):
2004    """Adapter preventing IMAP secrets from going to the logging facility."""
2005
2006    def process(self, msg, kwargs):
2007        # msg is usually unicode but see #367. Convert bytes to
2008        # unicode if required.
2009        if isinstance(msg, binary_type):
2010            msg = msg.decode("ascii", "ignore")
2011
2012        for command in ("LOGIN", "AUTHENTICATE"):
2013            if msg.startswith(">") and command in msg:
2014                msg_start = msg.split(command)[0]
2015                msg = "{}{} **REDACTED**".format(msg_start, command)
2016                break
2017        return super(IMAPlibLoggerAdapter, self).process(msg, kwargs)
2018