PyXR

c:\python24\lib \ imaplib.py



0001 """IMAP4 client.
0002 
0003 Based on RFC 2060.
0004 
0005 Public class:           IMAP4
0006 Public variable:        Debug
0007 Public functions:       Internaldate2tuple
0008                         Int2AP
0009                         ParseFlags
0010                         Time2Internaldate
0011 """
0012 
0013 # Author: Piers Lauder <piers@cs.su.oz.au> December 1997.
0014 #
0015 # Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998.
0016 # String method conversion by ESR, February 2001.
0017 # GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001.
0018 # IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
0019 # GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
0020 # PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
0021 
0022 __version__ = "2.55"
0023 
0024 import binascii, os, random, re, socket, sys, time
0025 
0026 __all__ = ["IMAP4", "IMAP4_SSL", "IMAP4_stream", "Internaldate2tuple",
0027            "Int2AP", "ParseFlags", "Time2Internaldate"]
0028 
0029 #       Globals
0030 
0031 CRLF = '\r\n'
0032 Debug = 0
0033 IMAP4_PORT = 143
0034 IMAP4_SSL_PORT = 993
0035 AllowedVersions = ('IMAP4REV1', 'IMAP4')        # Most recent first
0036 
0037 #       Commands
0038 
0039 Commands = {
0040         # name            valid states
0041         'APPEND':       ('AUTH', 'SELECTED'),
0042         'AUTHENTICATE': ('NONAUTH',),
0043         'CAPABILITY':   ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
0044         'CHECK':        ('SELECTED',),
0045         'CLOSE':        ('SELECTED',),
0046         'COPY':         ('SELECTED',),
0047         'CREATE':       ('AUTH', 'SELECTED'),
0048         'DELETE':       ('AUTH', 'SELECTED'),
0049         'DELETEACL':    ('AUTH', 'SELECTED'),
0050         'EXAMINE':      ('AUTH', 'SELECTED'),
0051         'EXPUNGE':      ('SELECTED',),
0052         'FETCH':        ('SELECTED',),
0053         'GETACL':       ('AUTH', 'SELECTED'),
0054         'GETQUOTA':     ('AUTH', 'SELECTED'),
0055         'GETQUOTAROOT': ('AUTH', 'SELECTED'),
0056         'MYRIGHTS':     ('AUTH', 'SELECTED'),
0057         'LIST':         ('AUTH', 'SELECTED'),
0058         'LOGIN':        ('NONAUTH',),
0059         'LOGOUT':       ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
0060         'LSUB':         ('AUTH', 'SELECTED'),
0061         'NAMESPACE':    ('AUTH', 'SELECTED'),
0062         'NOOP':         ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
0063         'PARTIAL':      ('SELECTED',),                                  # NB: obsolete
0064         'PROXYAUTH':    ('AUTH',),
0065         'RENAME':       ('AUTH', 'SELECTED'),
0066         'SEARCH':       ('SELECTED',),
0067         'SELECT':       ('AUTH', 'SELECTED'),
0068         'SETACL':       ('AUTH', 'SELECTED'),
0069         'SETQUOTA':     ('AUTH', 'SELECTED'),
0070         'SORT':         ('SELECTED',),
0071         'STATUS':       ('AUTH', 'SELECTED'),
0072         'STORE':        ('SELECTED',),
0073         'SUBSCRIBE':    ('AUTH', 'SELECTED'),
0074         'THREAD':       ('SELECTED',),
0075         'UID':          ('SELECTED',),
0076         'UNSUBSCRIBE':  ('AUTH', 'SELECTED'),
0077         }
0078 
0079 #       Patterns to match server responses
0080 
0081 Continuation = re.compile(r'\+( (?P<data>.*))?')
0082 Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)')
0083 InternalDate = re.compile(r'.*INTERNALDATE "'
0084         r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
0085         r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
0086         r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
0087         r'"')
0088 Literal = re.compile(r'.*{(?P<size>\d+)}$')
0089 MapCRLF = re.compile(r'\r\n|\r|\n')
0090 Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]')
0091 Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
0092 Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?')
0093 
0094 
0095 
0096 class IMAP4:
0097 
0098     """IMAP4 client class.
0099 
0100     Instantiate with: IMAP4([host[, port]])
0101 
0102             host - host's name (default: localhost);
0103             port - port number (default: standard IMAP4 port).
0104 
0105     All IMAP4rev1 commands are supported by methods of the same
0106     name (in lower-case).
0107 
0108     All arguments to commands are converted to strings, except for
0109     AUTHENTICATE, and the last argument to APPEND which is passed as
0110     an IMAP4 literal.  If necessary (the string contains any
0111     non-printing characters or white-space and isn't enclosed with
0112     either parentheses or double quotes) each string is quoted.
0113     However, the 'password' argument to the LOGIN command is always
0114     quoted.  If you want to avoid having an argument string quoted
0115     (eg: the 'flags' argument to STORE) then enclose the string in
0116     parentheses (eg: "(\Deleted)").
0117 
0118     Each command returns a tuple: (type, [data, ...]) where 'type'
0119     is usually 'OK' or 'NO', and 'data' is either the text from the
0120     tagged response, or untagged results from command. Each 'data'
0121     is either a string, or a tuple. If a tuple, then the first part
0122     is the header of the response, and the second part contains
0123     the data (ie: 'literal' value).
0124 
0125     Errors raise the exception class <instance>.error("<reason>").
0126     IMAP4 server errors raise <instance>.abort("<reason>"),
0127     which is a sub-class of 'error'. Mailbox status changes
0128     from READ-WRITE to READ-ONLY raise the exception class
0129     <instance>.readonly("<reason>"), which is a sub-class of 'abort'.
0130 
0131     "error" exceptions imply a program error.
0132     "abort" exceptions imply the connection should be reset, and
0133             the command re-tried.
0134     "readonly" exceptions imply the command should be re-tried.
0135 
0136     Note: to use this module, you must read the RFCs pertaining
0137     to the IMAP4 protocol, as the semantics of the arguments to
0138     each IMAP4 command are left to the invoker, not to mention
0139     the results.
0140     """
0141 
0142     class error(Exception): pass    # Logical errors - debug required
0143     class abort(error): pass        # Service errors - close and retry
0144     class readonly(abort): pass     # Mailbox status changed to READ-ONLY
0145 
0146     mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]")
0147 
0148     def __init__(self, host = '', port = IMAP4_PORT):
0149         self.debug = Debug
0150         self.state = 'LOGOUT'
0151         self.literal = None             # A literal argument to a command
0152         self.tagged_commands = {}       # Tagged commands awaiting response
0153         self.untagged_responses = {}    # {typ: [data, ...], ...}
0154         self.continuation_response = '' # Last continuation response
0155         self.is_readonly = None         # READ-ONLY desired state
0156         self.tagnum = 0
0157 
0158         # Open socket to server.
0159 
0160         self.open(host, port)
0161 
0162         # Create unique tag for this session,
0163         # and compile tagged response matcher.
0164 
0165         self.tagpre = Int2AP(random.randint(0, 31999))
0166         self.tagre = re.compile(r'(?P<tag>'
0167                         + self.tagpre
0168                         + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)')
0169 
0170         # Get server welcome message,
0171         # request and store CAPABILITY response.
0172 
0173         if __debug__:
0174             self._cmd_log_len = 10
0175             self._cmd_log_idx = 0
0176             self._cmd_log = {}           # Last `_cmd_log_len' interactions
0177             if self.debug >= 1:
0178                 self._mesg('imaplib version %s' % __version__)
0179                 self._mesg('new IMAP4 connection, tag=%s' % self.tagpre)
0180 
0181         self.welcome = self._get_response()
0182         if 'PREAUTH' in self.untagged_responses:
0183             self.state = 'AUTH'
0184         elif 'OK' in self.untagged_responses:
0185             self.state = 'NONAUTH'
0186         else:
0187             raise self.error(self.welcome)
0188 
0189         cap = 'CAPABILITY'
0190         self._simple_command(cap)
0191         if not cap in self.untagged_responses:
0192             raise self.error('no CAPABILITY response from server')
0193         self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split())
0194 
0195         if __debug__:
0196             if self.debug >= 3:
0197                 self._mesg('CAPABILITIES: %r' % (self.capabilities,))
0198 
0199         for version in AllowedVersions:
0200             if not version in self.capabilities:
0201                 continue
0202             self.PROTOCOL_VERSION = version
0203             return
0204 
0205         raise self.error('server not IMAP4 compliant')
0206 
0207 
0208     def __getattr__(self, attr):
0209         #       Allow UPPERCASE variants of IMAP4 command methods.
0210         if attr in Commands:
0211             return getattr(self, attr.lower())
0212         raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
0213 
0214 
0215 
0216     #       Overridable methods
0217 
0218 
0219     def open(self, host = '', port = IMAP4_PORT):
0220         """Setup connection to remote server on "host:port"
0221             (default: localhost:standard IMAP4 port).
0222         This connection will be used by the routines:
0223             read, readline, send, shutdown.
0224         """
0225         self.host = host
0226         self.port = port
0227         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
0228         self.sock.connect((host, port))
0229         self.file = self.sock.makefile('rb')
0230 
0231 
0232     def read(self, size):
0233         """Read 'size' bytes from remote."""
0234         return self.file.read(size)
0235 
0236 
0237     def readline(self):
0238         """Read line from remote."""
0239         return self.file.readline()
0240 
0241 
0242     def send(self, data):
0243         """Send data to remote."""
0244         self.sock.sendall(data)
0245 
0246 
0247     def shutdown(self):
0248         """Close I/O established in "open"."""
0249         self.file.close()
0250         self.sock.close()
0251 
0252 
0253     def socket(self):
0254         """Return socket instance used to connect to IMAP4 server.
0255 
0256         socket = <instance>.socket()
0257         """
0258         return self.sock
0259 
0260 
0261 
0262     #       Utility methods
0263 
0264 
0265     def recent(self):
0266         """Return most recent 'RECENT' responses if any exist,
0267         else prompt server for an update using the 'NOOP' command.
0268 
0269         (typ, [data]) = <instance>.recent()
0270 
0271         'data' is None if no new messages,
0272         else list of RECENT responses, most recent last.
0273         """
0274         name = 'RECENT'
0275         typ, dat = self._untagged_response('OK', [None], name)
0276         if dat[-1]:
0277             return typ, dat
0278         typ, dat = self.noop()  # Prod server for response
0279         return self._untagged_response(typ, dat, name)
0280 
0281 
0282     def response(self, code):
0283         """Return data for response 'code' if received, or None.
0284 
0285         Old value for response 'code' is cleared.
0286 
0287         (code, [data]) = <instance>.response(code)
0288         """
0289         return self._untagged_response(code, [None], code.upper())
0290 
0291 
0292 
0293     #       IMAP4 commands
0294 
0295 
0296     def append(self, mailbox, flags, date_time, message):
0297         """Append message to named mailbox.
0298 
0299         (typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
0300 
0301                 All args except `message' can be None.
0302         """
0303         name = 'APPEND'
0304         if not mailbox:
0305             mailbox = 'INBOX'
0306         if flags:
0307             if (flags[0],flags[-1]) != ('(',')'):
0308                 flags = '(%s)' % flags
0309         else:
0310             flags = None
0311         if date_time:
0312             date_time = Time2Internaldate(date_time)
0313         else:
0314             date_time = None
0315         self.literal = MapCRLF.sub(CRLF, message)
0316         return self._simple_command(name, mailbox, flags, date_time)
0317 
0318 
0319     def authenticate(self, mechanism, authobject):
0320         """Authenticate command - requires response processing.
0321 
0322         'mechanism' specifies which authentication mechanism is to
0323         be used - it must appear in <instance>.capabilities in the
0324         form AUTH=<mechanism>.
0325 
0326         'authobject' must be a callable object:
0327 
0328                 data = authobject(response)
0329 
0330         It will be called to process server continuation responses.
0331         It should return data that will be encoded and sent to server.
0332         It should return None if the client abort response '*' should
0333         be sent instead.
0334         """
0335         mech = mechanism.upper()
0336         # XXX: shouldn't this code be removed, not commented out?
0337         #cap = 'AUTH=%s' % mech
0338         #if not cap in self.capabilities:       # Let the server decide!
0339         #    raise self.error("Server doesn't allow %s authentication." % mech)
0340         self.literal = _Authenticator(authobject).process
0341         typ, dat = self._simple_command('AUTHENTICATE', mech)
0342         if typ != 'OK':
0343             raise self.error(dat[-1])
0344         self.state = 'AUTH'
0345         return typ, dat
0346 
0347 
0348     def check(self):
0349         """Checkpoint mailbox on server.
0350 
0351         (typ, [data]) = <instance>.check()
0352         """
0353         return self._simple_command('CHECK')
0354 
0355 
0356     def close(self):
0357         """Close currently selected mailbox.
0358 
0359         Deleted messages are removed from writable mailbox.
0360         This is the recommended command before 'LOGOUT'.
0361 
0362         (typ, [data]) = <instance>.close()
0363         """
0364         try:
0365             typ, dat = self._simple_command('CLOSE')
0366         finally:
0367             self.state = 'AUTH'
0368         return typ, dat
0369 
0370 
0371     def copy(self, message_set, new_mailbox):
0372         """Copy 'message_set' messages onto end of 'new_mailbox'.
0373 
0374         (typ, [data]) = <instance>.copy(message_set, new_mailbox)
0375         """
0376         return self._simple_command('COPY', message_set, new_mailbox)
0377 
0378 
0379     def create(self, mailbox):
0380         """Create new mailbox.
0381 
0382         (typ, [data]) = <instance>.create(mailbox)
0383         """
0384         return self._simple_command('CREATE', mailbox)
0385 
0386 
0387     def delete(self, mailbox):
0388         """Delete old mailbox.
0389 
0390         (typ, [data]) = <instance>.delete(mailbox)
0391         """
0392         return self._simple_command('DELETE', mailbox)
0393 
0394     def deleteacl(self, mailbox, who):
0395         """Delete the ACLs (remove any rights) set for who on mailbox.
0396 
0397         (typ, [data]) = <instance>.deleteacl(mailbox, who)
0398         """
0399         return self._simple_command('DELETEACL', mailbox, who)
0400 
0401     def expunge(self):
0402         """Permanently remove deleted items from selected mailbox.
0403 
0404         Generates 'EXPUNGE' response for each deleted message.
0405 
0406         (typ, [data]) = <instance>.expunge()
0407 
0408         'data' is list of 'EXPUNGE'd message numbers in order received.
0409         """
0410         name = 'EXPUNGE'
0411         typ, dat = self._simple_command(name)
0412         return self._untagged_response(typ, dat, name)
0413 
0414 
0415     def fetch(self, message_set, message_parts):
0416         """Fetch (parts of) messages.
0417 
0418         (typ, [data, ...]) = <instance>.fetch(message_set, message_parts)
0419 
0420         'message_parts' should be a string of selected parts
0421         enclosed in parentheses, eg: "(UID BODY[TEXT])".
0422 
0423         'data' are tuples of message part envelope and data.
0424         """
0425         name = 'FETCH'
0426         typ, dat = self._simple_command(name, message_set, message_parts)
0427         return self._untagged_response(typ, dat, name)
0428 
0429 
0430     def getacl(self, mailbox):
0431         """Get the ACLs for a mailbox.
0432 
0433         (typ, [data]) = <instance>.getacl(mailbox)
0434         """
0435         typ, dat = self._simple_command('GETACL', mailbox)
0436         return self._untagged_response(typ, dat, 'ACL')
0437 
0438 
0439     def getquota(self, root):
0440         """Get the quota root's resource usage and limits.
0441 
0442         Part of the IMAP4 QUOTA extension defined in rfc2087.
0443 
0444         (typ, [data]) = <instance>.getquota(root)
0445         """
0446         typ, dat = self._simple_command('GETQUOTA', root)
0447         return self._untagged_response(typ, dat, 'QUOTA')
0448 
0449 
0450     def getquotaroot(self, mailbox):
0451         """Get the list of quota roots for the named mailbox.
0452 
0453         (typ, [[QUOTAROOT responses...], [QUOTA responses]]) = <instance>.getquotaroot(mailbox)
0454         """
0455         typ, dat = self._simple_command('GETQUOTAROOT', mailbox)
0456         typ, quota = self._untagged_response(typ, dat, 'QUOTA')
0457         typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT')
0458         return typ, [quotaroot, quota]
0459 
0460 
0461     def list(self, directory='""', pattern='*'):
0462         """List mailbox names in directory matching pattern.
0463 
0464         (typ, [data]) = <instance>.list(directory='""', pattern='*')
0465 
0466         'data' is list of LIST responses.
0467         """
0468         name = 'LIST'
0469         typ, dat = self._simple_command(name, directory, pattern)
0470         return self._untagged_response(typ, dat, name)
0471 
0472 
0473     def login(self, user, password):
0474         """Identify client using plaintext password.
0475 
0476         (typ, [data]) = <instance>.login(user, password)
0477 
0478         NB: 'password' will be quoted.
0479         """
0480         typ, dat = self._simple_command('LOGIN', user, self._quote(password))
0481         if typ != 'OK':
0482             raise self.error(dat[-1])
0483         self.state = 'AUTH'
0484         return typ, dat
0485 
0486 
0487     def login_cram_md5(self, user, password):
0488         """ Force use of CRAM-MD5 authentication.
0489 
0490         (typ, [data]) = <instance>.login_cram_md5(user, password)
0491         """
0492         self.user, self.password = user, password
0493         return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
0494 
0495 
0496     def _CRAM_MD5_AUTH(self, challenge):
0497         """ Authobject to use with CRAM-MD5 authentication. """
0498         import hmac
0499         return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest()
0500 
0501 
0502     def logout(self):
0503         """Shutdown connection to server.
0504 
0505         (typ, [data]) = <instance>.logout()
0506 
0507         Returns server 'BYE' response.
0508         """
0509         self.state = 'LOGOUT'
0510         try: typ, dat = self._simple_command('LOGOUT')
0511         except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
0512         self.shutdown()
0513         if 'BYE' in self.untagged_responses:
0514             return 'BYE', self.untagged_responses['BYE']
0515         return typ, dat
0516 
0517 
0518     def lsub(self, directory='""', pattern='*'):
0519         """List 'subscribed' mailbox names in directory matching pattern.
0520 
0521         (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*')
0522 
0523         'data' are tuples of message part envelope and data.
0524         """
0525         name = 'LSUB'
0526         typ, dat = self._simple_command(name, directory, pattern)
0527         return self._untagged_response(typ, dat, name)
0528 
0529     def myrights(self, mailbox):
0530         """Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).
0531 
0532         (typ, [data]) = <instance>.myrights(mailbox)
0533         """
0534         typ,dat = self._simple_command('MYRIGHTS', mailbox)
0535         return self._untagged_response(typ, dat, 'MYRIGHTS')
0536 
0537     def namespace(self):
0538         """ Returns IMAP namespaces ala rfc2342
0539 
0540         (typ, [data, ...]) = <instance>.namespace()
0541         """
0542         name = 'NAMESPACE'
0543         typ, dat = self._simple_command(name)
0544         return self._untagged_response(typ, dat, name)
0545 
0546 
0547     def noop(self):
0548         """Send NOOP command.
0549 
0550         (typ, [data]) = <instance>.noop()
0551         """
0552         if __debug__:
0553             if self.debug >= 3:
0554                 self._dump_ur(self.untagged_responses)
0555         return self._simple_command('NOOP')
0556 
0557 
0558     def partial(self, message_num, message_part, start, length):
0559         """Fetch truncated part of a message.
0560 
0561         (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length)
0562 
0563         'data' is tuple of message part envelope and data.
0564         """
0565         name = 'PARTIAL'
0566         typ, dat = self._simple_command(name, message_num, message_part, start, length)
0567         return self._untagged_response(typ, dat, 'FETCH')
0568 
0569 
0570     def proxyauth(self, user):
0571         """Assume authentication as "user".
0572 
0573         Allows an authorised administrator to proxy into any user's
0574         mailbox.
0575 
0576         (typ, [data]) = <instance>.proxyauth(user)
0577         """
0578 
0579         name = 'PROXYAUTH'
0580         return self._simple_command('PROXYAUTH', user)
0581 
0582 
0583     def rename(self, oldmailbox, newmailbox):
0584         """Rename old mailbox name to new.
0585 
0586         (typ, [data]) = <instance>.rename(oldmailbox, newmailbox)
0587         """
0588         return self._simple_command('RENAME', oldmailbox, newmailbox)
0589 
0590 
0591     def search(self, charset, *criteria):
0592         """Search mailbox for matching messages.
0593 
0594         (typ, [data]) = <instance>.search(charset, criterion, ...)
0595 
0596         'data' is space separated list of matching message numbers.
0597         """
0598         name = 'SEARCH'
0599         if charset:
0600             typ, dat = self._simple_command(name, 'CHARSET', charset, *criteria)
0601         else:
0602             typ, dat = self._simple_command(name, *criteria)
0603         return self._untagged_response(typ, dat, name)
0604 
0605 
0606     def select(self, mailbox='INBOX', readonly=None):
0607         """Select a mailbox.
0608 
0609         Flush all untagged responses.
0610 
0611         (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None)
0612 
0613         'data' is count of messages in mailbox ('EXISTS' response).
0614 
0615         Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so
0616         other responses should be obtained via <instance>.response('FLAGS') etc.
0617         """
0618         self.untagged_responses = {}    # Flush old responses.
0619         self.is_readonly = readonly
0620         if readonly is not None:
0621             name = 'EXAMINE'
0622         else:
0623             name = 'SELECT'
0624         typ, dat = self._simple_command(name, mailbox)
0625         if typ != 'OK':
0626             self.state = 'AUTH'     # Might have been 'SELECTED'
0627             return typ, dat
0628         self.state = 'SELECTED'
0629         if 'READ-ONLY' in self.untagged_responses \
0630                 and not readonly:
0631             if __debug__:
0632                 if self.debug >= 1:
0633                     self._dump_ur(self.untagged_responses)
0634             raise self.readonly('%s is not writable' % mailbox)
0635         return typ, self.untagged_responses.get('EXISTS', [None])
0636 
0637 
0638     def setacl(self, mailbox, who, what):
0639         """Set a mailbox acl.
0640 
0641         (typ, [data]) = <instance>.setacl(mailbox, who, what)
0642         """
0643         return self._simple_command('SETACL', mailbox, who, what)
0644 
0645 
0646     def setquota(self, root, limits):
0647         """Set the quota root's resource limits.
0648 
0649         (typ, [data]) = <instance>.setquota(root, limits)
0650         """
0651         typ, dat = self._simple_command('SETQUOTA', root, limits)
0652         return self._untagged_response(typ, dat, 'QUOTA')
0653 
0654 
0655     def sort(self, sort_criteria, charset, *search_criteria):
0656         """IMAP4rev1 extension SORT command.
0657 
0658         (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...)
0659         """
0660         name = 'SORT'
0661         #if not name in self.capabilities:      # Let the server decide!
0662         #       raise self.error('unimplemented extension command: %s' % name)
0663         if (sort_criteria[0],sort_criteria[-1]) != ('(',')'):
0664             sort_criteria = '(%s)' % sort_criteria
0665         typ, dat = self._simple_command(name, sort_criteria, charset, *search_criteria)
0666         return self._untagged_response(typ, dat, name)
0667 
0668 
0669     def status(self, mailbox, names):
0670         """Request named status conditions for mailbox.
0671 
0672         (typ, [data]) = <instance>.status(mailbox, names)
0673         """
0674         name = 'STATUS'
0675         #if self.PROTOCOL_VERSION == 'IMAP4':   # Let the server decide!
0676         #    raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name)
0677         typ, dat = self._simple_command(name, mailbox, names)
0678         return self._untagged_response(typ, dat, name)
0679 
0680 
0681     def store(self, message_set, command, flags):
0682         """Alters flag dispositions for messages in mailbox.
0683 
0684         (typ, [data]) = <instance>.store(message_set, command, flags)
0685         """
0686         if (flags[0],flags[-1]) != ('(',')'):
0687             flags = '(%s)' % flags  # Avoid quoting the flags
0688         typ, dat = self._simple_command('STORE', message_set, command, flags)
0689         return self._untagged_response(typ, dat, 'FETCH')
0690 
0691 
0692     def subscribe(self, mailbox):
0693         """Subscribe to new mailbox.
0694 
0695         (typ, [data]) = <instance>.subscribe(mailbox)
0696         """
0697         return self._simple_command('SUBSCRIBE', mailbox)
0698 
0699 
0700     def thread(self, threading_algorithm, charset, *search_criteria):
0701         """IMAPrev1 extension THREAD command.
0702 
0703         (type, [data]) = <instance>.thread(threading_alogrithm, charset, search_criteria, ...)
0704         """
0705         name = 'THREAD'
0706         typ, dat = self._simple_command(name, threading_algorithm, charset, *search_criteria)
0707         return self._untagged_response(typ, dat, name)
0708 
0709 
0710     def uid(self, command, *args):
0711         """Execute "command arg ..." with messages identified by UID,
0712                 rather than message number.
0713 
0714         (typ, [data]) = <instance>.uid(command, arg1, arg2, ...)
0715 
0716         Returns response appropriate to 'command'.
0717         """
0718         command = command.upper()
0719         if not command in Commands:
0720             raise self.error("Unknown IMAP4 UID command: %s" % command)
0721         if self.state not in Commands[command]:
0722             raise self.error('command %s illegal in state %s'
0723                                     % (command, self.state))
0724         name = 'UID'
0725         typ, dat = self._simple_command(name, command, *args)
0726         if command in ('SEARCH', 'SORT'):
0727             name = command
0728         else:
0729             name = 'FETCH'
0730         return self._untagged_response(typ, dat, name)
0731 
0732 
0733     def unsubscribe(self, mailbox):
0734         """Unsubscribe from old mailbox.
0735 
0736         (typ, [data]) = <instance>.unsubscribe(mailbox)
0737         """
0738         return self._simple_command('UNSUBSCRIBE', mailbox)
0739 
0740 
0741     def xatom(self, name, *args):
0742         """Allow simple extension commands
0743                 notified by server in CAPABILITY response.
0744 
0745         Assumes command is legal in current state.
0746 
0747         (typ, [data]) = <instance>.xatom(name, arg, ...)
0748 
0749         Returns response appropriate to extension command `name'.
0750         """
0751         name = name.upper()
0752         #if not name in self.capabilities:      # Let the server decide!
0753         #    raise self.error('unknown extension command: %s' % name)
0754         if not name in Commands:
0755             Commands[name] = (self.state,)
0756         return self._simple_command(name, *args)
0757 
0758 
0759 
0760     #       Private methods
0761 
0762 
0763     def _append_untagged(self, typ, dat):
0764 
0765         if dat is None: dat = ''
0766         ur = self.untagged_responses
0767         if __debug__:
0768             if self.debug >= 5:
0769                 self._mesg('untagged_responses[%s] %s += ["%s"]' %
0770                         (typ, len(ur.get(typ,'')), dat))
0771         if typ in ur:
0772             ur[typ].append(dat)
0773         else:
0774             ur[typ] = [dat]
0775 
0776 
0777     def _check_bye(self):
0778         bye = self.untagged_responses.get('BYE')
0779         if bye:
0780             raise self.abort(bye[-1])
0781 
0782 
0783     def _command(self, name, *args):
0784 
0785         if self.state not in Commands[name]:
0786             self.literal = None
0787             raise self.error(
0788             'command %s illegal in state %s' % (name, self.state))
0789 
0790         for typ in ('OK', 'NO', 'BAD'):
0791             if typ in self.untagged_responses:
0792                 del self.untagged_responses[typ]
0793 
0794         if 'READ-ONLY' in self.untagged_responses \
0795         and not self.is_readonly:
0796             raise self.readonly('mailbox status changed to READ-ONLY')
0797 
0798         tag = self._new_tag()
0799         data = '%s %s' % (tag, name)
0800         for arg in args:
0801             if arg is None: continue
0802             data = '%s %s' % (data, self._checkquote(arg))
0803 
0804         literal = self.literal
0805         if literal is not None:
0806             self.literal = None
0807             if type(literal) is type(self._command):
0808                 literator = literal
0809             else:
0810                 literator = None
0811                 data = '%s {%s}' % (data, len(literal))
0812 
0813         if __debug__:
0814             if self.debug >= 4:
0815                 self._mesg('> %s' % data)
0816             else:
0817                 self._log('> %s' % data)
0818 
0819         try:
0820             self.send('%s%s' % (data, CRLF))
0821         except (socket.error, OSError), val:
0822             raise self.abort('socket error: %s' % val)
0823 
0824         if literal is None:
0825             return tag
0826 
0827         while 1:
0828             # Wait for continuation response
0829 
0830             while self._get_response():
0831                 if self.tagged_commands[tag]:   # BAD/NO?
0832                     return tag
0833 
0834             # Send literal
0835 
0836             if literator:
0837                 literal = literator(self.continuation_response)
0838 
0839             if __debug__:
0840                 if self.debug >= 4:
0841                     self._mesg('write literal size %s' % len(literal))
0842 
0843             try:
0844                 self.send(literal)
0845                 self.send(CRLF)
0846             except (socket.error, OSError), val:
0847                 raise self.abort('socket error: %s' % val)
0848 
0849             if not literator:
0850                 break
0851 
0852         return tag
0853 
0854 
0855     def _command_complete(self, name, tag):
0856         self._check_bye()
0857         try:
0858             typ, data = self._get_tagged_response(tag)
0859         except self.abort, val:
0860             raise self.abort('command: %s => %s' % (name, val))
0861         except self.error, val:
0862             raise self.error('command: %s => %s' % (name, val))
0863         self._check_bye()
0864         if typ == 'BAD':
0865             raise self.error('%s command error: %s %s' % (name, typ, data))
0866         return typ, data
0867 
0868 
0869     def _get_response(self):
0870 
0871         # Read response and store.
0872         #
0873         # Returns None for continuation responses,
0874         # otherwise first response line received.
0875 
0876         resp = self._get_line()
0877 
0878         # Command completion response?
0879 
0880         if self._match(self.tagre, resp):
0881             tag = self.mo.group('tag')
0882             if not tag in self.tagged_commands:
0883                 raise self.abort('unexpected tagged response: %s' % resp)
0884 
0885             typ = self.mo.group('type')
0886             dat = self.mo.group('data')
0887             self.tagged_commands[tag] = (typ, [dat])
0888         else:
0889             dat2 = None
0890 
0891             # '*' (untagged) responses?
0892 
0893             if not self._match(Untagged_response, resp):
0894                 if self._match(Untagged_status, resp):
0895                     dat2 = self.mo.group('data2')
0896 
0897             if self.mo is None:
0898                 # Only other possibility is '+' (continuation) response...
0899 
0900                 if self._match(Continuation, resp):
0901                     self.continuation_response = self.mo.group('data')
0902                     return None     # NB: indicates continuation
0903 
0904                 raise self.abort("unexpected response: '%s'" % resp)
0905 
0906             typ = self.mo.group('type')
0907             dat = self.mo.group('data')
0908             if dat is None: dat = ''        # Null untagged response
0909             if dat2: dat = dat + ' ' + dat2
0910 
0911             # Is there a literal to come?
0912 
0913             while self._match(Literal, dat):
0914 
0915                 # Read literal direct from connection.
0916 
0917                 size = int(self.mo.group('size'))
0918                 if __debug__:
0919                     if self.debug >= 4:
0920                         self._mesg('read literal size %s' % size)
0921                 data = self.read(size)
0922 
0923                 # Store response with literal as tuple
0924 
0925                 self._append_untagged(typ, (dat, data))
0926 
0927                 # Read trailer - possibly containing another literal
0928 
0929                 dat = self._get_line()
0930 
0931             self._append_untagged(typ, dat)
0932 
0933         # Bracketed response information?
0934 
0935         if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat):
0936             self._append_untagged(self.mo.group('type'), self.mo.group('data'))
0937 
0938         if __debug__:
0939             if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'):
0940                 self._mesg('%s response: %s' % (typ, dat))
0941 
0942         return resp
0943 
0944 
0945     def _get_tagged_response(self, tag):
0946 
0947         while 1:
0948             result = self.tagged_commands[tag]
0949             if result is not None:
0950                 del self.tagged_commands[tag]
0951                 return result
0952 
0953             # Some have reported "unexpected response" exceptions.
0954             # Note that ignoring them here causes loops.
0955             # Instead, send me details of the unexpected response and
0956             # I'll update the code in `_get_response()'.
0957 
0958             try:
0959                 self._get_response()
0960             except self.abort, val:
0961                 if __debug__:
0962                     if self.debug >= 1:
0963                         self.print_log()
0964                 raise
0965 
0966 
0967     def _get_line(self):
0968 
0969         line = self.readline()
0970         if not line:
0971             raise self.abort('socket error: EOF')
0972 
0973         # Protocol mandates all lines terminated by CRLF
0974 
0975         line = line[:-2]
0976         if __debug__:
0977             if self.debug >= 4:
0978                 self._mesg('< %s' % line)
0979             else:
0980                 self._log('< %s' % line)
0981         return line
0982 
0983 
0984     def _match(self, cre, s):
0985 
0986         # Run compiled regular expression match method on 's'.
0987         # Save result, return success.
0988 
0989         self.mo = cre.match(s)
0990         if __debug__:
0991             if self.mo is not None and self.debug >= 5:
0992                 self._mesg("\tmatched r'%s' => %r" % (cre.pattern, self.mo.groups()))
0993         return self.mo is not None
0994 
0995 
0996     def _new_tag(self):
0997 
0998         tag = '%s%s' % (self.tagpre, self.tagnum)
0999         self.tagnum = self.tagnum + 1
1000         self.tagged_commands[tag] = None
1001         return tag
1002 
1003 
1004     def _checkquote(self, arg):
1005 
1006         # Must quote command args if non-alphanumeric chars present,
1007         # and not already quoted.
1008 
1009         if type(arg) is not type(''):
1010             return arg
1011         if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')):
1012             return arg
1013         if arg and self.mustquote.search(arg) is None:
1014             return arg
1015         return self._quote(arg)
1016 
1017 
1018     def _quote(self, arg):
1019 
1020         arg = arg.replace('\\', '\\\\')
1021         arg = arg.replace('"', '\\"')
1022 
1023         return '"%s"' % arg
1024 
1025 
1026     def _simple_command(self, name, *args):
1027 
1028         return self._command_complete(name, self._command(name, *args))
1029 
1030 
1031     def _untagged_response(self, typ, dat, name):
1032 
1033         if typ == 'NO':
1034             return typ, dat
1035         if not name in self.untagged_responses:
1036             return typ, [None]
1037         data = self.untagged_responses.pop(name)
1038         if __debug__:
1039             if self.debug >= 5:
1040                 self._mesg('untagged_responses[%s] => %s' % (name, data))
1041         return typ, data
1042 
1043 
1044     if __debug__:
1045 
1046         def _mesg(self, s, secs=None):
1047             if secs is None:
1048                 secs = time.time()
1049             tm = time.strftime('%M:%S', time.localtime(secs))
1050             sys.stderr.write('  %s.%02d %s\n' % (tm, (secs*100)%100, s))
1051             sys.stderr.flush()
1052 
1053         def _dump_ur(self, dict):
1054             # Dump untagged responses (in `dict').
1055             l = dict.items()
1056             if not l: return
1057             t = '\n\t\t'
1058             l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l)
1059             self._mesg('untagged responses dump:%s%s' % (t, t.join(l)))
1060 
1061         def _log(self, line):
1062             # Keep log of last `_cmd_log_len' interactions for debugging.
1063             self._cmd_log[self._cmd_log_idx] = (line, time.time())
1064             self._cmd_log_idx += 1
1065             if self._cmd_log_idx >= self._cmd_log_len:
1066                 self._cmd_log_idx = 0
1067 
1068         def print_log(self):
1069             self._mesg('last %d IMAP4 interactions:' % len(self._cmd_log))
1070             i, n = self._cmd_log_idx, self._cmd_log_len
1071             while n:
1072                 try:
1073                     self._mesg(*self._cmd_log[i])
1074                 except:
1075                     pass
1076                 i += 1
1077                 if i >= self._cmd_log_len:
1078                     i = 0
1079                 n -= 1
1080 
1081 
1082 
1083 class IMAP4_SSL(IMAP4):
1084 
1085     """IMAP4 client class over SSL connection
1086 
1087     Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]])
1088 
1089             host - host's name (default: localhost);
1090             port - port number (default: standard IMAP4 SSL port).
1091             keyfile - PEM formatted file that contains your private key (default: None);
1092             certfile - PEM formatted certificate chain file (default: None);
1093 
1094     for more documentation see the docstring of the parent class IMAP4.
1095     """
1096 
1097 
1098     def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None):
1099         self.keyfile = keyfile
1100         self.certfile = certfile
1101         IMAP4.__init__(self, host, port)
1102 
1103 
1104     def open(self, host = '', port = IMAP4_SSL_PORT):
1105         """Setup connection to remote server on "host:port".
1106             (default: localhost:standard IMAP4 SSL port).
1107         This connection will be used by the routines:
1108             read, readline, send, shutdown.
1109         """
1110         self.host = host
1111         self.port = port
1112         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1113         self.sock.connect((host, port))
1114         self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
1115 
1116 
1117     def read(self, size):
1118         """Read 'size' bytes from remote."""
1119         # sslobj.read() sometimes returns < size bytes
1120         chunks = []
1121         read = 0
1122         while read < size:
1123             data = self.sslobj.read(size-read)
1124             read += len(data)
1125             chunks.append(data)
1126 
1127         return ''.join(chunks)
1128 
1129 
1130     def readline(self):
1131         """Read line from remote."""
1132         # NB: socket.ssl needs a "readline" method, or perhaps a "makefile" method.
1133         line = []
1134         while 1:
1135             char = self.sslobj.read(1)
1136             line.append(char)
1137             if char == "\n": return ''.join(line)
1138 
1139 
1140     def send(self, data):
1141         """Send data to remote."""
1142         # NB: socket.ssl needs a "sendall" method to match socket objects.
1143         bytes = len(data)
1144         while bytes > 0:
1145             sent = self.sslobj.write(data)
1146             if sent == bytes:
1147                 break    # avoid copy
1148             data = data[sent:]
1149             bytes = bytes - sent
1150 
1151 
1152     def shutdown(self):
1153         """Close I/O established in "open"."""
1154         self.sock.close()
1155 
1156 
1157     def socket(self):
1158         """Return socket instance used to connect to IMAP4 server.
1159 
1160         socket = <instance>.socket()
1161         """
1162         return self.sock
1163 
1164 
1165     def ssl(self):
1166         """Return SSLObject instance used to communicate with the IMAP4 server.
1167 
1168         ssl = <instance>.socket.ssl()
1169         """
1170         return self.sslobj
1171 
1172 
1173 
1174 class IMAP4_stream(IMAP4):
1175 
1176     """IMAP4 client class over a stream
1177 
1178     Instantiate with: IMAP4_stream(command)
1179 
1180             where "command" is a string that can be passed to os.popen2()
1181 
1182     for more documentation see the docstring of the parent class IMAP4.
1183     """
1184 
1185 
1186     def __init__(self, command):
1187         self.command = command
1188         IMAP4.__init__(self)
1189 
1190 
1191     def open(self, host = None, port = None):
1192         """Setup a stream connection.
1193         This connection will be used by the routines:
1194             read, readline, send, shutdown.
1195         """
1196         self.host = None        # For compatibility with parent class
1197         self.port = None
1198         self.sock = None
1199         self.file = None
1200         self.writefile, self.readfile = os.popen2(self.command)
1201 
1202 
1203     def read(self, size):
1204         """Read 'size' bytes from remote."""
1205         return self.readfile.read(size)
1206 
1207 
1208     def readline(self):
1209         """Read line from remote."""
1210         return self.readfile.readline()
1211 
1212 
1213     def send(self, data):
1214         """Send data to remote."""
1215         self.writefile.write(data)
1216         self.writefile.flush()
1217 
1218 
1219     def shutdown(self):
1220         """Close I/O established in "open"."""
1221         self.readfile.close()
1222         self.writefile.close()
1223 
1224 
1225 
1226 class _Authenticator:
1227 
1228     """Private class to provide en/decoding
1229             for base64-based authentication conversation.
1230     """
1231 
1232     def __init__(self, mechinst):
1233         self.mech = mechinst    # Callable object to provide/process data
1234 
1235     def process(self, data):
1236         ret = self.mech(self.decode(data))
1237         if ret is None:
1238             return '*'      # Abort conversation
1239         return self.encode(ret)
1240 
1241     def encode(self, inp):
1242         #
1243         #  Invoke binascii.b2a_base64 iteratively with
1244         #  short even length buffers, strip the trailing
1245         #  line feed from the result and append.  "Even"
1246         #  means a number that factors to both 6 and 8,
1247         #  so when it gets to the end of the 8-bit input
1248         #  there's no partial 6-bit output.
1249         #
1250         oup = ''
1251         while inp:
1252             if len(inp) > 48:
1253                 t = inp[:48]
1254                 inp = inp[48:]
1255             else:
1256                 t = inp
1257                 inp = ''
1258             e = binascii.b2a_base64(t)
1259             if e:
1260                 oup = oup + e[:-1]
1261         return oup
1262 
1263     def decode(self, inp):
1264         if not inp:
1265             return ''
1266         return binascii.a2b_base64(inp)
1267 
1268 
1269 
1270 Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
1271         'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
1272 
1273 def Internaldate2tuple(resp):
1274     """Convert IMAP4 INTERNALDATE to UT.
1275 
1276     Returns Python time module tuple.
1277     """
1278 
1279     mo = InternalDate.match(resp)
1280     if not mo:
1281         return None
1282 
1283     mon = Mon2num[mo.group('mon')]
1284     zonen = mo.group('zonen')
1285 
1286     day = int(mo.group('day'))
1287     year = int(mo.group('year'))
1288     hour = int(mo.group('hour'))
1289     min = int(mo.group('min'))
1290     sec = int(mo.group('sec'))
1291     zoneh = int(mo.group('zoneh'))
1292     zonem = int(mo.group('zonem'))
1293 
1294     # INTERNALDATE timezone must be subtracted to get UT
1295 
1296     zone = (zoneh*60 + zonem)*60
1297     if zonen == '-':
1298         zone = -zone
1299 
1300     tt = (year, mon, day, hour, min, sec, -1, -1, -1)
1301 
1302     utc = time.mktime(tt)
1303 
1304     # Following is necessary because the time module has no 'mkgmtime'.
1305     # 'mktime' assumes arg in local timezone, so adds timezone/altzone.
1306 
1307     lt = time.localtime(utc)
1308     if time.daylight and lt[-1]:
1309         zone = zone + time.altzone
1310     else:
1311         zone = zone + time.timezone
1312 
1313     return time.localtime(utc - zone)
1314 
1315 
1316 
1317 def Int2AP(num):
1318 
1319     """Convert integer to A-P string representation."""
1320 
1321     val = ''; AP = 'ABCDEFGHIJKLMNOP'
1322     num = int(abs(num))
1323     while num:
1324         num, mod = divmod(num, 16)
1325         val = AP[mod] + val
1326     return val
1327 
1328 
1329 
1330 def ParseFlags(resp):
1331 
1332     """Convert IMAP4 flags response to python tuple."""
1333 
1334     mo = Flags.match(resp)
1335     if not mo:
1336         return ()
1337 
1338     return tuple(mo.group('flags').split())
1339 
1340 
1341 def Time2Internaldate(date_time):
1342 
1343     """Convert 'date_time' to IMAP4 INTERNALDATE representation.
1344 
1345     Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
1346     """
1347 
1348     if isinstance(date_time, (int, float)):
1349         tt = time.localtime(date_time)
1350     elif isinstance(date_time, (tuple, time.struct_time)):
1351         tt = date_time
1352     elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'):
1353         return date_time        # Assume in correct format
1354     else:
1355         raise ValueError("date_time not of a known type")
1356 
1357     dt = time.strftime("%d-%b-%Y %H:%M:%S", tt)
1358     if dt[0] == '0':
1359         dt = ' ' + dt[1:]
1360     if time.daylight and tt[-1]:
1361         zone = -time.altzone
1362     else:
1363         zone = -time.timezone
1364     return '"' + dt + " %+03d%02d" % divmod(zone//60, 60) + '"'
1365 
1366 
1367 
1368 if __name__ == '__main__':
1369 
1370     # To test: invoke either as 'python imaplib.py [IMAP4_server_hostname]'
1371     # or 'python imaplib.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"'
1372     # to test the IMAP4_stream class
1373 
1374     import getopt, getpass
1375 
1376     try:
1377         optlist, args = getopt.getopt(sys.argv[1:], 'd:s:')
1378     except getopt.error, val:
1379         optlist, args = (), ()
1380 
1381     stream_command = None
1382     for opt,val in optlist:
1383         if opt == '-d':
1384             Debug = int(val)
1385         elif opt == '-s':
1386             stream_command = val
1387             if not args: args = (stream_command,)
1388 
1389     if not args: args = ('',)
1390 
1391     host = args[0]
1392 
1393     USER = getpass.getuser()
1394     PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost"))
1395 
1396     test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':'\n'}
1397     test_seq1 = (
1398     ('login', (USER, PASSWD)),
1399     ('create', ('/tmp/xxx 1',)),
1400     ('rename', ('/tmp/xxx 1', '/tmp/yyy')),
1401     ('CREATE', ('/tmp/yyz 2',)),
1402     ('append', ('/tmp/yyz 2', None, None, test_mesg)),
1403     ('list', ('/tmp', 'yy*')),
1404     ('select', ('/tmp/yyz 2',)),
1405     ('search', (None, 'SUBJECT', 'test')),
1406     ('fetch', ('1', '(FLAGS INTERNALDATE RFC822)')),
1407     ('store', ('1', 'FLAGS', '(\Deleted)')),
1408     ('namespace', ()),
1409     ('expunge', ()),
1410     ('recent', ()),
1411     ('close', ()),
1412     )
1413 
1414     test_seq2 = (
1415     ('select', ()),
1416     ('response',('UIDVALIDITY',)),
1417     ('uid', ('SEARCH', 'ALL')),
1418     ('response', ('EXISTS',)),
1419     ('append', (None, None, None, test_mesg)),
1420     ('recent', ()),
1421     ('logout', ()),
1422     )
1423 
1424     def run(cmd, args):
1425         M._mesg('%s %s' % (cmd, args))
1426         typ, dat = getattr(M, cmd)(*args)
1427         M._mesg('%s => %s %s' % (cmd, typ, dat))
1428         if typ == 'NO': raise dat[0]
1429         return dat
1430 
1431     try:
1432         if stream_command:
1433             M = IMAP4_stream(stream_command)
1434         else:
1435             M = IMAP4(host)
1436         if M.state == 'AUTH':
1437             test_seq1 = test_seq1[1:]   # Login not needed
1438         M._mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION)
1439         M._mesg('CAPABILITIES = %r' % (M.capabilities,))
1440 
1441         for cmd,args in test_seq1:
1442             run(cmd, args)
1443 
1444         for ml in run('list', ('/tmp/', 'yy%')):
1445             mo = re.match(r'.*"([^"]+)"$', ml)
1446             if mo: path = mo.group(1)
1447             else: path = ml.split()[-1]
1448             run('delete', (path,))
1449 
1450         for cmd,args in test_seq2:
1451             dat = run(cmd, args)
1452 
1453             if (cmd,args) != ('uid', ('SEARCH', 'ALL')):
1454                 continue
1455 
1456             uid = dat[-1].split()
1457             if not uid: continue
1458             run('uid', ('FETCH', '%s' % uid[-1],
1459                     '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)'))
1460 
1461         print '\nAll tests OK.'
1462 
1463     except:
1464         print '\nTests failed.'
1465 
1466         if not Debug:
1467             print '''
1468 If you would like to see debugging output,
1469 try: %s -d5
1470 ''' % sys.argv[0]
1471 
1472         raise
1473 

Generated by PyXR 0.9.4
SourceForge.net Logo