xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/imaplib.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
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