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