diff options
Diffstat (limited to 'Lib/imaplib.py')
-rw-r--r-- | Lib/imaplib.py | 118 |
1 files changed, 81 insertions, 37 deletions
diff --git a/Lib/imaplib.py b/Lib/imaplib.py index fc1e20a27d..e2a05818fa 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -22,7 +22,14 @@ Public functions: Internaldate2tuple __version__ = "2.58" -import binascii, errno, random, re, socket, subprocess, sys, time +import binascii, errno, random, re, socket, subprocess, sys, time, calendar +from io import DEFAULT_BUFFER_SIZE + +try: + import ssl + HAVE_SSL = True +except ImportError: + HAVE_SSL = False __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", "Int2AP", "ParseFlags", "Time2Internaldate"] @@ -71,6 +78,7 @@ Commands = { 'SETANNOTATION':('AUTH', 'SELECTED'), 'SETQUOTA': ('AUTH', 'SELECTED'), 'SORT': ('SELECTED',), + 'STARTTLS': ('NONAUTH',), 'STATUS': ('AUTH', 'SELECTED'), 'STORE': ('SELECTED',), 'SUBSCRIBE': ('AUTH', 'SELECTED'), @@ -156,11 +164,23 @@ class IMAP4: self.continuation_response = '' # Last continuation response self.is_readonly = False # READ-ONLY desired state self.tagnum = 0 + self._tls_established = False # Open socket to server. self.open(host, port) + try: + self._connect() + except Exception: + try: + self.shutdown() + except socket.error: + pass + raise + + + def _connect(self): # Create unique tag for this session, # and compile tagged response matcher. @@ -188,13 +208,7 @@ class IMAP4: else: raise self.error(self.welcome) - typ, dat = self.capability() - if dat == [None]: - raise self.error('no CAPABILITY response from server') - dat = str(dat[-1], "ASCII") - dat = dat.upper() - self.capabilities = tuple(dat.split()) - + self._get_capabilities() if __debug__: if self.debug >= 3: self._mesg('CAPABILITIES: %r' % (self.capabilities,)) @@ -347,10 +361,10 @@ class IMAP4: data = authobject(response) - It will be called to process server continuation responses. - It should return data that will be encoded and sent to server. - It should return None if the client abort response '*' should - be sent instead. + It will be called to process server continuation responses; the + response argument it is passed will be a bytes. It should return bytes + data that will be base64 encoded and sent to the server. It should + return None if the client abort response '*' should be sent instead. """ mech = mechanism.upper() # XXX: shouldn't this code be removed, not commented out? @@ -533,7 +547,9 @@ class IMAP4: def _CRAM_MD5_AUTH(self, challenge): """ Authobject to use with CRAM-MD5 authentication. """ import hmac - return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest() + pwd = (self.password.encode('ASCII') if isinstance(self.password, str) + else self.password) + return self.user + " " + hmac.HMAC(pwd, challenge).hexdigest() def logout(self): @@ -711,6 +727,30 @@ class IMAP4: return self._untagged_response(typ, dat, name) + def starttls(self, ssl_context=None): + name = 'STARTTLS' + if not HAVE_SSL: + raise self.error('SSL support missing') + if self._tls_established: + raise self.abort('TLS session already established') + if name not in self.capabilities: + raise self.abort('TLS not supported by server') + # Generate a default SSL context if none was passed. + if ssl_context is None: + ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + # SSLv2 considered harmful. + ssl_context.options |= ssl.OP_NO_SSLv2 + typ, dat = self._simple_command(name) + if typ == 'OK': + self.sock = ssl_context.wrap_socket(self.sock) + self.file = self.sock.makefile('rb') + self._tls_established = True + self._get_capabilities() + else: + raise self.error("Couldn't establish TLS session") + return self._untagged_response(typ, dat, name) + + def status(self, mailbox, names): """Request named status conditions for mailbox. @@ -921,6 +961,15 @@ class IMAP4: return typ, data + def _get_capabilities(self): + typ, dat = self.capability() + if dat == [None]: + raise self.error('no CAPABILITY response from server') + dat = str(dat[-1], "ASCII") + dat = dat.upper() + self.capabilities = tuple(dat.split()) + + def _get_response(self): # Read response and store. @@ -1125,12 +1174,8 @@ class IMAP4: n -= 1 +if HAVE_SSL: -try: - import ssl -except ImportError: - pass -else: class IMAP4_SSL(IMAP4): """IMAP4 client class over SSL connection @@ -1193,6 +1238,7 @@ class IMAP4_stream(IMAP4): self.sock = None self.file = None self.process = subprocess.Popen(self.command, + bufsize=DEFAULT_BUFFER_SIZE, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, close_fds=True) self.writefile = self.process.stdin @@ -1246,14 +1292,16 @@ class _Authenticator: # so when it gets to the end of the 8-bit input # there's no partial 6-bit output. # - oup = '' + oup = b'' + if isinstance(inp, str): + inp = inp.encode('ASCII') while inp: if len(inp) > 48: t = inp[:48] inp = inp[48:] else: t = inp - inp = '' + inp = b'' e = binascii.b2a_base64(t) if e: oup = oup + e[:-1] @@ -1261,7 +1309,7 @@ class _Authenticator: def decode(self, inp): if not inp: - return '' + return b'' return binascii.a2b_base64(inp) @@ -1270,9 +1318,10 @@ Mon2num = {b'Jan': 1, b'Feb': 2, b'Mar': 3, b'Apr': 4, b'May': 5, b'Jun': 6, b'Jul': 7, b'Aug': 8, b'Sep': 9, b'Oct': 10, b'Nov': 11, b'Dec': 12} def Internaldate2tuple(resp): - """Convert IMAP4 INTERNALDATE to UT. + """Parse an IMAP4 INTERNALDATE string. - Returns Python time module tuple. + Return corresponding local time. The return value is a + time.struct_time tuple or None if the string has wrong format. """ mo = InternalDate.match(resp) @@ -1297,19 +1346,9 @@ def Internaldate2tuple(resp): zone = -zone tt = (year, mon, day, hour, min, sec, -1, -1, -1) + utc = calendar.timegm(tt) - zone - utc = time.mktime(tt) - - # Following is necessary because the time module has no 'mkgmtime'. - # 'mktime' assumes arg in local timezone, so adds timezone/altzone. - - lt = time.localtime(utc) - if time.daylight and lt[-1]: - zone = zone + time.altzone - else: - zone = zone + time.timezone - - return time.localtime(utc - zone) + return time.localtime(utc) @@ -1339,9 +1378,14 @@ def ParseFlags(resp): def Time2Internaldate(date_time): - """Convert 'date_time' to IMAP4 INTERNALDATE representation. + """Convert date_time to IMAP4 INTERNALDATE representation. - Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"' + Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The + date_time argument can be a number (int or float) representing + seconds since epoch (as returned by time.time()), a 9-tuple + representing local time (as returned by time.localtime()), or a + double-quoted string. In the last case, it is assumed to already + be in the correct format. """ if isinstance(date_time, (int, float)): |