1*cda5da8dSAndroid Build Coastguard Worker"""IMAP4 client. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard WorkerBased on RFC 2060. 4*cda5da8dSAndroid Build Coastguard Worker 5*cda5da8dSAndroid Build Coastguard WorkerPublic class: IMAP4 6*cda5da8dSAndroid Build Coastguard WorkerPublic variable: Debug 7*cda5da8dSAndroid Build Coastguard WorkerPublic functions: Internaldate2tuple 8*cda5da8dSAndroid Build Coastguard Worker Int2AP 9*cda5da8dSAndroid Build Coastguard Worker ParseFlags 10*cda5da8dSAndroid Build Coastguard Worker Time2Internaldate 11*cda5da8dSAndroid Build Coastguard Worker""" 12*cda5da8dSAndroid Build Coastguard Worker 13*cda5da8dSAndroid Build Coastguard Worker# Author: Piers Lauder <[email protected]> December 1997. 14*cda5da8dSAndroid Build Coastguard Worker# 15*cda5da8dSAndroid Build Coastguard Worker# Authentication code contributed by Donn Cave <[email protected]> June 1998. 16*cda5da8dSAndroid Build Coastguard Worker# String method conversion by ESR, February 2001. 17*cda5da8dSAndroid Build Coastguard Worker# GET/SETACL contributed by Anthony Baxter <[email protected]> April 2001. 18*cda5da8dSAndroid Build Coastguard Worker# IMAP4_SSL contributed by Tino Lange <[email protected]> March 2002. 19*cda5da8dSAndroid Build Coastguard Worker# GET/SETQUOTA contributed by Andreas Zeidler <[email protected]> June 2002. 20*cda5da8dSAndroid Build Coastguard Worker# PROXYAUTH contributed by Rick Holbert <[email protected]> November 2002. 21*cda5da8dSAndroid Build Coastguard Worker# GET/SETANNOTATION contributed by Tomas Lindroos <[email protected]> June 2005. 22*cda5da8dSAndroid Build Coastguard Worker 23*cda5da8dSAndroid Build Coastguard Worker__version__ = "2.58" 24*cda5da8dSAndroid Build Coastguard Worker 25*cda5da8dSAndroid Build Coastguard Workerimport binascii, errno, random, re, socket, subprocess, sys, time, calendar 26*cda5da8dSAndroid Build Coastguard Workerfrom datetime import datetime, timezone, timedelta 27*cda5da8dSAndroid Build Coastguard Workerfrom io import DEFAULT_BUFFER_SIZE 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Workertry: 30*cda5da8dSAndroid Build Coastguard Worker import ssl 31*cda5da8dSAndroid Build Coastguard Worker HAVE_SSL = True 32*cda5da8dSAndroid Build Coastguard Workerexcept ImportError: 33*cda5da8dSAndroid Build Coastguard Worker HAVE_SSL = False 34*cda5da8dSAndroid Build Coastguard Worker 35*cda5da8dSAndroid Build Coastguard Worker__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", 36*cda5da8dSAndroid Build Coastguard Worker "Int2AP", "ParseFlags", "Time2Internaldate"] 37*cda5da8dSAndroid Build Coastguard Worker 38*cda5da8dSAndroid Build Coastguard Worker# Globals 39*cda5da8dSAndroid Build Coastguard Worker 40*cda5da8dSAndroid Build Coastguard WorkerCRLF = b'\r\n' 41*cda5da8dSAndroid Build Coastguard WorkerDebug = 0 42*cda5da8dSAndroid Build Coastguard WorkerIMAP4_PORT = 143 43*cda5da8dSAndroid Build Coastguard WorkerIMAP4_SSL_PORT = 993 44*cda5da8dSAndroid Build Coastguard WorkerAllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first 45*cda5da8dSAndroid Build Coastguard Worker 46*cda5da8dSAndroid Build Coastguard Worker# Maximal line length when calling readline(). This is to prevent 47*cda5da8dSAndroid Build Coastguard Worker# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1) 48*cda5da8dSAndroid Build Coastguard Worker# don't specify a line length. RFC 2683 suggests limiting client 49*cda5da8dSAndroid Build Coastguard Worker# command lines to 1000 octets and that servers should be prepared 50*cda5da8dSAndroid Build Coastguard Worker# to accept command lines up to 8000 octets, so we used to use 10K here. 51*cda5da8dSAndroid Build Coastguard Worker# In the modern world (eg: gmail) the response to, for example, a 52*cda5da8dSAndroid Build Coastguard Worker# search command can be quite large, so we now use 1M. 53*cda5da8dSAndroid Build Coastguard Worker_MAXLINE = 1000000 54*cda5da8dSAndroid Build Coastguard Worker 55*cda5da8dSAndroid Build Coastguard Worker 56*cda5da8dSAndroid Build Coastguard Worker# Commands 57*cda5da8dSAndroid Build Coastguard Worker 58*cda5da8dSAndroid Build Coastguard WorkerCommands = { 59*cda5da8dSAndroid Build Coastguard Worker # name valid states 60*cda5da8dSAndroid Build Coastguard Worker 'APPEND': ('AUTH', 'SELECTED'), 61*cda5da8dSAndroid Build Coastguard Worker 'AUTHENTICATE': ('NONAUTH',), 62*cda5da8dSAndroid Build Coastguard Worker 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), 63*cda5da8dSAndroid Build Coastguard Worker 'CHECK': ('SELECTED',), 64*cda5da8dSAndroid Build Coastguard Worker 'CLOSE': ('SELECTED',), 65*cda5da8dSAndroid Build Coastguard Worker 'COPY': ('SELECTED',), 66*cda5da8dSAndroid Build Coastguard Worker 'CREATE': ('AUTH', 'SELECTED'), 67*cda5da8dSAndroid Build Coastguard Worker 'DELETE': ('AUTH', 'SELECTED'), 68*cda5da8dSAndroid Build Coastguard Worker 'DELETEACL': ('AUTH', 'SELECTED'), 69*cda5da8dSAndroid Build Coastguard Worker 'ENABLE': ('AUTH', ), 70*cda5da8dSAndroid Build Coastguard Worker 'EXAMINE': ('AUTH', 'SELECTED'), 71*cda5da8dSAndroid Build Coastguard Worker 'EXPUNGE': ('SELECTED',), 72*cda5da8dSAndroid Build Coastguard Worker 'FETCH': ('SELECTED',), 73*cda5da8dSAndroid Build Coastguard Worker 'GETACL': ('AUTH', 'SELECTED'), 74*cda5da8dSAndroid Build Coastguard Worker 'GETANNOTATION':('AUTH', 'SELECTED'), 75*cda5da8dSAndroid Build Coastguard Worker 'GETQUOTA': ('AUTH', 'SELECTED'), 76*cda5da8dSAndroid Build Coastguard Worker 'GETQUOTAROOT': ('AUTH', 'SELECTED'), 77*cda5da8dSAndroid Build Coastguard Worker 'MYRIGHTS': ('AUTH', 'SELECTED'), 78*cda5da8dSAndroid Build Coastguard Worker 'LIST': ('AUTH', 'SELECTED'), 79*cda5da8dSAndroid Build Coastguard Worker 'LOGIN': ('NONAUTH',), 80*cda5da8dSAndroid Build Coastguard Worker 'LOGOUT': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), 81*cda5da8dSAndroid Build Coastguard Worker 'LSUB': ('AUTH', 'SELECTED'), 82*cda5da8dSAndroid Build Coastguard Worker 'MOVE': ('SELECTED',), 83*cda5da8dSAndroid Build Coastguard Worker 'NAMESPACE': ('AUTH', 'SELECTED'), 84*cda5da8dSAndroid Build Coastguard Worker 'NOOP': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), 85*cda5da8dSAndroid Build Coastguard Worker 'PARTIAL': ('SELECTED',), # NB: obsolete 86*cda5da8dSAndroid Build Coastguard Worker 'PROXYAUTH': ('AUTH',), 87*cda5da8dSAndroid Build Coastguard Worker 'RENAME': ('AUTH', 'SELECTED'), 88*cda5da8dSAndroid Build Coastguard Worker 'SEARCH': ('SELECTED',), 89*cda5da8dSAndroid Build Coastguard Worker 'SELECT': ('AUTH', 'SELECTED'), 90*cda5da8dSAndroid Build Coastguard Worker 'SETACL': ('AUTH', 'SELECTED'), 91*cda5da8dSAndroid Build Coastguard Worker 'SETANNOTATION':('AUTH', 'SELECTED'), 92*cda5da8dSAndroid Build Coastguard Worker 'SETQUOTA': ('AUTH', 'SELECTED'), 93*cda5da8dSAndroid Build Coastguard Worker 'SORT': ('SELECTED',), 94*cda5da8dSAndroid Build Coastguard Worker 'STARTTLS': ('NONAUTH',), 95*cda5da8dSAndroid Build Coastguard Worker 'STATUS': ('AUTH', 'SELECTED'), 96*cda5da8dSAndroid Build Coastguard Worker 'STORE': ('SELECTED',), 97*cda5da8dSAndroid Build Coastguard Worker 'SUBSCRIBE': ('AUTH', 'SELECTED'), 98*cda5da8dSAndroid Build Coastguard Worker 'THREAD': ('SELECTED',), 99*cda5da8dSAndroid Build Coastguard Worker 'UID': ('SELECTED',), 100*cda5da8dSAndroid Build Coastguard Worker 'UNSUBSCRIBE': ('AUTH', 'SELECTED'), 101*cda5da8dSAndroid Build Coastguard Worker 'UNSELECT': ('SELECTED',), 102*cda5da8dSAndroid Build Coastguard Worker } 103*cda5da8dSAndroid Build Coastguard Worker 104*cda5da8dSAndroid Build Coastguard Worker# Patterns to match server responses 105*cda5da8dSAndroid Build Coastguard Worker 106*cda5da8dSAndroid Build Coastguard WorkerContinuation = re.compile(br'\+( (?P<data>.*))?') 107*cda5da8dSAndroid Build Coastguard WorkerFlags = re.compile(br'.*FLAGS \((?P<flags>[^\)]*)\)') 108*cda5da8dSAndroid Build Coastguard WorkerInternalDate = re.compile(br'.*INTERNALDATE "' 109*cda5da8dSAndroid Build Coastguard Worker br'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])' 110*cda5da8dSAndroid Build Coastguard Worker br' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])' 111*cda5da8dSAndroid Build Coastguard Worker br' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])' 112*cda5da8dSAndroid Build Coastguard Worker br'"') 113*cda5da8dSAndroid Build Coastguard Worker# Literal is no longer used; kept for backward compatibility. 114*cda5da8dSAndroid Build Coastguard WorkerLiteral = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII) 115*cda5da8dSAndroid Build Coastguard WorkerMapCRLF = re.compile(br'\r\n|\r|\n') 116*cda5da8dSAndroid Build Coastguard Worker# We no longer exclude the ']' character from the data portion of the response 117*cda5da8dSAndroid Build Coastguard Worker# code, even though it violates the RFC. Popular IMAP servers such as Gmail 118*cda5da8dSAndroid Build Coastguard Worker# allow flags with ']', and there are programs (including imaplib!) that can 119*cda5da8dSAndroid Build Coastguard Worker# produce them. The problem with this is if the 'text' portion of the response 120*cda5da8dSAndroid Build Coastguard Worker# includes a ']' we'll parse the response wrong (which is the point of the RFC 121*cda5da8dSAndroid Build Coastguard Worker# restriction). However, that seems less likely to be a problem in practice 122*cda5da8dSAndroid Build Coastguard Worker# than being unable to correctly parse flags that include ']' chars, which 123*cda5da8dSAndroid Build Coastguard Worker# was reported as a real-world problem in issue #21815. 124*cda5da8dSAndroid Build Coastguard WorkerResponse_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]') 125*cda5da8dSAndroid Build Coastguard WorkerUntagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?') 126*cda5da8dSAndroid Build Coastguard Worker# Untagged_status is no longer used; kept for backward compatibility 127*cda5da8dSAndroid Build Coastguard WorkerUntagged_status = re.compile( 128*cda5da8dSAndroid Build Coastguard Worker br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?', re.ASCII) 129*cda5da8dSAndroid Build Coastguard Worker# We compile these in _mode_xxx. 130*cda5da8dSAndroid Build Coastguard Worker_Literal = br'.*{(?P<size>\d+)}$' 131*cda5da8dSAndroid Build Coastguard Worker_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?' 132*cda5da8dSAndroid Build Coastguard Worker 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker 135*cda5da8dSAndroid Build Coastguard Workerclass IMAP4: 136*cda5da8dSAndroid Build Coastguard Worker 137*cda5da8dSAndroid Build Coastguard Worker r"""IMAP4 client class. 138*cda5da8dSAndroid Build Coastguard Worker 139*cda5da8dSAndroid Build Coastguard Worker Instantiate with: IMAP4([host[, port[, timeout=None]]]) 140*cda5da8dSAndroid Build Coastguard Worker 141*cda5da8dSAndroid Build Coastguard Worker host - host's name (default: localhost); 142*cda5da8dSAndroid Build Coastguard Worker port - port number (default: standard IMAP4 port). 143*cda5da8dSAndroid Build Coastguard Worker timeout - socket timeout (default: None) 144*cda5da8dSAndroid Build Coastguard Worker If timeout is not given or is None, 145*cda5da8dSAndroid Build Coastguard Worker the global default socket timeout is used 146*cda5da8dSAndroid Build Coastguard Worker 147*cda5da8dSAndroid Build Coastguard Worker All IMAP4rev1 commands are supported by methods of the same 148*cda5da8dSAndroid Build Coastguard Worker name (in lowercase). 149*cda5da8dSAndroid Build Coastguard Worker 150*cda5da8dSAndroid Build Coastguard Worker All arguments to commands are converted to strings, except for 151*cda5da8dSAndroid Build Coastguard Worker AUTHENTICATE, and the last argument to APPEND which is passed as 152*cda5da8dSAndroid Build Coastguard Worker an IMAP4 literal. If necessary (the string contains any 153*cda5da8dSAndroid Build Coastguard Worker non-printing characters or white-space and isn't enclosed with 154*cda5da8dSAndroid Build Coastguard Worker either parentheses or double quotes) each string is quoted. 155*cda5da8dSAndroid Build Coastguard Worker However, the 'password' argument to the LOGIN command is always 156*cda5da8dSAndroid Build Coastguard Worker quoted. If you want to avoid having an argument string quoted 157*cda5da8dSAndroid Build Coastguard Worker (eg: the 'flags' argument to STORE) then enclose the string in 158*cda5da8dSAndroid Build Coastguard Worker parentheses (eg: "(\Deleted)"). 159*cda5da8dSAndroid Build Coastguard Worker 160*cda5da8dSAndroid Build Coastguard Worker Each command returns a tuple: (type, [data, ...]) where 'type' 161*cda5da8dSAndroid Build Coastguard Worker is usually 'OK' or 'NO', and 'data' is either the text from the 162*cda5da8dSAndroid Build Coastguard Worker tagged response, or untagged results from command. Each 'data' 163*cda5da8dSAndroid Build Coastguard Worker is either a string, or a tuple. If a tuple, then the first part 164*cda5da8dSAndroid Build Coastguard Worker is the header of the response, and the second part contains 165*cda5da8dSAndroid Build Coastguard Worker the data (ie: 'literal' value). 166*cda5da8dSAndroid Build Coastguard Worker 167*cda5da8dSAndroid Build Coastguard Worker Errors raise the exception class <instance>.error("<reason>"). 168*cda5da8dSAndroid Build Coastguard Worker IMAP4 server errors raise <instance>.abort("<reason>"), 169*cda5da8dSAndroid Build Coastguard Worker which is a sub-class of 'error'. Mailbox status changes 170*cda5da8dSAndroid Build Coastguard Worker from READ-WRITE to READ-ONLY raise the exception class 171*cda5da8dSAndroid Build Coastguard Worker <instance>.readonly("<reason>"), which is a sub-class of 'abort'. 172*cda5da8dSAndroid Build Coastguard Worker 173*cda5da8dSAndroid Build Coastguard Worker "error" exceptions imply a program error. 174*cda5da8dSAndroid Build Coastguard Worker "abort" exceptions imply the connection should be reset, and 175*cda5da8dSAndroid Build Coastguard Worker the command re-tried. 176*cda5da8dSAndroid Build Coastguard Worker "readonly" exceptions imply the command should be re-tried. 177*cda5da8dSAndroid Build Coastguard Worker 178*cda5da8dSAndroid Build Coastguard Worker Note: to use this module, you must read the RFCs pertaining to the 179*cda5da8dSAndroid Build Coastguard Worker IMAP4 protocol, as the semantics of the arguments to each IMAP4 180*cda5da8dSAndroid Build Coastguard Worker command are left to the invoker, not to mention the results. Also, 181*cda5da8dSAndroid Build Coastguard Worker most IMAP servers implement a sub-set of the commands available here. 182*cda5da8dSAndroid Build Coastguard Worker """ 183*cda5da8dSAndroid Build Coastguard Worker 184*cda5da8dSAndroid Build Coastguard Worker class error(Exception): pass # Logical errors - debug required 185*cda5da8dSAndroid Build Coastguard Worker class abort(error): pass # Service errors - close and retry 186*cda5da8dSAndroid Build Coastguard Worker class readonly(abort): pass # Mailbox status changed to READ-ONLY 187*cda5da8dSAndroid Build Coastguard Worker 188*cda5da8dSAndroid Build Coastguard Worker def __init__(self, host='', port=IMAP4_PORT, timeout=None): 189*cda5da8dSAndroid Build Coastguard Worker self.debug = Debug 190*cda5da8dSAndroid Build Coastguard Worker self.state = 'LOGOUT' 191*cda5da8dSAndroid Build Coastguard Worker self.literal = None # A literal argument to a command 192*cda5da8dSAndroid Build Coastguard Worker self.tagged_commands = {} # Tagged commands awaiting response 193*cda5da8dSAndroid Build Coastguard Worker self.untagged_responses = {} # {typ: [data, ...], ...} 194*cda5da8dSAndroid Build Coastguard Worker self.continuation_response = '' # Last continuation response 195*cda5da8dSAndroid Build Coastguard Worker self.is_readonly = False # READ-ONLY desired state 196*cda5da8dSAndroid Build Coastguard Worker self.tagnum = 0 197*cda5da8dSAndroid Build Coastguard Worker self._tls_established = False 198*cda5da8dSAndroid Build Coastguard Worker self._mode_ascii() 199*cda5da8dSAndroid Build Coastguard Worker 200*cda5da8dSAndroid Build Coastguard Worker # Open socket to server. 201*cda5da8dSAndroid Build Coastguard Worker 202*cda5da8dSAndroid Build Coastguard Worker self.open(host, port, timeout) 203*cda5da8dSAndroid Build Coastguard Worker 204*cda5da8dSAndroid Build Coastguard Worker try: 205*cda5da8dSAndroid Build Coastguard Worker self._connect() 206*cda5da8dSAndroid Build Coastguard Worker except Exception: 207*cda5da8dSAndroid Build Coastguard Worker try: 208*cda5da8dSAndroid Build Coastguard Worker self.shutdown() 209*cda5da8dSAndroid Build Coastguard Worker except OSError: 210*cda5da8dSAndroid Build Coastguard Worker pass 211*cda5da8dSAndroid Build Coastguard Worker raise 212*cda5da8dSAndroid Build Coastguard Worker 213*cda5da8dSAndroid Build Coastguard Worker def _mode_ascii(self): 214*cda5da8dSAndroid Build Coastguard Worker self.utf8_enabled = False 215*cda5da8dSAndroid Build Coastguard Worker self._encoding = 'ascii' 216*cda5da8dSAndroid Build Coastguard Worker self.Literal = re.compile(_Literal, re.ASCII) 217*cda5da8dSAndroid Build Coastguard Worker self.Untagged_status = re.compile(_Untagged_status, re.ASCII) 218*cda5da8dSAndroid Build Coastguard Worker 219*cda5da8dSAndroid Build Coastguard Worker 220*cda5da8dSAndroid Build Coastguard Worker def _mode_utf8(self): 221*cda5da8dSAndroid Build Coastguard Worker self.utf8_enabled = True 222*cda5da8dSAndroid Build Coastguard Worker self._encoding = 'utf-8' 223*cda5da8dSAndroid Build Coastguard Worker self.Literal = re.compile(_Literal) 224*cda5da8dSAndroid Build Coastguard Worker self.Untagged_status = re.compile(_Untagged_status) 225*cda5da8dSAndroid Build Coastguard Worker 226*cda5da8dSAndroid Build Coastguard Worker 227*cda5da8dSAndroid Build Coastguard Worker def _connect(self): 228*cda5da8dSAndroid Build Coastguard Worker # Create unique tag for this session, 229*cda5da8dSAndroid Build Coastguard Worker # and compile tagged response matcher. 230*cda5da8dSAndroid Build Coastguard Worker 231*cda5da8dSAndroid Build Coastguard Worker self.tagpre = Int2AP(random.randint(4096, 65535)) 232*cda5da8dSAndroid Build Coastguard Worker self.tagre = re.compile(br'(?P<tag>' 233*cda5da8dSAndroid Build Coastguard Worker + self.tagpre 234*cda5da8dSAndroid Build Coastguard Worker + br'\d+) (?P<type>[A-Z]+) (?P<data>.*)', re.ASCII) 235*cda5da8dSAndroid Build Coastguard Worker 236*cda5da8dSAndroid Build Coastguard Worker # Get server welcome message, 237*cda5da8dSAndroid Build Coastguard Worker # request and store CAPABILITY response. 238*cda5da8dSAndroid Build Coastguard Worker 239*cda5da8dSAndroid Build Coastguard Worker if __debug__: 240*cda5da8dSAndroid Build Coastguard Worker self._cmd_log_len = 10 241*cda5da8dSAndroid Build Coastguard Worker self._cmd_log_idx = 0 242*cda5da8dSAndroid Build Coastguard Worker self._cmd_log = {} # Last `_cmd_log_len' interactions 243*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 1: 244*cda5da8dSAndroid Build Coastguard Worker self._mesg('imaplib version %s' % __version__) 245*cda5da8dSAndroid Build Coastguard Worker self._mesg('new IMAP4 connection, tag=%s' % self.tagpre) 246*cda5da8dSAndroid Build Coastguard Worker 247*cda5da8dSAndroid Build Coastguard Worker self.welcome = self._get_response() 248*cda5da8dSAndroid Build Coastguard Worker if 'PREAUTH' in self.untagged_responses: 249*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' 250*cda5da8dSAndroid Build Coastguard Worker elif 'OK' in self.untagged_responses: 251*cda5da8dSAndroid Build Coastguard Worker self.state = 'NONAUTH' 252*cda5da8dSAndroid Build Coastguard Worker else: 253*cda5da8dSAndroid Build Coastguard Worker raise self.error(self.welcome) 254*cda5da8dSAndroid Build Coastguard Worker 255*cda5da8dSAndroid Build Coastguard Worker self._get_capabilities() 256*cda5da8dSAndroid Build Coastguard Worker if __debug__: 257*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 3: 258*cda5da8dSAndroid Build Coastguard Worker self._mesg('CAPABILITIES: %r' % (self.capabilities,)) 259*cda5da8dSAndroid Build Coastguard Worker 260*cda5da8dSAndroid Build Coastguard Worker for version in AllowedVersions: 261*cda5da8dSAndroid Build Coastguard Worker if not version in self.capabilities: 262*cda5da8dSAndroid Build Coastguard Worker continue 263*cda5da8dSAndroid Build Coastguard Worker self.PROTOCOL_VERSION = version 264*cda5da8dSAndroid Build Coastguard Worker return 265*cda5da8dSAndroid Build Coastguard Worker 266*cda5da8dSAndroid Build Coastguard Worker raise self.error('server not IMAP4 compliant') 267*cda5da8dSAndroid Build Coastguard Worker 268*cda5da8dSAndroid Build Coastguard Worker 269*cda5da8dSAndroid Build Coastguard Worker def __getattr__(self, attr): 270*cda5da8dSAndroid Build Coastguard Worker # Allow UPPERCASE variants of IMAP4 command methods. 271*cda5da8dSAndroid Build Coastguard Worker if attr in Commands: 272*cda5da8dSAndroid Build Coastguard Worker return getattr(self, attr.lower()) 273*cda5da8dSAndroid Build Coastguard Worker raise AttributeError("Unknown IMAP4 command: '%s'" % attr) 274*cda5da8dSAndroid Build Coastguard Worker 275*cda5da8dSAndroid Build Coastguard Worker def __enter__(self): 276*cda5da8dSAndroid Build Coastguard Worker return self 277*cda5da8dSAndroid Build Coastguard Worker 278*cda5da8dSAndroid Build Coastguard Worker def __exit__(self, *args): 279*cda5da8dSAndroid Build Coastguard Worker if self.state == "LOGOUT": 280*cda5da8dSAndroid Build Coastguard Worker return 281*cda5da8dSAndroid Build Coastguard Worker 282*cda5da8dSAndroid Build Coastguard Worker try: 283*cda5da8dSAndroid Build Coastguard Worker self.logout() 284*cda5da8dSAndroid Build Coastguard Worker except OSError: 285*cda5da8dSAndroid Build Coastguard Worker pass 286*cda5da8dSAndroid Build Coastguard Worker 287*cda5da8dSAndroid Build Coastguard Worker 288*cda5da8dSAndroid Build Coastguard Worker # Overridable methods 289*cda5da8dSAndroid Build Coastguard Worker 290*cda5da8dSAndroid Build Coastguard Worker 291*cda5da8dSAndroid Build Coastguard Worker def _create_socket(self, timeout): 292*cda5da8dSAndroid Build Coastguard Worker # Default value of IMAP4.host is '', but socket.getaddrinfo() 293*cda5da8dSAndroid Build Coastguard Worker # (which is used by socket.create_connection()) expects None 294*cda5da8dSAndroid Build Coastguard Worker # as a default value for host. 295*cda5da8dSAndroid Build Coastguard Worker if timeout is not None and not timeout: 296*cda5da8dSAndroid Build Coastguard Worker raise ValueError('Non-blocking socket (timeout=0) is not supported') 297*cda5da8dSAndroid Build Coastguard Worker host = None if not self.host else self.host 298*cda5da8dSAndroid Build Coastguard Worker sys.audit("imaplib.open", self, self.host, self.port) 299*cda5da8dSAndroid Build Coastguard Worker address = (host, self.port) 300*cda5da8dSAndroid Build Coastguard Worker if timeout is not None: 301*cda5da8dSAndroid Build Coastguard Worker return socket.create_connection(address, timeout) 302*cda5da8dSAndroid Build Coastguard Worker return socket.create_connection(address) 303*cda5da8dSAndroid Build Coastguard Worker 304*cda5da8dSAndroid Build Coastguard Worker def open(self, host='', port=IMAP4_PORT, timeout=None): 305*cda5da8dSAndroid Build Coastguard Worker """Setup connection to remote server on "host:port" 306*cda5da8dSAndroid Build Coastguard Worker (default: localhost:standard IMAP4 port). 307*cda5da8dSAndroid Build Coastguard Worker This connection will be used by the routines: 308*cda5da8dSAndroid Build Coastguard Worker read, readline, send, shutdown. 309*cda5da8dSAndroid Build Coastguard Worker """ 310*cda5da8dSAndroid Build Coastguard Worker self.host = host 311*cda5da8dSAndroid Build Coastguard Worker self.port = port 312*cda5da8dSAndroid Build Coastguard Worker self.sock = self._create_socket(timeout) 313*cda5da8dSAndroid Build Coastguard Worker self.file = self.sock.makefile('rb') 314*cda5da8dSAndroid Build Coastguard Worker 315*cda5da8dSAndroid Build Coastguard Worker 316*cda5da8dSAndroid Build Coastguard Worker def read(self, size): 317*cda5da8dSAndroid Build Coastguard Worker """Read 'size' bytes from remote.""" 318*cda5da8dSAndroid Build Coastguard Worker return self.file.read(size) 319*cda5da8dSAndroid Build Coastguard Worker 320*cda5da8dSAndroid Build Coastguard Worker 321*cda5da8dSAndroid Build Coastguard Worker def readline(self): 322*cda5da8dSAndroid Build Coastguard Worker """Read line from remote.""" 323*cda5da8dSAndroid Build Coastguard Worker line = self.file.readline(_MAXLINE + 1) 324*cda5da8dSAndroid Build Coastguard Worker if len(line) > _MAXLINE: 325*cda5da8dSAndroid Build Coastguard Worker raise self.error("got more than %d bytes" % _MAXLINE) 326*cda5da8dSAndroid Build Coastguard Worker return line 327*cda5da8dSAndroid Build Coastguard Worker 328*cda5da8dSAndroid Build Coastguard Worker 329*cda5da8dSAndroid Build Coastguard Worker def send(self, data): 330*cda5da8dSAndroid Build Coastguard Worker """Send data to remote.""" 331*cda5da8dSAndroid Build Coastguard Worker sys.audit("imaplib.send", self, data) 332*cda5da8dSAndroid Build Coastguard Worker self.sock.sendall(data) 333*cda5da8dSAndroid Build Coastguard Worker 334*cda5da8dSAndroid Build Coastguard Worker 335*cda5da8dSAndroid Build Coastguard Worker def shutdown(self): 336*cda5da8dSAndroid Build Coastguard Worker """Close I/O established in "open".""" 337*cda5da8dSAndroid Build Coastguard Worker self.file.close() 338*cda5da8dSAndroid Build Coastguard Worker try: 339*cda5da8dSAndroid Build Coastguard Worker self.sock.shutdown(socket.SHUT_RDWR) 340*cda5da8dSAndroid Build Coastguard Worker except OSError as exc: 341*cda5da8dSAndroid Build Coastguard Worker # The server might already have closed the connection. 342*cda5da8dSAndroid Build Coastguard Worker # On Windows, this may result in WSAEINVAL (error 10022): 343*cda5da8dSAndroid Build Coastguard Worker # An invalid operation was attempted. 344*cda5da8dSAndroid Build Coastguard Worker if (exc.errno != errno.ENOTCONN 345*cda5da8dSAndroid Build Coastguard Worker and getattr(exc, 'winerror', 0) != 10022): 346*cda5da8dSAndroid Build Coastguard Worker raise 347*cda5da8dSAndroid Build Coastguard Worker finally: 348*cda5da8dSAndroid Build Coastguard Worker self.sock.close() 349*cda5da8dSAndroid Build Coastguard Worker 350*cda5da8dSAndroid Build Coastguard Worker 351*cda5da8dSAndroid Build Coastguard Worker def socket(self): 352*cda5da8dSAndroid Build Coastguard Worker """Return socket instance used to connect to IMAP4 server. 353*cda5da8dSAndroid Build Coastguard Worker 354*cda5da8dSAndroid Build Coastguard Worker socket = <instance>.socket() 355*cda5da8dSAndroid Build Coastguard Worker """ 356*cda5da8dSAndroid Build Coastguard Worker return self.sock 357*cda5da8dSAndroid Build Coastguard Worker 358*cda5da8dSAndroid Build Coastguard Worker 359*cda5da8dSAndroid Build Coastguard Worker 360*cda5da8dSAndroid Build Coastguard Worker # Utility methods 361*cda5da8dSAndroid Build Coastguard Worker 362*cda5da8dSAndroid Build Coastguard Worker 363*cda5da8dSAndroid Build Coastguard Worker def recent(self): 364*cda5da8dSAndroid Build Coastguard Worker """Return most recent 'RECENT' responses if any exist, 365*cda5da8dSAndroid Build Coastguard Worker else prompt server for an update using the 'NOOP' command. 366*cda5da8dSAndroid Build Coastguard Worker 367*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.recent() 368*cda5da8dSAndroid Build Coastguard Worker 369*cda5da8dSAndroid Build Coastguard Worker 'data' is None if no new messages, 370*cda5da8dSAndroid Build Coastguard Worker else list of RECENT responses, most recent last. 371*cda5da8dSAndroid Build Coastguard Worker """ 372*cda5da8dSAndroid Build Coastguard Worker name = 'RECENT' 373*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._untagged_response('OK', [None], name) 374*cda5da8dSAndroid Build Coastguard Worker if dat[-1]: 375*cda5da8dSAndroid Build Coastguard Worker return typ, dat 376*cda5da8dSAndroid Build Coastguard Worker typ, dat = self.noop() # Prod server for response 377*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 378*cda5da8dSAndroid Build Coastguard Worker 379*cda5da8dSAndroid Build Coastguard Worker 380*cda5da8dSAndroid Build Coastguard Worker def response(self, code): 381*cda5da8dSAndroid Build Coastguard Worker """Return data for response 'code' if received, or None. 382*cda5da8dSAndroid Build Coastguard Worker 383*cda5da8dSAndroid Build Coastguard Worker Old value for response 'code' is cleared. 384*cda5da8dSAndroid Build Coastguard Worker 385*cda5da8dSAndroid Build Coastguard Worker (code, [data]) = <instance>.response(code) 386*cda5da8dSAndroid Build Coastguard Worker """ 387*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(code, [None], code.upper()) 388*cda5da8dSAndroid Build Coastguard Worker 389*cda5da8dSAndroid Build Coastguard Worker 390*cda5da8dSAndroid Build Coastguard Worker 391*cda5da8dSAndroid Build Coastguard Worker # IMAP4 commands 392*cda5da8dSAndroid Build Coastguard Worker 393*cda5da8dSAndroid Build Coastguard Worker 394*cda5da8dSAndroid Build Coastguard Worker def append(self, mailbox, flags, date_time, message): 395*cda5da8dSAndroid Build Coastguard Worker """Append message to named mailbox. 396*cda5da8dSAndroid Build Coastguard Worker 397*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.append(mailbox, flags, date_time, message) 398*cda5da8dSAndroid Build Coastguard Worker 399*cda5da8dSAndroid Build Coastguard Worker All args except `message' can be None. 400*cda5da8dSAndroid Build Coastguard Worker """ 401*cda5da8dSAndroid Build Coastguard Worker name = 'APPEND' 402*cda5da8dSAndroid Build Coastguard Worker if not mailbox: 403*cda5da8dSAndroid Build Coastguard Worker mailbox = 'INBOX' 404*cda5da8dSAndroid Build Coastguard Worker if flags: 405*cda5da8dSAndroid Build Coastguard Worker if (flags[0],flags[-1]) != ('(',')'): 406*cda5da8dSAndroid Build Coastguard Worker flags = '(%s)' % flags 407*cda5da8dSAndroid Build Coastguard Worker else: 408*cda5da8dSAndroid Build Coastguard Worker flags = None 409*cda5da8dSAndroid Build Coastguard Worker if date_time: 410*cda5da8dSAndroid Build Coastguard Worker date_time = Time2Internaldate(date_time) 411*cda5da8dSAndroid Build Coastguard Worker else: 412*cda5da8dSAndroid Build Coastguard Worker date_time = None 413*cda5da8dSAndroid Build Coastguard Worker literal = MapCRLF.sub(CRLF, message) 414*cda5da8dSAndroid Build Coastguard Worker if self.utf8_enabled: 415*cda5da8dSAndroid Build Coastguard Worker literal = b'UTF8 (' + literal + b')' 416*cda5da8dSAndroid Build Coastguard Worker self.literal = literal 417*cda5da8dSAndroid Build Coastguard Worker return self._simple_command(name, mailbox, flags, date_time) 418*cda5da8dSAndroid Build Coastguard Worker 419*cda5da8dSAndroid Build Coastguard Worker 420*cda5da8dSAndroid Build Coastguard Worker def authenticate(self, mechanism, authobject): 421*cda5da8dSAndroid Build Coastguard Worker """Authenticate command - requires response processing. 422*cda5da8dSAndroid Build Coastguard Worker 423*cda5da8dSAndroid Build Coastguard Worker 'mechanism' specifies which authentication mechanism is to 424*cda5da8dSAndroid Build Coastguard Worker be used - it must appear in <instance>.capabilities in the 425*cda5da8dSAndroid Build Coastguard Worker form AUTH=<mechanism>. 426*cda5da8dSAndroid Build Coastguard Worker 427*cda5da8dSAndroid Build Coastguard Worker 'authobject' must be a callable object: 428*cda5da8dSAndroid Build Coastguard Worker 429*cda5da8dSAndroid Build Coastguard Worker data = authobject(response) 430*cda5da8dSAndroid Build Coastguard Worker 431*cda5da8dSAndroid Build Coastguard Worker It will be called to process server continuation responses; the 432*cda5da8dSAndroid Build Coastguard Worker response argument it is passed will be a bytes. It should return bytes 433*cda5da8dSAndroid Build Coastguard Worker data that will be base64 encoded and sent to the server. It should 434*cda5da8dSAndroid Build Coastguard Worker return None if the client abort response '*' should be sent instead. 435*cda5da8dSAndroid Build Coastguard Worker """ 436*cda5da8dSAndroid Build Coastguard Worker mech = mechanism.upper() 437*cda5da8dSAndroid Build Coastguard Worker # XXX: shouldn't this code be removed, not commented out? 438*cda5da8dSAndroid Build Coastguard Worker #cap = 'AUTH=%s' % mech 439*cda5da8dSAndroid Build Coastguard Worker #if not cap in self.capabilities: # Let the server decide! 440*cda5da8dSAndroid Build Coastguard Worker # raise self.error("Server doesn't allow %s authentication." % mech) 441*cda5da8dSAndroid Build Coastguard Worker self.literal = _Authenticator(authobject).process 442*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('AUTHENTICATE', mech) 443*cda5da8dSAndroid Build Coastguard Worker if typ != 'OK': 444*cda5da8dSAndroid Build Coastguard Worker raise self.error(dat[-1].decode('utf-8', 'replace')) 445*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' 446*cda5da8dSAndroid Build Coastguard Worker return typ, dat 447*cda5da8dSAndroid Build Coastguard Worker 448*cda5da8dSAndroid Build Coastguard Worker 449*cda5da8dSAndroid Build Coastguard Worker def capability(self): 450*cda5da8dSAndroid Build Coastguard Worker """(typ, [data]) = <instance>.capability() 451*cda5da8dSAndroid Build Coastguard Worker Fetch capabilities list from server.""" 452*cda5da8dSAndroid Build Coastguard Worker 453*cda5da8dSAndroid Build Coastguard Worker name = 'CAPABILITY' 454*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name) 455*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 456*cda5da8dSAndroid Build Coastguard Worker 457*cda5da8dSAndroid Build Coastguard Worker 458*cda5da8dSAndroid Build Coastguard Worker def check(self): 459*cda5da8dSAndroid Build Coastguard Worker """Checkpoint mailbox on server. 460*cda5da8dSAndroid Build Coastguard Worker 461*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.check() 462*cda5da8dSAndroid Build Coastguard Worker """ 463*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('CHECK') 464*cda5da8dSAndroid Build Coastguard Worker 465*cda5da8dSAndroid Build Coastguard Worker 466*cda5da8dSAndroid Build Coastguard Worker def close(self): 467*cda5da8dSAndroid Build Coastguard Worker """Close currently selected mailbox. 468*cda5da8dSAndroid Build Coastguard Worker 469*cda5da8dSAndroid Build Coastguard Worker Deleted messages are removed from writable mailbox. 470*cda5da8dSAndroid Build Coastguard Worker This is the recommended command before 'LOGOUT'. 471*cda5da8dSAndroid Build Coastguard Worker 472*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.close() 473*cda5da8dSAndroid Build Coastguard Worker """ 474*cda5da8dSAndroid Build Coastguard Worker try: 475*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('CLOSE') 476*cda5da8dSAndroid Build Coastguard Worker finally: 477*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' 478*cda5da8dSAndroid Build Coastguard Worker return typ, dat 479*cda5da8dSAndroid Build Coastguard Worker 480*cda5da8dSAndroid Build Coastguard Worker 481*cda5da8dSAndroid Build Coastguard Worker def copy(self, message_set, new_mailbox): 482*cda5da8dSAndroid Build Coastguard Worker """Copy 'message_set' messages onto end of 'new_mailbox'. 483*cda5da8dSAndroid Build Coastguard Worker 484*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.copy(message_set, new_mailbox) 485*cda5da8dSAndroid Build Coastguard Worker """ 486*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('COPY', message_set, new_mailbox) 487*cda5da8dSAndroid Build Coastguard Worker 488*cda5da8dSAndroid Build Coastguard Worker 489*cda5da8dSAndroid Build Coastguard Worker def create(self, mailbox): 490*cda5da8dSAndroid Build Coastguard Worker """Create new mailbox. 491*cda5da8dSAndroid Build Coastguard Worker 492*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.create(mailbox) 493*cda5da8dSAndroid Build Coastguard Worker """ 494*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('CREATE', mailbox) 495*cda5da8dSAndroid Build Coastguard Worker 496*cda5da8dSAndroid Build Coastguard Worker 497*cda5da8dSAndroid Build Coastguard Worker def delete(self, mailbox): 498*cda5da8dSAndroid Build Coastguard Worker """Delete old mailbox. 499*cda5da8dSAndroid Build Coastguard Worker 500*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.delete(mailbox) 501*cda5da8dSAndroid Build Coastguard Worker """ 502*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('DELETE', mailbox) 503*cda5da8dSAndroid Build Coastguard Worker 504*cda5da8dSAndroid Build Coastguard Worker def deleteacl(self, mailbox, who): 505*cda5da8dSAndroid Build Coastguard Worker """Delete the ACLs (remove any rights) set for who on mailbox. 506*cda5da8dSAndroid Build Coastguard Worker 507*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.deleteacl(mailbox, who) 508*cda5da8dSAndroid Build Coastguard Worker """ 509*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('DELETEACL', mailbox, who) 510*cda5da8dSAndroid Build Coastguard Worker 511*cda5da8dSAndroid Build Coastguard Worker def enable(self, capability): 512*cda5da8dSAndroid Build Coastguard Worker """Send an RFC5161 enable string to the server. 513*cda5da8dSAndroid Build Coastguard Worker 514*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.enable(capability) 515*cda5da8dSAndroid Build Coastguard Worker """ 516*cda5da8dSAndroid Build Coastguard Worker if 'ENABLE' not in self.capabilities: 517*cda5da8dSAndroid Build Coastguard Worker raise IMAP4.error("Server does not support ENABLE") 518*cda5da8dSAndroid Build Coastguard Worker typ, data = self._simple_command('ENABLE', capability) 519*cda5da8dSAndroid Build Coastguard Worker if typ == 'OK' and 'UTF8=ACCEPT' in capability.upper(): 520*cda5da8dSAndroid Build Coastguard Worker self._mode_utf8() 521*cda5da8dSAndroid Build Coastguard Worker return typ, data 522*cda5da8dSAndroid Build Coastguard Worker 523*cda5da8dSAndroid Build Coastguard Worker def expunge(self): 524*cda5da8dSAndroid Build Coastguard Worker """Permanently remove deleted items from selected mailbox. 525*cda5da8dSAndroid Build Coastguard Worker 526*cda5da8dSAndroid Build Coastguard Worker Generates 'EXPUNGE' response for each deleted message. 527*cda5da8dSAndroid Build Coastguard Worker 528*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.expunge() 529*cda5da8dSAndroid Build Coastguard Worker 530*cda5da8dSAndroid Build Coastguard Worker 'data' is list of 'EXPUNGE'd message numbers in order received. 531*cda5da8dSAndroid Build Coastguard Worker """ 532*cda5da8dSAndroid Build Coastguard Worker name = 'EXPUNGE' 533*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name) 534*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 535*cda5da8dSAndroid Build Coastguard Worker 536*cda5da8dSAndroid Build Coastguard Worker 537*cda5da8dSAndroid Build Coastguard Worker def fetch(self, message_set, message_parts): 538*cda5da8dSAndroid Build Coastguard Worker """Fetch (parts of) messages. 539*cda5da8dSAndroid Build Coastguard Worker 540*cda5da8dSAndroid Build Coastguard Worker (typ, [data, ...]) = <instance>.fetch(message_set, message_parts) 541*cda5da8dSAndroid Build Coastguard Worker 542*cda5da8dSAndroid Build Coastguard Worker 'message_parts' should be a string of selected parts 543*cda5da8dSAndroid Build Coastguard Worker enclosed in parentheses, eg: "(UID BODY[TEXT])". 544*cda5da8dSAndroid Build Coastguard Worker 545*cda5da8dSAndroid Build Coastguard Worker 'data' are tuples of message part envelope and data. 546*cda5da8dSAndroid Build Coastguard Worker """ 547*cda5da8dSAndroid Build Coastguard Worker name = 'FETCH' 548*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, message_set, message_parts) 549*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 550*cda5da8dSAndroid Build Coastguard Worker 551*cda5da8dSAndroid Build Coastguard Worker 552*cda5da8dSAndroid Build Coastguard Worker def getacl(self, mailbox): 553*cda5da8dSAndroid Build Coastguard Worker """Get the ACLs for a mailbox. 554*cda5da8dSAndroid Build Coastguard Worker 555*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.getacl(mailbox) 556*cda5da8dSAndroid Build Coastguard Worker """ 557*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('GETACL', mailbox) 558*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'ACL') 559*cda5da8dSAndroid Build Coastguard Worker 560*cda5da8dSAndroid Build Coastguard Worker 561*cda5da8dSAndroid Build Coastguard Worker def getannotation(self, mailbox, entry, attribute): 562*cda5da8dSAndroid Build Coastguard Worker """(typ, [data]) = <instance>.getannotation(mailbox, entry, attribute) 563*cda5da8dSAndroid Build Coastguard Worker Retrieve ANNOTATIONs.""" 564*cda5da8dSAndroid Build Coastguard Worker 565*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('GETANNOTATION', mailbox, entry, attribute) 566*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'ANNOTATION') 567*cda5da8dSAndroid Build Coastguard Worker 568*cda5da8dSAndroid Build Coastguard Worker 569*cda5da8dSAndroid Build Coastguard Worker def getquota(self, root): 570*cda5da8dSAndroid Build Coastguard Worker """Get the quota root's resource usage and limits. 571*cda5da8dSAndroid Build Coastguard Worker 572*cda5da8dSAndroid Build Coastguard Worker Part of the IMAP4 QUOTA extension defined in rfc2087. 573*cda5da8dSAndroid Build Coastguard Worker 574*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.getquota(root) 575*cda5da8dSAndroid Build Coastguard Worker """ 576*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('GETQUOTA', root) 577*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'QUOTA') 578*cda5da8dSAndroid Build Coastguard Worker 579*cda5da8dSAndroid Build Coastguard Worker 580*cda5da8dSAndroid Build Coastguard Worker def getquotaroot(self, mailbox): 581*cda5da8dSAndroid Build Coastguard Worker """Get the list of quota roots for the named mailbox. 582*cda5da8dSAndroid Build Coastguard Worker 583*cda5da8dSAndroid Build Coastguard Worker (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox) 584*cda5da8dSAndroid Build Coastguard Worker """ 585*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('GETQUOTAROOT', mailbox) 586*cda5da8dSAndroid Build Coastguard Worker typ, quota = self._untagged_response(typ, dat, 'QUOTA') 587*cda5da8dSAndroid Build Coastguard Worker typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT') 588*cda5da8dSAndroid Build Coastguard Worker return typ, [quotaroot, quota] 589*cda5da8dSAndroid Build Coastguard Worker 590*cda5da8dSAndroid Build Coastguard Worker 591*cda5da8dSAndroid Build Coastguard Worker def list(self, directory='""', pattern='*'): 592*cda5da8dSAndroid Build Coastguard Worker """List mailbox names in directory matching pattern. 593*cda5da8dSAndroid Build Coastguard Worker 594*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.list(directory='""', pattern='*') 595*cda5da8dSAndroid Build Coastguard Worker 596*cda5da8dSAndroid Build Coastguard Worker 'data' is list of LIST responses. 597*cda5da8dSAndroid Build Coastguard Worker """ 598*cda5da8dSAndroid Build Coastguard Worker name = 'LIST' 599*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, directory, pattern) 600*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 601*cda5da8dSAndroid Build Coastguard Worker 602*cda5da8dSAndroid Build Coastguard Worker 603*cda5da8dSAndroid Build Coastguard Worker def login(self, user, password): 604*cda5da8dSAndroid Build Coastguard Worker """Identify client using plaintext password. 605*cda5da8dSAndroid Build Coastguard Worker 606*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.login(user, password) 607*cda5da8dSAndroid Build Coastguard Worker 608*cda5da8dSAndroid Build Coastguard Worker NB: 'password' will be quoted. 609*cda5da8dSAndroid Build Coastguard Worker """ 610*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('LOGIN', user, self._quote(password)) 611*cda5da8dSAndroid Build Coastguard Worker if typ != 'OK': 612*cda5da8dSAndroid Build Coastguard Worker raise self.error(dat[-1]) 613*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' 614*cda5da8dSAndroid Build Coastguard Worker return typ, dat 615*cda5da8dSAndroid Build Coastguard Worker 616*cda5da8dSAndroid Build Coastguard Worker 617*cda5da8dSAndroid Build Coastguard Worker def login_cram_md5(self, user, password): 618*cda5da8dSAndroid Build Coastguard Worker """ Force use of CRAM-MD5 authentication. 619*cda5da8dSAndroid Build Coastguard Worker 620*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.login_cram_md5(user, password) 621*cda5da8dSAndroid Build Coastguard Worker """ 622*cda5da8dSAndroid Build Coastguard Worker self.user, self.password = user, password 623*cda5da8dSAndroid Build Coastguard Worker return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH) 624*cda5da8dSAndroid Build Coastguard Worker 625*cda5da8dSAndroid Build Coastguard Worker 626*cda5da8dSAndroid Build Coastguard Worker def _CRAM_MD5_AUTH(self, challenge): 627*cda5da8dSAndroid Build Coastguard Worker """ Authobject to use with CRAM-MD5 authentication. """ 628*cda5da8dSAndroid Build Coastguard Worker import hmac 629*cda5da8dSAndroid Build Coastguard Worker pwd = (self.password.encode('utf-8') if isinstance(self.password, str) 630*cda5da8dSAndroid Build Coastguard Worker else self.password) 631*cda5da8dSAndroid Build Coastguard Worker return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest() 632*cda5da8dSAndroid Build Coastguard Worker 633*cda5da8dSAndroid Build Coastguard Worker 634*cda5da8dSAndroid Build Coastguard Worker def logout(self): 635*cda5da8dSAndroid Build Coastguard Worker """Shutdown connection to server. 636*cda5da8dSAndroid Build Coastguard Worker 637*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.logout() 638*cda5da8dSAndroid Build Coastguard Worker 639*cda5da8dSAndroid Build Coastguard Worker Returns server 'BYE' response. 640*cda5da8dSAndroid Build Coastguard Worker """ 641*cda5da8dSAndroid Build Coastguard Worker self.state = 'LOGOUT' 642*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('LOGOUT') 643*cda5da8dSAndroid Build Coastguard Worker self.shutdown() 644*cda5da8dSAndroid Build Coastguard Worker return typ, dat 645*cda5da8dSAndroid Build Coastguard Worker 646*cda5da8dSAndroid Build Coastguard Worker 647*cda5da8dSAndroid Build Coastguard Worker def lsub(self, directory='""', pattern='*'): 648*cda5da8dSAndroid Build Coastguard Worker """List 'subscribed' mailbox names in directory matching pattern. 649*cda5da8dSAndroid Build Coastguard Worker 650*cda5da8dSAndroid Build Coastguard Worker (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*') 651*cda5da8dSAndroid Build Coastguard Worker 652*cda5da8dSAndroid Build Coastguard Worker 'data' are tuples of message part envelope and data. 653*cda5da8dSAndroid Build Coastguard Worker """ 654*cda5da8dSAndroid Build Coastguard Worker name = 'LSUB' 655*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, directory, pattern) 656*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 657*cda5da8dSAndroid Build Coastguard Worker 658*cda5da8dSAndroid Build Coastguard Worker def myrights(self, mailbox): 659*cda5da8dSAndroid Build Coastguard Worker """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox). 660*cda5da8dSAndroid Build Coastguard Worker 661*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.myrights(mailbox) 662*cda5da8dSAndroid Build Coastguard Worker """ 663*cda5da8dSAndroid Build Coastguard Worker typ,dat = self._simple_command('MYRIGHTS', mailbox) 664*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'MYRIGHTS') 665*cda5da8dSAndroid Build Coastguard Worker 666*cda5da8dSAndroid Build Coastguard Worker def namespace(self): 667*cda5da8dSAndroid Build Coastguard Worker """ Returns IMAP namespaces ala rfc2342 668*cda5da8dSAndroid Build Coastguard Worker 669*cda5da8dSAndroid Build Coastguard Worker (typ, [data, ...]) = <instance>.namespace() 670*cda5da8dSAndroid Build Coastguard Worker """ 671*cda5da8dSAndroid Build Coastguard Worker name = 'NAMESPACE' 672*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name) 673*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 674*cda5da8dSAndroid Build Coastguard Worker 675*cda5da8dSAndroid Build Coastguard Worker 676*cda5da8dSAndroid Build Coastguard Worker def noop(self): 677*cda5da8dSAndroid Build Coastguard Worker """Send NOOP command. 678*cda5da8dSAndroid Build Coastguard Worker 679*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.noop() 680*cda5da8dSAndroid Build Coastguard Worker """ 681*cda5da8dSAndroid Build Coastguard Worker if __debug__: 682*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 3: 683*cda5da8dSAndroid Build Coastguard Worker self._dump_ur(self.untagged_responses) 684*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('NOOP') 685*cda5da8dSAndroid Build Coastguard Worker 686*cda5da8dSAndroid Build Coastguard Worker 687*cda5da8dSAndroid Build Coastguard Worker def partial(self, message_num, message_part, start, length): 688*cda5da8dSAndroid Build Coastguard Worker """Fetch truncated part of a message. 689*cda5da8dSAndroid Build Coastguard Worker 690*cda5da8dSAndroid Build Coastguard Worker (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length) 691*cda5da8dSAndroid Build Coastguard Worker 692*cda5da8dSAndroid Build Coastguard Worker 'data' is tuple of message part envelope and data. 693*cda5da8dSAndroid Build Coastguard Worker """ 694*cda5da8dSAndroid Build Coastguard Worker name = 'PARTIAL' 695*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, message_num, message_part, start, length) 696*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'FETCH') 697*cda5da8dSAndroid Build Coastguard Worker 698*cda5da8dSAndroid Build Coastguard Worker 699*cda5da8dSAndroid Build Coastguard Worker def proxyauth(self, user): 700*cda5da8dSAndroid Build Coastguard Worker """Assume authentication as "user". 701*cda5da8dSAndroid Build Coastguard Worker 702*cda5da8dSAndroid Build Coastguard Worker Allows an authorised administrator to proxy into any user's 703*cda5da8dSAndroid Build Coastguard Worker mailbox. 704*cda5da8dSAndroid Build Coastguard Worker 705*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.proxyauth(user) 706*cda5da8dSAndroid Build Coastguard Worker """ 707*cda5da8dSAndroid Build Coastguard Worker 708*cda5da8dSAndroid Build Coastguard Worker name = 'PROXYAUTH' 709*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('PROXYAUTH', user) 710*cda5da8dSAndroid Build Coastguard Worker 711*cda5da8dSAndroid Build Coastguard Worker 712*cda5da8dSAndroid Build Coastguard Worker def rename(self, oldmailbox, newmailbox): 713*cda5da8dSAndroid Build Coastguard Worker """Rename old mailbox name to new. 714*cda5da8dSAndroid Build Coastguard Worker 715*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.rename(oldmailbox, newmailbox) 716*cda5da8dSAndroid Build Coastguard Worker """ 717*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('RENAME', oldmailbox, newmailbox) 718*cda5da8dSAndroid Build Coastguard Worker 719*cda5da8dSAndroid Build Coastguard Worker 720*cda5da8dSAndroid Build Coastguard Worker def search(self, charset, *criteria): 721*cda5da8dSAndroid Build Coastguard Worker """Search mailbox for matching messages. 722*cda5da8dSAndroid Build Coastguard Worker 723*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.search(charset, criterion, ...) 724*cda5da8dSAndroid Build Coastguard Worker 725*cda5da8dSAndroid Build Coastguard Worker 'data' is space separated list of matching message numbers. 726*cda5da8dSAndroid Build Coastguard Worker If UTF8 is enabled, charset MUST be None. 727*cda5da8dSAndroid Build Coastguard Worker """ 728*cda5da8dSAndroid Build Coastguard Worker name = 'SEARCH' 729*cda5da8dSAndroid Build Coastguard Worker if charset: 730*cda5da8dSAndroid Build Coastguard Worker if self.utf8_enabled: 731*cda5da8dSAndroid Build Coastguard Worker raise IMAP4.error("Non-None charset not valid in UTF8 mode") 732*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria) 733*cda5da8dSAndroid Build Coastguard Worker else: 734*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, *criteria) 735*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 736*cda5da8dSAndroid Build Coastguard Worker 737*cda5da8dSAndroid Build Coastguard Worker 738*cda5da8dSAndroid Build Coastguard Worker def select(self, mailbox='INBOX', readonly=False): 739*cda5da8dSAndroid Build Coastguard Worker """Select a mailbox. 740*cda5da8dSAndroid Build Coastguard Worker 741*cda5da8dSAndroid Build Coastguard Worker Flush all untagged responses. 742*cda5da8dSAndroid Build Coastguard Worker 743*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=False) 744*cda5da8dSAndroid Build Coastguard Worker 745*cda5da8dSAndroid Build Coastguard Worker 'data' is count of messages in mailbox ('EXISTS' response). 746*cda5da8dSAndroid Build Coastguard Worker 747*cda5da8dSAndroid Build Coastguard Worker Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so 748*cda5da8dSAndroid Build Coastguard Worker other responses should be obtained via <instance>.response('FLAGS') etc. 749*cda5da8dSAndroid Build Coastguard Worker """ 750*cda5da8dSAndroid Build Coastguard Worker self.untagged_responses = {} # Flush old responses. 751*cda5da8dSAndroid Build Coastguard Worker self.is_readonly = readonly 752*cda5da8dSAndroid Build Coastguard Worker if readonly: 753*cda5da8dSAndroid Build Coastguard Worker name = 'EXAMINE' 754*cda5da8dSAndroid Build Coastguard Worker else: 755*cda5da8dSAndroid Build Coastguard Worker name = 'SELECT' 756*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, mailbox) 757*cda5da8dSAndroid Build Coastguard Worker if typ != 'OK': 758*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' # Might have been 'SELECTED' 759*cda5da8dSAndroid Build Coastguard Worker return typ, dat 760*cda5da8dSAndroid Build Coastguard Worker self.state = 'SELECTED' 761*cda5da8dSAndroid Build Coastguard Worker if 'READ-ONLY' in self.untagged_responses \ 762*cda5da8dSAndroid Build Coastguard Worker and not readonly: 763*cda5da8dSAndroid Build Coastguard Worker if __debug__: 764*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 1: 765*cda5da8dSAndroid Build Coastguard Worker self._dump_ur(self.untagged_responses) 766*cda5da8dSAndroid Build Coastguard Worker raise self.readonly('%s is not writable' % mailbox) 767*cda5da8dSAndroid Build Coastguard Worker return typ, self.untagged_responses.get('EXISTS', [None]) 768*cda5da8dSAndroid Build Coastguard Worker 769*cda5da8dSAndroid Build Coastguard Worker 770*cda5da8dSAndroid Build Coastguard Worker def setacl(self, mailbox, who, what): 771*cda5da8dSAndroid Build Coastguard Worker """Set a mailbox acl. 772*cda5da8dSAndroid Build Coastguard Worker 773*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.setacl(mailbox, who, what) 774*cda5da8dSAndroid Build Coastguard Worker """ 775*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('SETACL', mailbox, who, what) 776*cda5da8dSAndroid Build Coastguard Worker 777*cda5da8dSAndroid Build Coastguard Worker 778*cda5da8dSAndroid Build Coastguard Worker def setannotation(self, *args): 779*cda5da8dSAndroid Build Coastguard Worker """(typ, [data]) = <instance>.setannotation(mailbox[, entry, attribute]+) 780*cda5da8dSAndroid Build Coastguard Worker Set ANNOTATIONs.""" 781*cda5da8dSAndroid Build Coastguard Worker 782*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('SETANNOTATION', *args) 783*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'ANNOTATION') 784*cda5da8dSAndroid Build Coastguard Worker 785*cda5da8dSAndroid Build Coastguard Worker 786*cda5da8dSAndroid Build Coastguard Worker def setquota(self, root, limits): 787*cda5da8dSAndroid Build Coastguard Worker """Set the quota root's resource limits. 788*cda5da8dSAndroid Build Coastguard Worker 789*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.setquota(root, limits) 790*cda5da8dSAndroid Build Coastguard Worker """ 791*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('SETQUOTA', root, limits) 792*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'QUOTA') 793*cda5da8dSAndroid Build Coastguard Worker 794*cda5da8dSAndroid Build Coastguard Worker 795*cda5da8dSAndroid Build Coastguard Worker def sort(self, sort_criteria, charset, *search_criteria): 796*cda5da8dSAndroid Build Coastguard Worker """IMAP4rev1 extension SORT command. 797*cda5da8dSAndroid Build Coastguard Worker 798*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...) 799*cda5da8dSAndroid Build Coastguard Worker """ 800*cda5da8dSAndroid Build Coastguard Worker name = 'SORT' 801*cda5da8dSAndroid Build Coastguard Worker #if not name in self.capabilities: # Let the server decide! 802*cda5da8dSAndroid Build Coastguard Worker # raise self.error('unimplemented extension command: %s' % name) 803*cda5da8dSAndroid Build Coastguard Worker if (sort_criteria[0],sort_criteria[-1]) != ('(',')'): 804*cda5da8dSAndroid Build Coastguard Worker sort_criteria = '(%s)' % sort_criteria 805*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria) 806*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 807*cda5da8dSAndroid Build Coastguard Worker 808*cda5da8dSAndroid Build Coastguard Worker 809*cda5da8dSAndroid Build Coastguard Worker def starttls(self, ssl_context=None): 810*cda5da8dSAndroid Build Coastguard Worker name = 'STARTTLS' 811*cda5da8dSAndroid Build Coastguard Worker if not HAVE_SSL: 812*cda5da8dSAndroid Build Coastguard Worker raise self.error('SSL support missing') 813*cda5da8dSAndroid Build Coastguard Worker if self._tls_established: 814*cda5da8dSAndroid Build Coastguard Worker raise self.abort('TLS session already established') 815*cda5da8dSAndroid Build Coastguard Worker if name not in self.capabilities: 816*cda5da8dSAndroid Build Coastguard Worker raise self.abort('TLS not supported by server') 817*cda5da8dSAndroid Build Coastguard Worker # Generate a default SSL context if none was passed. 818*cda5da8dSAndroid Build Coastguard Worker if ssl_context is None: 819*cda5da8dSAndroid Build Coastguard Worker ssl_context = ssl._create_stdlib_context() 820*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name) 821*cda5da8dSAndroid Build Coastguard Worker if typ == 'OK': 822*cda5da8dSAndroid Build Coastguard Worker self.sock = ssl_context.wrap_socket(self.sock, 823*cda5da8dSAndroid Build Coastguard Worker server_hostname=self.host) 824*cda5da8dSAndroid Build Coastguard Worker self.file = self.sock.makefile('rb') 825*cda5da8dSAndroid Build Coastguard Worker self._tls_established = True 826*cda5da8dSAndroid Build Coastguard Worker self._get_capabilities() 827*cda5da8dSAndroid Build Coastguard Worker else: 828*cda5da8dSAndroid Build Coastguard Worker raise self.error("Couldn't establish TLS session") 829*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 830*cda5da8dSAndroid Build Coastguard Worker 831*cda5da8dSAndroid Build Coastguard Worker 832*cda5da8dSAndroid Build Coastguard Worker def status(self, mailbox, names): 833*cda5da8dSAndroid Build Coastguard Worker """Request named status conditions for mailbox. 834*cda5da8dSAndroid Build Coastguard Worker 835*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.status(mailbox, names) 836*cda5da8dSAndroid Build Coastguard Worker """ 837*cda5da8dSAndroid Build Coastguard Worker name = 'STATUS' 838*cda5da8dSAndroid Build Coastguard Worker #if self.PROTOCOL_VERSION == 'IMAP4': # Let the server decide! 839*cda5da8dSAndroid Build Coastguard Worker # raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name) 840*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, mailbox, names) 841*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 842*cda5da8dSAndroid Build Coastguard Worker 843*cda5da8dSAndroid Build Coastguard Worker 844*cda5da8dSAndroid Build Coastguard Worker def store(self, message_set, command, flags): 845*cda5da8dSAndroid Build Coastguard Worker """Alters flag dispositions for messages in mailbox. 846*cda5da8dSAndroid Build Coastguard Worker 847*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.store(message_set, command, flags) 848*cda5da8dSAndroid Build Coastguard Worker """ 849*cda5da8dSAndroid Build Coastguard Worker if (flags[0],flags[-1]) != ('(',')'): 850*cda5da8dSAndroid Build Coastguard Worker flags = '(%s)' % flags # Avoid quoting the flags 851*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command('STORE', message_set, command, flags) 852*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, 'FETCH') 853*cda5da8dSAndroid Build Coastguard Worker 854*cda5da8dSAndroid Build Coastguard Worker 855*cda5da8dSAndroid Build Coastguard Worker def subscribe(self, mailbox): 856*cda5da8dSAndroid Build Coastguard Worker """Subscribe to new mailbox. 857*cda5da8dSAndroid Build Coastguard Worker 858*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.subscribe(mailbox) 859*cda5da8dSAndroid Build Coastguard Worker """ 860*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('SUBSCRIBE', mailbox) 861*cda5da8dSAndroid Build Coastguard Worker 862*cda5da8dSAndroid Build Coastguard Worker 863*cda5da8dSAndroid Build Coastguard Worker def thread(self, threading_algorithm, charset, *search_criteria): 864*cda5da8dSAndroid Build Coastguard Worker """IMAPrev1 extension THREAD command. 865*cda5da8dSAndroid Build Coastguard Worker 866*cda5da8dSAndroid Build Coastguard Worker (type, [data]) = <instance>.thread(threading_algorithm, charset, search_criteria, ...) 867*cda5da8dSAndroid Build Coastguard Worker """ 868*cda5da8dSAndroid Build Coastguard Worker name = 'THREAD' 869*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria) 870*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 871*cda5da8dSAndroid Build Coastguard Worker 872*cda5da8dSAndroid Build Coastguard Worker 873*cda5da8dSAndroid Build Coastguard Worker def uid(self, command, *args): 874*cda5da8dSAndroid Build Coastguard Worker """Execute "command arg ..." with messages identified by UID, 875*cda5da8dSAndroid Build Coastguard Worker rather than message number. 876*cda5da8dSAndroid Build Coastguard Worker 877*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.uid(command, arg1, arg2, ...) 878*cda5da8dSAndroid Build Coastguard Worker 879*cda5da8dSAndroid Build Coastguard Worker Returns response appropriate to 'command'. 880*cda5da8dSAndroid Build Coastguard Worker """ 881*cda5da8dSAndroid Build Coastguard Worker command = command.upper() 882*cda5da8dSAndroid Build Coastguard Worker if not command in Commands: 883*cda5da8dSAndroid Build Coastguard Worker raise self.error("Unknown IMAP4 UID command: %s" % command) 884*cda5da8dSAndroid Build Coastguard Worker if self.state not in Commands[command]: 885*cda5da8dSAndroid Build Coastguard Worker raise self.error("command %s illegal in state %s, " 886*cda5da8dSAndroid Build Coastguard Worker "only allowed in states %s" % 887*cda5da8dSAndroid Build Coastguard Worker (command, self.state, 888*cda5da8dSAndroid Build Coastguard Worker ', '.join(Commands[command]))) 889*cda5da8dSAndroid Build Coastguard Worker name = 'UID' 890*cda5da8dSAndroid Build Coastguard Worker typ, dat = self._simple_command(name, command, *args) 891*cda5da8dSAndroid Build Coastguard Worker if command in ('SEARCH', 'SORT', 'THREAD'): 892*cda5da8dSAndroid Build Coastguard Worker name = command 893*cda5da8dSAndroid Build Coastguard Worker else: 894*cda5da8dSAndroid Build Coastguard Worker name = 'FETCH' 895*cda5da8dSAndroid Build Coastguard Worker return self._untagged_response(typ, dat, name) 896*cda5da8dSAndroid Build Coastguard Worker 897*cda5da8dSAndroid Build Coastguard Worker 898*cda5da8dSAndroid Build Coastguard Worker def unsubscribe(self, mailbox): 899*cda5da8dSAndroid Build Coastguard Worker """Unsubscribe from old mailbox. 900*cda5da8dSAndroid Build Coastguard Worker 901*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.unsubscribe(mailbox) 902*cda5da8dSAndroid Build Coastguard Worker """ 903*cda5da8dSAndroid Build Coastguard Worker return self._simple_command('UNSUBSCRIBE', mailbox) 904*cda5da8dSAndroid Build Coastguard Worker 905*cda5da8dSAndroid Build Coastguard Worker 906*cda5da8dSAndroid Build Coastguard Worker def unselect(self): 907*cda5da8dSAndroid Build Coastguard Worker """Free server's resources associated with the selected mailbox 908*cda5da8dSAndroid Build Coastguard Worker and returns the server to the authenticated state. 909*cda5da8dSAndroid Build Coastguard Worker This command performs the same actions as CLOSE, except 910*cda5da8dSAndroid Build Coastguard Worker that no messages are permanently removed from the currently 911*cda5da8dSAndroid Build Coastguard Worker selected mailbox. 912*cda5da8dSAndroid Build Coastguard Worker 913*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.unselect() 914*cda5da8dSAndroid Build Coastguard Worker """ 915*cda5da8dSAndroid Build Coastguard Worker try: 916*cda5da8dSAndroid Build Coastguard Worker typ, data = self._simple_command('UNSELECT') 917*cda5da8dSAndroid Build Coastguard Worker finally: 918*cda5da8dSAndroid Build Coastguard Worker self.state = 'AUTH' 919*cda5da8dSAndroid Build Coastguard Worker return typ, data 920*cda5da8dSAndroid Build Coastguard Worker 921*cda5da8dSAndroid Build Coastguard Worker 922*cda5da8dSAndroid Build Coastguard Worker def xatom(self, name, *args): 923*cda5da8dSAndroid Build Coastguard Worker """Allow simple extension commands 924*cda5da8dSAndroid Build Coastguard Worker notified by server in CAPABILITY response. 925*cda5da8dSAndroid Build Coastguard Worker 926*cda5da8dSAndroid Build Coastguard Worker Assumes command is legal in current state. 927*cda5da8dSAndroid Build Coastguard Worker 928*cda5da8dSAndroid Build Coastguard Worker (typ, [data]) = <instance>.xatom(name, arg, ...) 929*cda5da8dSAndroid Build Coastguard Worker 930*cda5da8dSAndroid Build Coastguard Worker Returns response appropriate to extension command `name'. 931*cda5da8dSAndroid Build Coastguard Worker """ 932*cda5da8dSAndroid Build Coastguard Worker name = name.upper() 933*cda5da8dSAndroid Build Coastguard Worker #if not name in self.capabilities: # Let the server decide! 934*cda5da8dSAndroid Build Coastguard Worker # raise self.error('unknown extension command: %s' % name) 935*cda5da8dSAndroid Build Coastguard Worker if not name in Commands: 936*cda5da8dSAndroid Build Coastguard Worker Commands[name] = (self.state,) 937*cda5da8dSAndroid Build Coastguard Worker return self._simple_command(name, *args) 938*cda5da8dSAndroid Build Coastguard Worker 939*cda5da8dSAndroid Build Coastguard Worker 940*cda5da8dSAndroid Build Coastguard Worker 941*cda5da8dSAndroid Build Coastguard Worker # Private methods 942*cda5da8dSAndroid Build Coastguard Worker 943*cda5da8dSAndroid Build Coastguard Worker 944*cda5da8dSAndroid Build Coastguard Worker def _append_untagged(self, typ, dat): 945*cda5da8dSAndroid Build Coastguard Worker if dat is None: 946*cda5da8dSAndroid Build Coastguard Worker dat = b'' 947*cda5da8dSAndroid Build Coastguard Worker ur = self.untagged_responses 948*cda5da8dSAndroid Build Coastguard Worker if __debug__: 949*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 5: 950*cda5da8dSAndroid Build Coastguard Worker self._mesg('untagged_responses[%s] %s += ["%r"]' % 951*cda5da8dSAndroid Build Coastguard Worker (typ, len(ur.get(typ,'')), dat)) 952*cda5da8dSAndroid Build Coastguard Worker if typ in ur: 953*cda5da8dSAndroid Build Coastguard Worker ur[typ].append(dat) 954*cda5da8dSAndroid Build Coastguard Worker else: 955*cda5da8dSAndroid Build Coastguard Worker ur[typ] = [dat] 956*cda5da8dSAndroid Build Coastguard Worker 957*cda5da8dSAndroid Build Coastguard Worker 958*cda5da8dSAndroid Build Coastguard Worker def _check_bye(self): 959*cda5da8dSAndroid Build Coastguard Worker bye = self.untagged_responses.get('BYE') 960*cda5da8dSAndroid Build Coastguard Worker if bye: 961*cda5da8dSAndroid Build Coastguard Worker raise self.abort(bye[-1].decode(self._encoding, 'replace')) 962*cda5da8dSAndroid Build Coastguard Worker 963*cda5da8dSAndroid Build Coastguard Worker 964*cda5da8dSAndroid Build Coastguard Worker def _command(self, name, *args): 965*cda5da8dSAndroid Build Coastguard Worker 966*cda5da8dSAndroid Build Coastguard Worker if self.state not in Commands[name]: 967*cda5da8dSAndroid Build Coastguard Worker self.literal = None 968*cda5da8dSAndroid Build Coastguard Worker raise self.error("command %s illegal in state %s, " 969*cda5da8dSAndroid Build Coastguard Worker "only allowed in states %s" % 970*cda5da8dSAndroid Build Coastguard Worker (name, self.state, 971*cda5da8dSAndroid Build Coastguard Worker ', '.join(Commands[name]))) 972*cda5da8dSAndroid Build Coastguard Worker 973*cda5da8dSAndroid Build Coastguard Worker for typ in ('OK', 'NO', 'BAD'): 974*cda5da8dSAndroid Build Coastguard Worker if typ in self.untagged_responses: 975*cda5da8dSAndroid Build Coastguard Worker del self.untagged_responses[typ] 976*cda5da8dSAndroid Build Coastguard Worker 977*cda5da8dSAndroid Build Coastguard Worker if 'READ-ONLY' in self.untagged_responses \ 978*cda5da8dSAndroid Build Coastguard Worker and not self.is_readonly: 979*cda5da8dSAndroid Build Coastguard Worker raise self.readonly('mailbox status changed to READ-ONLY') 980*cda5da8dSAndroid Build Coastguard Worker 981*cda5da8dSAndroid Build Coastguard Worker tag = self._new_tag() 982*cda5da8dSAndroid Build Coastguard Worker name = bytes(name, self._encoding) 983*cda5da8dSAndroid Build Coastguard Worker data = tag + b' ' + name 984*cda5da8dSAndroid Build Coastguard Worker for arg in args: 985*cda5da8dSAndroid Build Coastguard Worker if arg is None: continue 986*cda5da8dSAndroid Build Coastguard Worker if isinstance(arg, str): 987*cda5da8dSAndroid Build Coastguard Worker arg = bytes(arg, self._encoding) 988*cda5da8dSAndroid Build Coastguard Worker data = data + b' ' + arg 989*cda5da8dSAndroid Build Coastguard Worker 990*cda5da8dSAndroid Build Coastguard Worker literal = self.literal 991*cda5da8dSAndroid Build Coastguard Worker if literal is not None: 992*cda5da8dSAndroid Build Coastguard Worker self.literal = None 993*cda5da8dSAndroid Build Coastguard Worker if type(literal) is type(self._command): 994*cda5da8dSAndroid Build Coastguard Worker literator = literal 995*cda5da8dSAndroid Build Coastguard Worker else: 996*cda5da8dSAndroid Build Coastguard Worker literator = None 997*cda5da8dSAndroid Build Coastguard Worker data = data + bytes(' {%s}' % len(literal), self._encoding) 998*cda5da8dSAndroid Build Coastguard Worker 999*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1000*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 4: 1001*cda5da8dSAndroid Build Coastguard Worker self._mesg('> %r' % data) 1002*cda5da8dSAndroid Build Coastguard Worker else: 1003*cda5da8dSAndroid Build Coastguard Worker self._log('> %r' % data) 1004*cda5da8dSAndroid Build Coastguard Worker 1005*cda5da8dSAndroid Build Coastguard Worker try: 1006*cda5da8dSAndroid Build Coastguard Worker self.send(data + CRLF) 1007*cda5da8dSAndroid Build Coastguard Worker except OSError as val: 1008*cda5da8dSAndroid Build Coastguard Worker raise self.abort('socket error: %s' % val) 1009*cda5da8dSAndroid Build Coastguard Worker 1010*cda5da8dSAndroid Build Coastguard Worker if literal is None: 1011*cda5da8dSAndroid Build Coastguard Worker return tag 1012*cda5da8dSAndroid Build Coastguard Worker 1013*cda5da8dSAndroid Build Coastguard Worker while 1: 1014*cda5da8dSAndroid Build Coastguard Worker # Wait for continuation response 1015*cda5da8dSAndroid Build Coastguard Worker 1016*cda5da8dSAndroid Build Coastguard Worker while self._get_response(): 1017*cda5da8dSAndroid Build Coastguard Worker if self.tagged_commands[tag]: # BAD/NO? 1018*cda5da8dSAndroid Build Coastguard Worker return tag 1019*cda5da8dSAndroid Build Coastguard Worker 1020*cda5da8dSAndroid Build Coastguard Worker # Send literal 1021*cda5da8dSAndroid Build Coastguard Worker 1022*cda5da8dSAndroid Build Coastguard Worker if literator: 1023*cda5da8dSAndroid Build Coastguard Worker literal = literator(self.continuation_response) 1024*cda5da8dSAndroid Build Coastguard Worker 1025*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1026*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 4: 1027*cda5da8dSAndroid Build Coastguard Worker self._mesg('write literal size %s' % len(literal)) 1028*cda5da8dSAndroid Build Coastguard Worker 1029*cda5da8dSAndroid Build Coastguard Worker try: 1030*cda5da8dSAndroid Build Coastguard Worker self.send(literal) 1031*cda5da8dSAndroid Build Coastguard Worker self.send(CRLF) 1032*cda5da8dSAndroid Build Coastguard Worker except OSError as val: 1033*cda5da8dSAndroid Build Coastguard Worker raise self.abort('socket error: %s' % val) 1034*cda5da8dSAndroid Build Coastguard Worker 1035*cda5da8dSAndroid Build Coastguard Worker if not literator: 1036*cda5da8dSAndroid Build Coastguard Worker break 1037*cda5da8dSAndroid Build Coastguard Worker 1038*cda5da8dSAndroid Build Coastguard Worker return tag 1039*cda5da8dSAndroid Build Coastguard Worker 1040*cda5da8dSAndroid Build Coastguard Worker 1041*cda5da8dSAndroid Build Coastguard Worker def _command_complete(self, name, tag): 1042*cda5da8dSAndroid Build Coastguard Worker logout = (name == 'LOGOUT') 1043*cda5da8dSAndroid Build Coastguard Worker # BYE is expected after LOGOUT 1044*cda5da8dSAndroid Build Coastguard Worker if not logout: 1045*cda5da8dSAndroid Build Coastguard Worker self._check_bye() 1046*cda5da8dSAndroid Build Coastguard Worker try: 1047*cda5da8dSAndroid Build Coastguard Worker typ, data = self._get_tagged_response(tag, expect_bye=logout) 1048*cda5da8dSAndroid Build Coastguard Worker except self.abort as val: 1049*cda5da8dSAndroid Build Coastguard Worker raise self.abort('command: %s => %s' % (name, val)) 1050*cda5da8dSAndroid Build Coastguard Worker except self.error as val: 1051*cda5da8dSAndroid Build Coastguard Worker raise self.error('command: %s => %s' % (name, val)) 1052*cda5da8dSAndroid Build Coastguard Worker if not logout: 1053*cda5da8dSAndroid Build Coastguard Worker self._check_bye() 1054*cda5da8dSAndroid Build Coastguard Worker if typ == 'BAD': 1055*cda5da8dSAndroid Build Coastguard Worker raise self.error('%s command error: %s %s' % (name, typ, data)) 1056*cda5da8dSAndroid Build Coastguard Worker return typ, data 1057*cda5da8dSAndroid Build Coastguard Worker 1058*cda5da8dSAndroid Build Coastguard Worker 1059*cda5da8dSAndroid Build Coastguard Worker def _get_capabilities(self): 1060*cda5da8dSAndroid Build Coastguard Worker typ, dat = self.capability() 1061*cda5da8dSAndroid Build Coastguard Worker if dat == [None]: 1062*cda5da8dSAndroid Build Coastguard Worker raise self.error('no CAPABILITY response from server') 1063*cda5da8dSAndroid Build Coastguard Worker dat = str(dat[-1], self._encoding) 1064*cda5da8dSAndroid Build Coastguard Worker dat = dat.upper() 1065*cda5da8dSAndroid Build Coastguard Worker self.capabilities = tuple(dat.split()) 1066*cda5da8dSAndroid Build Coastguard Worker 1067*cda5da8dSAndroid Build Coastguard Worker 1068*cda5da8dSAndroid Build Coastguard Worker def _get_response(self): 1069*cda5da8dSAndroid Build Coastguard Worker 1070*cda5da8dSAndroid Build Coastguard Worker # Read response and store. 1071*cda5da8dSAndroid Build Coastguard Worker # 1072*cda5da8dSAndroid Build Coastguard Worker # Returns None for continuation responses, 1073*cda5da8dSAndroid Build Coastguard Worker # otherwise first response line received. 1074*cda5da8dSAndroid Build Coastguard Worker 1075*cda5da8dSAndroid Build Coastguard Worker resp = self._get_line() 1076*cda5da8dSAndroid Build Coastguard Worker 1077*cda5da8dSAndroid Build Coastguard Worker # Command completion response? 1078*cda5da8dSAndroid Build Coastguard Worker 1079*cda5da8dSAndroid Build Coastguard Worker if self._match(self.tagre, resp): 1080*cda5da8dSAndroid Build Coastguard Worker tag = self.mo.group('tag') 1081*cda5da8dSAndroid Build Coastguard Worker if not tag in self.tagged_commands: 1082*cda5da8dSAndroid Build Coastguard Worker raise self.abort('unexpected tagged response: %r' % resp) 1083*cda5da8dSAndroid Build Coastguard Worker 1084*cda5da8dSAndroid Build Coastguard Worker typ = self.mo.group('type') 1085*cda5da8dSAndroid Build Coastguard Worker typ = str(typ, self._encoding) 1086*cda5da8dSAndroid Build Coastguard Worker dat = self.mo.group('data') 1087*cda5da8dSAndroid Build Coastguard Worker self.tagged_commands[tag] = (typ, [dat]) 1088*cda5da8dSAndroid Build Coastguard Worker else: 1089*cda5da8dSAndroid Build Coastguard Worker dat2 = None 1090*cda5da8dSAndroid Build Coastguard Worker 1091*cda5da8dSAndroid Build Coastguard Worker # '*' (untagged) responses? 1092*cda5da8dSAndroid Build Coastguard Worker 1093*cda5da8dSAndroid Build Coastguard Worker if not self._match(Untagged_response, resp): 1094*cda5da8dSAndroid Build Coastguard Worker if self._match(self.Untagged_status, resp): 1095*cda5da8dSAndroid Build Coastguard Worker dat2 = self.mo.group('data2') 1096*cda5da8dSAndroid Build Coastguard Worker 1097*cda5da8dSAndroid Build Coastguard Worker if self.mo is None: 1098*cda5da8dSAndroid Build Coastguard Worker # Only other possibility is '+' (continuation) response... 1099*cda5da8dSAndroid Build Coastguard Worker 1100*cda5da8dSAndroid Build Coastguard Worker if self._match(Continuation, resp): 1101*cda5da8dSAndroid Build Coastguard Worker self.continuation_response = self.mo.group('data') 1102*cda5da8dSAndroid Build Coastguard Worker return None # NB: indicates continuation 1103*cda5da8dSAndroid Build Coastguard Worker 1104*cda5da8dSAndroid Build Coastguard Worker raise self.abort("unexpected response: %r" % resp) 1105*cda5da8dSAndroid Build Coastguard Worker 1106*cda5da8dSAndroid Build Coastguard Worker typ = self.mo.group('type') 1107*cda5da8dSAndroid Build Coastguard Worker typ = str(typ, self._encoding) 1108*cda5da8dSAndroid Build Coastguard Worker dat = self.mo.group('data') 1109*cda5da8dSAndroid Build Coastguard Worker if dat is None: dat = b'' # Null untagged response 1110*cda5da8dSAndroid Build Coastguard Worker if dat2: dat = dat + b' ' + dat2 1111*cda5da8dSAndroid Build Coastguard Worker 1112*cda5da8dSAndroid Build Coastguard Worker # Is there a literal to come? 1113*cda5da8dSAndroid Build Coastguard Worker 1114*cda5da8dSAndroid Build Coastguard Worker while self._match(self.Literal, dat): 1115*cda5da8dSAndroid Build Coastguard Worker 1116*cda5da8dSAndroid Build Coastguard Worker # Read literal direct from connection. 1117*cda5da8dSAndroid Build Coastguard Worker 1118*cda5da8dSAndroid Build Coastguard Worker size = int(self.mo.group('size')) 1119*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1120*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 4: 1121*cda5da8dSAndroid Build Coastguard Worker self._mesg('read literal size %s' % size) 1122*cda5da8dSAndroid Build Coastguard Worker data = self.read(size) 1123*cda5da8dSAndroid Build Coastguard Worker 1124*cda5da8dSAndroid Build Coastguard Worker # Store response with literal as tuple 1125*cda5da8dSAndroid Build Coastguard Worker 1126*cda5da8dSAndroid Build Coastguard Worker self._append_untagged(typ, (dat, data)) 1127*cda5da8dSAndroid Build Coastguard Worker 1128*cda5da8dSAndroid Build Coastguard Worker # Read trailer - possibly containing another literal 1129*cda5da8dSAndroid Build Coastguard Worker 1130*cda5da8dSAndroid Build Coastguard Worker dat = self._get_line() 1131*cda5da8dSAndroid Build Coastguard Worker 1132*cda5da8dSAndroid Build Coastguard Worker self._append_untagged(typ, dat) 1133*cda5da8dSAndroid Build Coastguard Worker 1134*cda5da8dSAndroid Build Coastguard Worker # Bracketed response information? 1135*cda5da8dSAndroid Build Coastguard Worker 1136*cda5da8dSAndroid Build Coastguard Worker if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat): 1137*cda5da8dSAndroid Build Coastguard Worker typ = self.mo.group('type') 1138*cda5da8dSAndroid Build Coastguard Worker typ = str(typ, self._encoding) 1139*cda5da8dSAndroid Build Coastguard Worker self._append_untagged(typ, self.mo.group('data')) 1140*cda5da8dSAndroid Build Coastguard Worker 1141*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1142*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'): 1143*cda5da8dSAndroid Build Coastguard Worker self._mesg('%s response: %r' % (typ, dat)) 1144*cda5da8dSAndroid Build Coastguard Worker 1145*cda5da8dSAndroid Build Coastguard Worker return resp 1146*cda5da8dSAndroid Build Coastguard Worker 1147*cda5da8dSAndroid Build Coastguard Worker 1148*cda5da8dSAndroid Build Coastguard Worker def _get_tagged_response(self, tag, expect_bye=False): 1149*cda5da8dSAndroid Build Coastguard Worker 1150*cda5da8dSAndroid Build Coastguard Worker while 1: 1151*cda5da8dSAndroid Build Coastguard Worker result = self.tagged_commands[tag] 1152*cda5da8dSAndroid Build Coastguard Worker if result is not None: 1153*cda5da8dSAndroid Build Coastguard Worker del self.tagged_commands[tag] 1154*cda5da8dSAndroid Build Coastguard Worker return result 1155*cda5da8dSAndroid Build Coastguard Worker 1156*cda5da8dSAndroid Build Coastguard Worker if expect_bye: 1157*cda5da8dSAndroid Build Coastguard Worker typ = 'BYE' 1158*cda5da8dSAndroid Build Coastguard Worker bye = self.untagged_responses.pop(typ, None) 1159*cda5da8dSAndroid Build Coastguard Worker if bye is not None: 1160*cda5da8dSAndroid Build Coastguard Worker # Server replies to the "LOGOUT" command with "BYE" 1161*cda5da8dSAndroid Build Coastguard Worker return (typ, bye) 1162*cda5da8dSAndroid Build Coastguard Worker 1163*cda5da8dSAndroid Build Coastguard Worker # If we've seen a BYE at this point, the socket will be 1164*cda5da8dSAndroid Build Coastguard Worker # closed, so report the BYE now. 1165*cda5da8dSAndroid Build Coastguard Worker self._check_bye() 1166*cda5da8dSAndroid Build Coastguard Worker 1167*cda5da8dSAndroid Build Coastguard Worker # Some have reported "unexpected response" exceptions. 1168*cda5da8dSAndroid Build Coastguard Worker # Note that ignoring them here causes loops. 1169*cda5da8dSAndroid Build Coastguard Worker # Instead, send me details of the unexpected response and 1170*cda5da8dSAndroid Build Coastguard Worker # I'll update the code in `_get_response()'. 1171*cda5da8dSAndroid Build Coastguard Worker 1172*cda5da8dSAndroid Build Coastguard Worker try: 1173*cda5da8dSAndroid Build Coastguard Worker self._get_response() 1174*cda5da8dSAndroid Build Coastguard Worker except self.abort as val: 1175*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1176*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 1: 1177*cda5da8dSAndroid Build Coastguard Worker self.print_log() 1178*cda5da8dSAndroid Build Coastguard Worker raise 1179*cda5da8dSAndroid Build Coastguard Worker 1180*cda5da8dSAndroid Build Coastguard Worker 1181*cda5da8dSAndroid Build Coastguard Worker def _get_line(self): 1182*cda5da8dSAndroid Build Coastguard Worker 1183*cda5da8dSAndroid Build Coastguard Worker line = self.readline() 1184*cda5da8dSAndroid Build Coastguard Worker if not line: 1185*cda5da8dSAndroid Build Coastguard Worker raise self.abort('socket error: EOF') 1186*cda5da8dSAndroid Build Coastguard Worker 1187*cda5da8dSAndroid Build Coastguard Worker # Protocol mandates all lines terminated by CRLF 1188*cda5da8dSAndroid Build Coastguard Worker if not line.endswith(b'\r\n'): 1189*cda5da8dSAndroid Build Coastguard Worker raise self.abort('socket error: unterminated line: %r' % line) 1190*cda5da8dSAndroid Build Coastguard Worker 1191*cda5da8dSAndroid Build Coastguard Worker line = line[:-2] 1192*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1193*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 4: 1194*cda5da8dSAndroid Build Coastguard Worker self._mesg('< %r' % line) 1195*cda5da8dSAndroid Build Coastguard Worker else: 1196*cda5da8dSAndroid Build Coastguard Worker self._log('< %r' % line) 1197*cda5da8dSAndroid Build Coastguard Worker return line 1198*cda5da8dSAndroid Build Coastguard Worker 1199*cda5da8dSAndroid Build Coastguard Worker 1200*cda5da8dSAndroid Build Coastguard Worker def _match(self, cre, s): 1201*cda5da8dSAndroid Build Coastguard Worker 1202*cda5da8dSAndroid Build Coastguard Worker # Run compiled regular expression match method on 's'. 1203*cda5da8dSAndroid Build Coastguard Worker # Save result, return success. 1204*cda5da8dSAndroid Build Coastguard Worker 1205*cda5da8dSAndroid Build Coastguard Worker self.mo = cre.match(s) 1206*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1207*cda5da8dSAndroid Build Coastguard Worker if self.mo is not None and self.debug >= 5: 1208*cda5da8dSAndroid Build Coastguard Worker self._mesg("\tmatched %r => %r" % (cre.pattern, self.mo.groups())) 1209*cda5da8dSAndroid Build Coastguard Worker return self.mo is not None 1210*cda5da8dSAndroid Build Coastguard Worker 1211*cda5da8dSAndroid Build Coastguard Worker 1212*cda5da8dSAndroid Build Coastguard Worker def _new_tag(self): 1213*cda5da8dSAndroid Build Coastguard Worker 1214*cda5da8dSAndroid Build Coastguard Worker tag = self.tagpre + bytes(str(self.tagnum), self._encoding) 1215*cda5da8dSAndroid Build Coastguard Worker self.tagnum = self.tagnum + 1 1216*cda5da8dSAndroid Build Coastguard Worker self.tagged_commands[tag] = None 1217*cda5da8dSAndroid Build Coastguard Worker return tag 1218*cda5da8dSAndroid Build Coastguard Worker 1219*cda5da8dSAndroid Build Coastguard Worker 1220*cda5da8dSAndroid Build Coastguard Worker def _quote(self, arg): 1221*cda5da8dSAndroid Build Coastguard Worker 1222*cda5da8dSAndroid Build Coastguard Worker arg = arg.replace('\\', '\\\\') 1223*cda5da8dSAndroid Build Coastguard Worker arg = arg.replace('"', '\\"') 1224*cda5da8dSAndroid Build Coastguard Worker 1225*cda5da8dSAndroid Build Coastguard Worker return '"' + arg + '"' 1226*cda5da8dSAndroid Build Coastguard Worker 1227*cda5da8dSAndroid Build Coastguard Worker 1228*cda5da8dSAndroid Build Coastguard Worker def _simple_command(self, name, *args): 1229*cda5da8dSAndroid Build Coastguard Worker 1230*cda5da8dSAndroid Build Coastguard Worker return self._command_complete(name, self._command(name, *args)) 1231*cda5da8dSAndroid Build Coastguard Worker 1232*cda5da8dSAndroid Build Coastguard Worker 1233*cda5da8dSAndroid Build Coastguard Worker def _untagged_response(self, typ, dat, name): 1234*cda5da8dSAndroid Build Coastguard Worker if typ == 'NO': 1235*cda5da8dSAndroid Build Coastguard Worker return typ, dat 1236*cda5da8dSAndroid Build Coastguard Worker if not name in self.untagged_responses: 1237*cda5da8dSAndroid Build Coastguard Worker return typ, [None] 1238*cda5da8dSAndroid Build Coastguard Worker data = self.untagged_responses.pop(name) 1239*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1240*cda5da8dSAndroid Build Coastguard Worker if self.debug >= 5: 1241*cda5da8dSAndroid Build Coastguard Worker self._mesg('untagged_responses[%s] => %s' % (name, data)) 1242*cda5da8dSAndroid Build Coastguard Worker return typ, data 1243*cda5da8dSAndroid Build Coastguard Worker 1244*cda5da8dSAndroid Build Coastguard Worker 1245*cda5da8dSAndroid Build Coastguard Worker if __debug__: 1246*cda5da8dSAndroid Build Coastguard Worker 1247*cda5da8dSAndroid Build Coastguard Worker def _mesg(self, s, secs=None): 1248*cda5da8dSAndroid Build Coastguard Worker if secs is None: 1249*cda5da8dSAndroid Build Coastguard Worker secs = time.time() 1250*cda5da8dSAndroid Build Coastguard Worker tm = time.strftime('%M:%S', time.localtime(secs)) 1251*cda5da8dSAndroid Build Coastguard Worker sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s)) 1252*cda5da8dSAndroid Build Coastguard Worker sys.stderr.flush() 1253*cda5da8dSAndroid Build Coastguard Worker 1254*cda5da8dSAndroid Build Coastguard Worker def _dump_ur(self, untagged_resp_dict): 1255*cda5da8dSAndroid Build Coastguard Worker if not untagged_resp_dict: 1256*cda5da8dSAndroid Build Coastguard Worker return 1257*cda5da8dSAndroid Build Coastguard Worker items = (f'{key}: {value!r}' 1258*cda5da8dSAndroid Build Coastguard Worker for key, value in untagged_resp_dict.items()) 1259*cda5da8dSAndroid Build Coastguard Worker self._mesg('untagged responses dump:' + '\n\t\t'.join(items)) 1260*cda5da8dSAndroid Build Coastguard Worker 1261*cda5da8dSAndroid Build Coastguard Worker def _log(self, line): 1262*cda5da8dSAndroid Build Coastguard Worker # Keep log of last `_cmd_log_len' interactions for debugging. 1263*cda5da8dSAndroid Build Coastguard Worker self._cmd_log[self._cmd_log_idx] = (line, time.time()) 1264*cda5da8dSAndroid Build Coastguard Worker self._cmd_log_idx += 1 1265*cda5da8dSAndroid Build Coastguard Worker if self._cmd_log_idx >= self._cmd_log_len: 1266*cda5da8dSAndroid Build Coastguard Worker self._cmd_log_idx = 0 1267*cda5da8dSAndroid Build Coastguard Worker 1268*cda5da8dSAndroid Build Coastguard Worker def print_log(self): 1269*cda5da8dSAndroid Build Coastguard Worker self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log)) 1270*cda5da8dSAndroid Build Coastguard Worker i, n = self._cmd_log_idx, self._cmd_log_len 1271*cda5da8dSAndroid Build Coastguard Worker while n: 1272*cda5da8dSAndroid Build Coastguard Worker try: 1273*cda5da8dSAndroid Build Coastguard Worker self._mesg(*self._cmd_log[i]) 1274*cda5da8dSAndroid Build Coastguard Worker except: 1275*cda5da8dSAndroid Build Coastguard Worker pass 1276*cda5da8dSAndroid Build Coastguard Worker i += 1 1277*cda5da8dSAndroid Build Coastguard Worker if i >= self._cmd_log_len: 1278*cda5da8dSAndroid Build Coastguard Worker i = 0 1279*cda5da8dSAndroid Build Coastguard Worker n -= 1 1280*cda5da8dSAndroid Build Coastguard Worker 1281*cda5da8dSAndroid Build Coastguard Worker 1282*cda5da8dSAndroid Build Coastguard Workerif HAVE_SSL: 1283*cda5da8dSAndroid Build Coastguard Worker 1284*cda5da8dSAndroid Build Coastguard Worker class IMAP4_SSL(IMAP4): 1285*cda5da8dSAndroid Build Coastguard Worker 1286*cda5da8dSAndroid Build Coastguard Worker """IMAP4 client class over SSL connection 1287*cda5da8dSAndroid Build Coastguard Worker 1288*cda5da8dSAndroid Build Coastguard Worker Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context[, timeout=None]]]]]]) 1289*cda5da8dSAndroid Build Coastguard Worker 1290*cda5da8dSAndroid Build Coastguard Worker host - host's name (default: localhost); 1291*cda5da8dSAndroid Build Coastguard Worker port - port number (default: standard IMAP4 SSL port); 1292*cda5da8dSAndroid Build Coastguard Worker keyfile - PEM formatted file that contains your private key (default: None); 1293*cda5da8dSAndroid Build Coastguard Worker certfile - PEM formatted certificate chain file (default: None); 1294*cda5da8dSAndroid Build Coastguard Worker ssl_context - a SSLContext object that contains your certificate chain 1295*cda5da8dSAndroid Build Coastguard Worker and private key (default: None) 1296*cda5da8dSAndroid Build Coastguard Worker Note: if ssl_context is provided, then parameters keyfile or 1297*cda5da8dSAndroid Build Coastguard Worker certfile should not be set otherwise ValueError is raised. 1298*cda5da8dSAndroid Build Coastguard Worker timeout - socket timeout (default: None) If timeout is not given or is None, 1299*cda5da8dSAndroid Build Coastguard Worker the global default socket timeout is used 1300*cda5da8dSAndroid Build Coastguard Worker 1301*cda5da8dSAndroid Build Coastguard Worker for more documentation see the docstring of the parent class IMAP4. 1302*cda5da8dSAndroid Build Coastguard Worker """ 1303*cda5da8dSAndroid Build Coastguard Worker 1304*cda5da8dSAndroid Build Coastguard Worker 1305*cda5da8dSAndroid Build Coastguard Worker def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, 1306*cda5da8dSAndroid Build Coastguard Worker certfile=None, ssl_context=None, timeout=None): 1307*cda5da8dSAndroid Build Coastguard Worker if ssl_context is not None and keyfile is not None: 1308*cda5da8dSAndroid Build Coastguard Worker raise ValueError("ssl_context and keyfile arguments are mutually " 1309*cda5da8dSAndroid Build Coastguard Worker "exclusive") 1310*cda5da8dSAndroid Build Coastguard Worker if ssl_context is not None and certfile is not None: 1311*cda5da8dSAndroid Build Coastguard Worker raise ValueError("ssl_context and certfile arguments are mutually " 1312*cda5da8dSAndroid Build Coastguard Worker "exclusive") 1313*cda5da8dSAndroid Build Coastguard Worker if keyfile is not None or certfile is not None: 1314*cda5da8dSAndroid Build Coastguard Worker import warnings 1315*cda5da8dSAndroid Build Coastguard Worker warnings.warn("keyfile and certfile are deprecated, use a " 1316*cda5da8dSAndroid Build Coastguard Worker "custom ssl_context instead", DeprecationWarning, 2) 1317*cda5da8dSAndroid Build Coastguard Worker self.keyfile = keyfile 1318*cda5da8dSAndroid Build Coastguard Worker self.certfile = certfile 1319*cda5da8dSAndroid Build Coastguard Worker if ssl_context is None: 1320*cda5da8dSAndroid Build Coastguard Worker ssl_context = ssl._create_stdlib_context(certfile=certfile, 1321*cda5da8dSAndroid Build Coastguard Worker keyfile=keyfile) 1322*cda5da8dSAndroid Build Coastguard Worker self.ssl_context = ssl_context 1323*cda5da8dSAndroid Build Coastguard Worker IMAP4.__init__(self, host, port, timeout) 1324*cda5da8dSAndroid Build Coastguard Worker 1325*cda5da8dSAndroid Build Coastguard Worker def _create_socket(self, timeout): 1326*cda5da8dSAndroid Build Coastguard Worker sock = IMAP4._create_socket(self, timeout) 1327*cda5da8dSAndroid Build Coastguard Worker return self.ssl_context.wrap_socket(sock, 1328*cda5da8dSAndroid Build Coastguard Worker server_hostname=self.host) 1329*cda5da8dSAndroid Build Coastguard Worker 1330*cda5da8dSAndroid Build Coastguard Worker def open(self, host='', port=IMAP4_SSL_PORT, timeout=None): 1331*cda5da8dSAndroid Build Coastguard Worker """Setup connection to remote server on "host:port". 1332*cda5da8dSAndroid Build Coastguard Worker (default: localhost:standard IMAP4 SSL port). 1333*cda5da8dSAndroid Build Coastguard Worker This connection will be used by the routines: 1334*cda5da8dSAndroid Build Coastguard Worker read, readline, send, shutdown. 1335*cda5da8dSAndroid Build Coastguard Worker """ 1336*cda5da8dSAndroid Build Coastguard Worker IMAP4.open(self, host, port, timeout) 1337*cda5da8dSAndroid Build Coastguard Worker 1338*cda5da8dSAndroid Build Coastguard Worker __all__.append("IMAP4_SSL") 1339*cda5da8dSAndroid Build Coastguard Worker 1340*cda5da8dSAndroid Build Coastguard Worker 1341*cda5da8dSAndroid Build Coastguard Workerclass IMAP4_stream(IMAP4): 1342*cda5da8dSAndroid Build Coastguard Worker 1343*cda5da8dSAndroid Build Coastguard Worker """IMAP4 client class over a stream 1344*cda5da8dSAndroid Build Coastguard Worker 1345*cda5da8dSAndroid Build Coastguard Worker Instantiate with: IMAP4_stream(command) 1346*cda5da8dSAndroid Build Coastguard Worker 1347*cda5da8dSAndroid Build Coastguard Worker "command" - a string that can be passed to subprocess.Popen() 1348*cda5da8dSAndroid Build Coastguard Worker 1349*cda5da8dSAndroid Build Coastguard Worker for more documentation see the docstring of the parent class IMAP4. 1350*cda5da8dSAndroid Build Coastguard Worker """ 1351*cda5da8dSAndroid Build Coastguard Worker 1352*cda5da8dSAndroid Build Coastguard Worker 1353*cda5da8dSAndroid Build Coastguard Worker def __init__(self, command): 1354*cda5da8dSAndroid Build Coastguard Worker self.command = command 1355*cda5da8dSAndroid Build Coastguard Worker IMAP4.__init__(self) 1356*cda5da8dSAndroid Build Coastguard Worker 1357*cda5da8dSAndroid Build Coastguard Worker 1358*cda5da8dSAndroid Build Coastguard Worker def open(self, host=None, port=None, timeout=None): 1359*cda5da8dSAndroid Build Coastguard Worker """Setup a stream connection. 1360*cda5da8dSAndroid Build Coastguard Worker This connection will be used by the routines: 1361*cda5da8dSAndroid Build Coastguard Worker read, readline, send, shutdown. 1362*cda5da8dSAndroid Build Coastguard Worker """ 1363*cda5da8dSAndroid Build Coastguard Worker self.host = None # For compatibility with parent class 1364*cda5da8dSAndroid Build Coastguard Worker self.port = None 1365*cda5da8dSAndroid Build Coastguard Worker self.sock = None 1366*cda5da8dSAndroid Build Coastguard Worker self.file = None 1367*cda5da8dSAndroid Build Coastguard Worker self.process = subprocess.Popen(self.command, 1368*cda5da8dSAndroid Build Coastguard Worker bufsize=DEFAULT_BUFFER_SIZE, 1369*cda5da8dSAndroid Build Coastguard Worker stdin=subprocess.PIPE, stdout=subprocess.PIPE, 1370*cda5da8dSAndroid Build Coastguard Worker shell=True, close_fds=True) 1371*cda5da8dSAndroid Build Coastguard Worker self.writefile = self.process.stdin 1372*cda5da8dSAndroid Build Coastguard Worker self.readfile = self.process.stdout 1373*cda5da8dSAndroid Build Coastguard Worker 1374*cda5da8dSAndroid Build Coastguard Worker def read(self, size): 1375*cda5da8dSAndroid Build Coastguard Worker """Read 'size' bytes from remote.""" 1376*cda5da8dSAndroid Build Coastguard Worker return self.readfile.read(size) 1377*cda5da8dSAndroid Build Coastguard Worker 1378*cda5da8dSAndroid Build Coastguard Worker 1379*cda5da8dSAndroid Build Coastguard Worker def readline(self): 1380*cda5da8dSAndroid Build Coastguard Worker """Read line from remote.""" 1381*cda5da8dSAndroid Build Coastguard Worker return self.readfile.readline() 1382*cda5da8dSAndroid Build Coastguard Worker 1383*cda5da8dSAndroid Build Coastguard Worker 1384*cda5da8dSAndroid Build Coastguard Worker def send(self, data): 1385*cda5da8dSAndroid Build Coastguard Worker """Send data to remote.""" 1386*cda5da8dSAndroid Build Coastguard Worker self.writefile.write(data) 1387*cda5da8dSAndroid Build Coastguard Worker self.writefile.flush() 1388*cda5da8dSAndroid Build Coastguard Worker 1389*cda5da8dSAndroid Build Coastguard Worker 1390*cda5da8dSAndroid Build Coastguard Worker def shutdown(self): 1391*cda5da8dSAndroid Build Coastguard Worker """Close I/O established in "open".""" 1392*cda5da8dSAndroid Build Coastguard Worker self.readfile.close() 1393*cda5da8dSAndroid Build Coastguard Worker self.writefile.close() 1394*cda5da8dSAndroid Build Coastguard Worker self.process.wait() 1395*cda5da8dSAndroid Build Coastguard Worker 1396*cda5da8dSAndroid Build Coastguard Worker 1397*cda5da8dSAndroid Build Coastguard Worker 1398*cda5da8dSAndroid Build Coastguard Workerclass _Authenticator: 1399*cda5da8dSAndroid Build Coastguard Worker 1400*cda5da8dSAndroid Build Coastguard Worker """Private class to provide en/decoding 1401*cda5da8dSAndroid Build Coastguard Worker for base64-based authentication conversation. 1402*cda5da8dSAndroid Build Coastguard Worker """ 1403*cda5da8dSAndroid Build Coastguard Worker 1404*cda5da8dSAndroid Build Coastguard Worker def __init__(self, mechinst): 1405*cda5da8dSAndroid Build Coastguard Worker self.mech = mechinst # Callable object to provide/process data 1406*cda5da8dSAndroid Build Coastguard Worker 1407*cda5da8dSAndroid Build Coastguard Worker def process(self, data): 1408*cda5da8dSAndroid Build Coastguard Worker ret = self.mech(self.decode(data)) 1409*cda5da8dSAndroid Build Coastguard Worker if ret is None: 1410*cda5da8dSAndroid Build Coastguard Worker return b'*' # Abort conversation 1411*cda5da8dSAndroid Build Coastguard Worker return self.encode(ret) 1412*cda5da8dSAndroid Build Coastguard Worker 1413*cda5da8dSAndroid Build Coastguard Worker def encode(self, inp): 1414*cda5da8dSAndroid Build Coastguard Worker # 1415*cda5da8dSAndroid Build Coastguard Worker # Invoke binascii.b2a_base64 iteratively with 1416*cda5da8dSAndroid Build Coastguard Worker # short even length buffers, strip the trailing 1417*cda5da8dSAndroid Build Coastguard Worker # line feed from the result and append. "Even" 1418*cda5da8dSAndroid Build Coastguard Worker # means a number that factors to both 6 and 8, 1419*cda5da8dSAndroid Build Coastguard Worker # so when it gets to the end of the 8-bit input 1420*cda5da8dSAndroid Build Coastguard Worker # there's no partial 6-bit output. 1421*cda5da8dSAndroid Build Coastguard Worker # 1422*cda5da8dSAndroid Build Coastguard Worker oup = b'' 1423*cda5da8dSAndroid Build Coastguard Worker if isinstance(inp, str): 1424*cda5da8dSAndroid Build Coastguard Worker inp = inp.encode('utf-8') 1425*cda5da8dSAndroid Build Coastguard Worker while inp: 1426*cda5da8dSAndroid Build Coastguard Worker if len(inp) > 48: 1427*cda5da8dSAndroid Build Coastguard Worker t = inp[:48] 1428*cda5da8dSAndroid Build Coastguard Worker inp = inp[48:] 1429*cda5da8dSAndroid Build Coastguard Worker else: 1430*cda5da8dSAndroid Build Coastguard Worker t = inp 1431*cda5da8dSAndroid Build Coastguard Worker inp = b'' 1432*cda5da8dSAndroid Build Coastguard Worker e = binascii.b2a_base64(t) 1433*cda5da8dSAndroid Build Coastguard Worker if e: 1434*cda5da8dSAndroid Build Coastguard Worker oup = oup + e[:-1] 1435*cda5da8dSAndroid Build Coastguard Worker return oup 1436*cda5da8dSAndroid Build Coastguard Worker 1437*cda5da8dSAndroid Build Coastguard Worker def decode(self, inp): 1438*cda5da8dSAndroid Build Coastguard Worker if not inp: 1439*cda5da8dSAndroid Build Coastguard Worker return b'' 1440*cda5da8dSAndroid Build Coastguard Worker return binascii.a2b_base64(inp) 1441*cda5da8dSAndroid Build Coastguard Worker 1442*cda5da8dSAndroid Build Coastguard WorkerMonths = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ') 1443*cda5da8dSAndroid Build Coastguard WorkerMon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])} 1444*cda5da8dSAndroid Build Coastguard Worker 1445*cda5da8dSAndroid Build Coastguard Workerdef Internaldate2tuple(resp): 1446*cda5da8dSAndroid Build Coastguard Worker """Parse an IMAP4 INTERNALDATE string. 1447*cda5da8dSAndroid Build Coastguard Worker 1448*cda5da8dSAndroid Build Coastguard Worker Return corresponding local time. The return value is a 1449*cda5da8dSAndroid Build Coastguard Worker time.struct_time tuple or None if the string has wrong format. 1450*cda5da8dSAndroid Build Coastguard Worker """ 1451*cda5da8dSAndroid Build Coastguard Worker 1452*cda5da8dSAndroid Build Coastguard Worker mo = InternalDate.match(resp) 1453*cda5da8dSAndroid Build Coastguard Worker if not mo: 1454*cda5da8dSAndroid Build Coastguard Worker return None 1455*cda5da8dSAndroid Build Coastguard Worker 1456*cda5da8dSAndroid Build Coastguard Worker mon = Mon2num[mo.group('mon')] 1457*cda5da8dSAndroid Build Coastguard Worker zonen = mo.group('zonen') 1458*cda5da8dSAndroid Build Coastguard Worker 1459*cda5da8dSAndroid Build Coastguard Worker day = int(mo.group('day')) 1460*cda5da8dSAndroid Build Coastguard Worker year = int(mo.group('year')) 1461*cda5da8dSAndroid Build Coastguard Worker hour = int(mo.group('hour')) 1462*cda5da8dSAndroid Build Coastguard Worker min = int(mo.group('min')) 1463*cda5da8dSAndroid Build Coastguard Worker sec = int(mo.group('sec')) 1464*cda5da8dSAndroid Build Coastguard Worker zoneh = int(mo.group('zoneh')) 1465*cda5da8dSAndroid Build Coastguard Worker zonem = int(mo.group('zonem')) 1466*cda5da8dSAndroid Build Coastguard Worker 1467*cda5da8dSAndroid Build Coastguard Worker # INTERNALDATE timezone must be subtracted to get UT 1468*cda5da8dSAndroid Build Coastguard Worker 1469*cda5da8dSAndroid Build Coastguard Worker zone = (zoneh*60 + zonem)*60 1470*cda5da8dSAndroid Build Coastguard Worker if zonen == b'-': 1471*cda5da8dSAndroid Build Coastguard Worker zone = -zone 1472*cda5da8dSAndroid Build Coastguard Worker 1473*cda5da8dSAndroid Build Coastguard Worker tt = (year, mon, day, hour, min, sec, -1, -1, -1) 1474*cda5da8dSAndroid Build Coastguard Worker utc = calendar.timegm(tt) - zone 1475*cda5da8dSAndroid Build Coastguard Worker 1476*cda5da8dSAndroid Build Coastguard Worker return time.localtime(utc) 1477*cda5da8dSAndroid Build Coastguard Worker 1478*cda5da8dSAndroid Build Coastguard Worker 1479*cda5da8dSAndroid Build Coastguard Worker 1480*cda5da8dSAndroid Build Coastguard Workerdef Int2AP(num): 1481*cda5da8dSAndroid Build Coastguard Worker 1482*cda5da8dSAndroid Build Coastguard Worker """Convert integer to A-P string representation.""" 1483*cda5da8dSAndroid Build Coastguard Worker 1484*cda5da8dSAndroid Build Coastguard Worker val = b''; AP = b'ABCDEFGHIJKLMNOP' 1485*cda5da8dSAndroid Build Coastguard Worker num = int(abs(num)) 1486*cda5da8dSAndroid Build Coastguard Worker while num: 1487*cda5da8dSAndroid Build Coastguard Worker num, mod = divmod(num, 16) 1488*cda5da8dSAndroid Build Coastguard Worker val = AP[mod:mod+1] + val 1489*cda5da8dSAndroid Build Coastguard Worker return val 1490*cda5da8dSAndroid Build Coastguard Worker 1491*cda5da8dSAndroid Build Coastguard Worker 1492*cda5da8dSAndroid Build Coastguard Worker 1493*cda5da8dSAndroid Build Coastguard Workerdef ParseFlags(resp): 1494*cda5da8dSAndroid Build Coastguard Worker 1495*cda5da8dSAndroid Build Coastguard Worker """Convert IMAP4 flags response to python tuple.""" 1496*cda5da8dSAndroid Build Coastguard Worker 1497*cda5da8dSAndroid Build Coastguard Worker mo = Flags.match(resp) 1498*cda5da8dSAndroid Build Coastguard Worker if not mo: 1499*cda5da8dSAndroid Build Coastguard Worker return () 1500*cda5da8dSAndroid Build Coastguard Worker 1501*cda5da8dSAndroid Build Coastguard Worker return tuple(mo.group('flags').split()) 1502*cda5da8dSAndroid Build Coastguard Worker 1503*cda5da8dSAndroid Build Coastguard Worker 1504*cda5da8dSAndroid Build Coastguard Workerdef Time2Internaldate(date_time): 1505*cda5da8dSAndroid Build Coastguard Worker 1506*cda5da8dSAndroid Build Coastguard Worker """Convert date_time to IMAP4 INTERNALDATE representation. 1507*cda5da8dSAndroid Build Coastguard Worker 1508*cda5da8dSAndroid Build Coastguard Worker Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The 1509*cda5da8dSAndroid Build Coastguard Worker date_time argument can be a number (int or float) representing 1510*cda5da8dSAndroid Build Coastguard Worker seconds since epoch (as returned by time.time()), a 9-tuple 1511*cda5da8dSAndroid Build Coastguard Worker representing local time, an instance of time.struct_time (as 1512*cda5da8dSAndroid Build Coastguard Worker returned by time.localtime()), an aware datetime instance or a 1513*cda5da8dSAndroid Build Coastguard Worker double-quoted string. In the last case, it is assumed to already 1514*cda5da8dSAndroid Build Coastguard Worker be in the correct format. 1515*cda5da8dSAndroid Build Coastguard Worker """ 1516*cda5da8dSAndroid Build Coastguard Worker if isinstance(date_time, (int, float)): 1517*cda5da8dSAndroid Build Coastguard Worker dt = datetime.fromtimestamp(date_time, 1518*cda5da8dSAndroid Build Coastguard Worker timezone.utc).astimezone() 1519*cda5da8dSAndroid Build Coastguard Worker elif isinstance(date_time, tuple): 1520*cda5da8dSAndroid Build Coastguard Worker try: 1521*cda5da8dSAndroid Build Coastguard Worker gmtoff = date_time.tm_gmtoff 1522*cda5da8dSAndroid Build Coastguard Worker except AttributeError: 1523*cda5da8dSAndroid Build Coastguard Worker if time.daylight: 1524*cda5da8dSAndroid Build Coastguard Worker dst = date_time[8] 1525*cda5da8dSAndroid Build Coastguard Worker if dst == -1: 1526*cda5da8dSAndroid Build Coastguard Worker dst = time.localtime(time.mktime(date_time))[8] 1527*cda5da8dSAndroid Build Coastguard Worker gmtoff = -(time.timezone, time.altzone)[dst] 1528*cda5da8dSAndroid Build Coastguard Worker else: 1529*cda5da8dSAndroid Build Coastguard Worker gmtoff = -time.timezone 1530*cda5da8dSAndroid Build Coastguard Worker delta = timedelta(seconds=gmtoff) 1531*cda5da8dSAndroid Build Coastguard Worker dt = datetime(*date_time[:6], tzinfo=timezone(delta)) 1532*cda5da8dSAndroid Build Coastguard Worker elif isinstance(date_time, datetime): 1533*cda5da8dSAndroid Build Coastguard Worker if date_time.tzinfo is None: 1534*cda5da8dSAndroid Build Coastguard Worker raise ValueError("date_time must be aware") 1535*cda5da8dSAndroid Build Coastguard Worker dt = date_time 1536*cda5da8dSAndroid Build Coastguard Worker elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'): 1537*cda5da8dSAndroid Build Coastguard Worker return date_time # Assume in correct format 1538*cda5da8dSAndroid Build Coastguard Worker else: 1539*cda5da8dSAndroid Build Coastguard Worker raise ValueError("date_time not of a known type") 1540*cda5da8dSAndroid Build Coastguard Worker fmt = '"%d-{}-%Y %H:%M:%S %z"'.format(Months[dt.month]) 1541*cda5da8dSAndroid Build Coastguard Worker return dt.strftime(fmt) 1542*cda5da8dSAndroid Build Coastguard Worker 1543*cda5da8dSAndroid Build Coastguard Worker 1544*cda5da8dSAndroid Build Coastguard Worker 1545*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__': 1546*cda5da8dSAndroid Build Coastguard Worker 1547*cda5da8dSAndroid Build Coastguard Worker # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]' 1548*cda5da8dSAndroid Build Coastguard Worker # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"' 1549*cda5da8dSAndroid Build Coastguard Worker # to test the IMAP4_stream class 1550*cda5da8dSAndroid Build Coastguard Worker 1551*cda5da8dSAndroid Build Coastguard Worker import getopt, getpass 1552*cda5da8dSAndroid Build Coastguard Worker 1553*cda5da8dSAndroid Build Coastguard Worker try: 1554*cda5da8dSAndroid Build Coastguard Worker optlist, args = getopt.getopt(sys.argv[1:], 'd:s:') 1555*cda5da8dSAndroid Build Coastguard Worker except getopt.error as val: 1556*cda5da8dSAndroid Build Coastguard Worker optlist, args = (), () 1557*cda5da8dSAndroid Build Coastguard Worker 1558*cda5da8dSAndroid Build Coastguard Worker stream_command = None 1559*cda5da8dSAndroid Build Coastguard Worker for opt,val in optlist: 1560*cda5da8dSAndroid Build Coastguard Worker if opt == '-d': 1561*cda5da8dSAndroid Build Coastguard Worker Debug = int(val) 1562*cda5da8dSAndroid Build Coastguard Worker elif opt == '-s': 1563*cda5da8dSAndroid Build Coastguard Worker stream_command = val 1564*cda5da8dSAndroid Build Coastguard Worker if not args: args = (stream_command,) 1565*cda5da8dSAndroid Build Coastguard Worker 1566*cda5da8dSAndroid Build Coastguard Worker if not args: args = ('',) 1567*cda5da8dSAndroid Build Coastguard Worker 1568*cda5da8dSAndroid Build Coastguard Worker host = args[0] 1569*cda5da8dSAndroid Build Coastguard Worker 1570*cda5da8dSAndroid Build Coastguard Worker USER = getpass.getuser() 1571*cda5da8dSAndroid Build Coastguard Worker PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost")) 1572*cda5da8dSAndroid Build Coastguard Worker 1573*cda5da8dSAndroid Build Coastguard Worker test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':'\n'} 1574*cda5da8dSAndroid Build Coastguard Worker test_seq1 = ( 1575*cda5da8dSAndroid Build Coastguard Worker ('login', (USER, PASSWD)), 1576*cda5da8dSAndroid Build Coastguard Worker ('create', ('/tmp/xxx 1',)), 1577*cda5da8dSAndroid Build Coastguard Worker ('rename', ('/tmp/xxx 1', '/tmp/yyy')), 1578*cda5da8dSAndroid Build Coastguard Worker ('CREATE', ('/tmp/yyz 2',)), 1579*cda5da8dSAndroid Build Coastguard Worker ('append', ('/tmp/yyz 2', None, None, test_mesg)), 1580*cda5da8dSAndroid Build Coastguard Worker ('list', ('/tmp', 'yy*')), 1581*cda5da8dSAndroid Build Coastguard Worker ('select', ('/tmp/yyz 2',)), 1582*cda5da8dSAndroid Build Coastguard Worker ('search', (None, 'SUBJECT', 'test')), 1583*cda5da8dSAndroid Build Coastguard Worker ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')), 1584*cda5da8dSAndroid Build Coastguard Worker ('store', ('1', 'FLAGS', r'(\Deleted)')), 1585*cda5da8dSAndroid Build Coastguard Worker ('namespace', ()), 1586*cda5da8dSAndroid Build Coastguard Worker ('expunge', ()), 1587*cda5da8dSAndroid Build Coastguard Worker ('recent', ()), 1588*cda5da8dSAndroid Build Coastguard Worker ('close', ()), 1589*cda5da8dSAndroid Build Coastguard Worker ) 1590*cda5da8dSAndroid Build Coastguard Worker 1591*cda5da8dSAndroid Build Coastguard Worker test_seq2 = ( 1592*cda5da8dSAndroid Build Coastguard Worker ('select', ()), 1593*cda5da8dSAndroid Build Coastguard Worker ('response',('UIDVALIDITY',)), 1594*cda5da8dSAndroid Build Coastguard Worker ('uid', ('SEARCH', 'ALL')), 1595*cda5da8dSAndroid Build Coastguard Worker ('response', ('EXISTS',)), 1596*cda5da8dSAndroid Build Coastguard Worker ('append', (None, None, None, test_mesg)), 1597*cda5da8dSAndroid Build Coastguard Worker ('recent', ()), 1598*cda5da8dSAndroid Build Coastguard Worker ('logout', ()), 1599*cda5da8dSAndroid Build Coastguard Worker ) 1600*cda5da8dSAndroid Build Coastguard Worker 1601*cda5da8dSAndroid Build Coastguard Worker def run(cmd, args): 1602*cda5da8dSAndroid Build Coastguard Worker M._mesg('%s %s' % (cmd, args)) 1603*cda5da8dSAndroid Build Coastguard Worker typ, dat = getattr(M, cmd)(*args) 1604*cda5da8dSAndroid Build Coastguard Worker M._mesg('%s => %s %s' % (cmd, typ, dat)) 1605*cda5da8dSAndroid Build Coastguard Worker if typ == 'NO': raise dat[0] 1606*cda5da8dSAndroid Build Coastguard Worker return dat 1607*cda5da8dSAndroid Build Coastguard Worker 1608*cda5da8dSAndroid Build Coastguard Worker try: 1609*cda5da8dSAndroid Build Coastguard Worker if stream_command: 1610*cda5da8dSAndroid Build Coastguard Worker M = IMAP4_stream(stream_command) 1611*cda5da8dSAndroid Build Coastguard Worker else: 1612*cda5da8dSAndroid Build Coastguard Worker M = IMAP4(host) 1613*cda5da8dSAndroid Build Coastguard Worker if M.state == 'AUTH': 1614*cda5da8dSAndroid Build Coastguard Worker test_seq1 = test_seq1[1:] # Login not needed 1615*cda5da8dSAndroid Build Coastguard Worker M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION) 1616*cda5da8dSAndroid Build Coastguard Worker M._mesg('CAPABILITIES = %r' % (M.capabilities,)) 1617*cda5da8dSAndroid Build Coastguard Worker 1618*cda5da8dSAndroid Build Coastguard Worker for cmd,args in test_seq1: 1619*cda5da8dSAndroid Build Coastguard Worker run(cmd, args) 1620*cda5da8dSAndroid Build Coastguard Worker 1621*cda5da8dSAndroid Build Coastguard Worker for ml in run('list', ('/tmp/', 'yy%')): 1622*cda5da8dSAndroid Build Coastguard Worker mo = re.match(r'.*"([^"]+)"$', ml) 1623*cda5da8dSAndroid Build Coastguard Worker if mo: path = mo.group(1) 1624*cda5da8dSAndroid Build Coastguard Worker else: path = ml.split()[-1] 1625*cda5da8dSAndroid Build Coastguard Worker run('delete', (path,)) 1626*cda5da8dSAndroid Build Coastguard Worker 1627*cda5da8dSAndroid Build Coastguard Worker for cmd,args in test_seq2: 1628*cda5da8dSAndroid Build Coastguard Worker dat = run(cmd, args) 1629*cda5da8dSAndroid Build Coastguard Worker 1630*cda5da8dSAndroid Build Coastguard Worker if (cmd,args) != ('uid', ('SEARCH', 'ALL')): 1631*cda5da8dSAndroid Build Coastguard Worker continue 1632*cda5da8dSAndroid Build Coastguard Worker 1633*cda5da8dSAndroid Build Coastguard Worker uid = dat[-1].split() 1634*cda5da8dSAndroid Build Coastguard Worker if not uid: continue 1635*cda5da8dSAndroid Build Coastguard Worker run('uid', ('FETCH', '%s' % uid[-1], 1636*cda5da8dSAndroid Build Coastguard Worker '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)')) 1637*cda5da8dSAndroid Build Coastguard Worker 1638*cda5da8dSAndroid Build Coastguard Worker print('\nAll tests OK.') 1639*cda5da8dSAndroid Build Coastguard Worker 1640*cda5da8dSAndroid Build Coastguard Worker except: 1641*cda5da8dSAndroid Build Coastguard Worker print('\nTests failed.') 1642*cda5da8dSAndroid Build Coastguard Worker 1643*cda5da8dSAndroid Build Coastguard Worker if not Debug: 1644*cda5da8dSAndroid Build Coastguard Worker print(''' 1645*cda5da8dSAndroid Build Coastguard WorkerIf you would like to see debugging output, 1646*cda5da8dSAndroid Build Coastguard Workertry: %s -d5 1647*cda5da8dSAndroid Build Coastguard Worker''' % sys.argv[0]) 1648*cda5da8dSAndroid Build Coastguard Worker 1649*cda5da8dSAndroid Build Coastguard Worker raise 1650