| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1 | """IMAP4 client. | 
 | 2 |  | 
 | 3 | Based on RFC 2060. | 
 | 4 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 5 | Public class:           IMAP4 | 
 | 6 | Public variable:        Debug | 
 | 7 | Public functions:       Internaldate2tuple | 
 | 8 |                         Int2AP | 
 | 9 |                         ParseFlags | 
 | 10 |                         Time2Internaldate | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 11 | """ | 
| Guido van Rossum | b1f0812 | 1998-06-25 02:22:16 +0000 | [diff] [blame] | 12 |  | 
| Guido van Rossum | 98d9fd3 | 2000-02-28 15:12:25 +0000 | [diff] [blame] | 13 | # Author: Piers Lauder <piers@cs.su.oz.au> December 1997. | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 14 | # | 
| Guido van Rossum | 98d9fd3 | 2000-02-28 15:12:25 +0000 | [diff] [blame] | 15 | # Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998. | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 16 | # String method conversion by ESR, February 2001. | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 17 | # GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001. | 
| Guido van Rossum | 98d9fd3 | 2000-02-28 15:12:25 +0000 | [diff] [blame] | 18 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 19 | __version__ = "2.47" | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 20 |  | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 21 | import binascii, re, socket, time, random, sys | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 22 |  | 
| Barry Warsaw | f449391 | 2001-01-24 04:16:09 +0000 | [diff] [blame] | 23 | __all__ = ["IMAP4", "Internaldate2tuple", | 
 | 24 |            "Int2AP", "ParseFlags", "Time2Internaldate"] | 
| Skip Montanaro | 2dd4276 | 2001-01-23 15:35:05 +0000 | [diff] [blame] | 25 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 26 | #       Globals | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 27 |  | 
 | 28 | CRLF = '\r\n' | 
 | 29 | Debug = 0 | 
 | 30 | IMAP4_PORT = 143 | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 31 | AllowedVersions = ('IMAP4REV1', 'IMAP4')        # Most recent first | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 32 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 33 | #       Commands | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 34 |  | 
 | 35 | Commands = { | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 36 |         # name            valid states | 
 | 37 |         'APPEND':       ('AUTH', 'SELECTED'), | 
 | 38 |         'AUTHENTICATE': ('NONAUTH',), | 
 | 39 |         'CAPABILITY':   ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
 | 40 |         'CHECK':        ('SELECTED',), | 
 | 41 |         'CLOSE':        ('SELECTED',), | 
 | 42 |         'COPY':         ('SELECTED',), | 
 | 43 |         'CREATE':       ('AUTH', 'SELECTED'), | 
 | 44 |         'DELETE':       ('AUTH', 'SELECTED'), | 
 | 45 |         'EXAMINE':      ('AUTH', 'SELECTED'), | 
 | 46 |         'EXPUNGE':      ('SELECTED',), | 
 | 47 |         'FETCH':        ('SELECTED',), | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 48 |         'GETACL':       ('AUTH', 'SELECTED'), | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 49 |         'LIST':         ('AUTH', 'SELECTED'), | 
 | 50 |         'LOGIN':        ('NONAUTH',), | 
 | 51 |         'LOGOUT':       ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
 | 52 |         'LSUB':         ('AUTH', 'SELECTED'), | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 53 |         'NAMESPACE':    ('AUTH', 'SELECTED'), | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 54 |         'NOOP':         ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), | 
 | 55 |         'PARTIAL':      ('SELECTED',), | 
 | 56 |         'RENAME':       ('AUTH', 'SELECTED'), | 
 | 57 |         'SEARCH':       ('SELECTED',), | 
 | 58 |         'SELECT':       ('AUTH', 'SELECTED'), | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 59 |         'SETACL':       ('AUTH', 'SELECTED'), | 
 | 60 |         'SORT':         ('SELECTED',), | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 61 |         'STATUS':       ('AUTH', 'SELECTED'), | 
 | 62 |         'STORE':        ('SELECTED',), | 
 | 63 |         'SUBSCRIBE':    ('AUTH', 'SELECTED'), | 
 | 64 |         'UID':          ('SELECTED',), | 
 | 65 |         'UNSUBSCRIBE':  ('AUTH', 'SELECTED'), | 
 | 66 |         } | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 67 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 68 | #       Patterns to match server responses | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 69 |  | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 70 | Continuation = re.compile(r'\+( (?P<data>.*))?') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 71 | Flags = re.compile(r'.*FLAGS \((?P<flags>[^\)]*)\)') | 
 | 72 | InternalDate = re.compile(r'.*INTERNALDATE "' | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 73 |         r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])' | 
 | 74 |         r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])' | 
 | 75 |         r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])' | 
 | 76 |         r'"') | 
| Guido van Rossum | f36b182 | 2000-02-17 17:12:39 +0000 | [diff] [blame] | 77 | Literal = re.compile(r'.*{(?P<size>\d+)}$') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 78 | Response_code = re.compile(r'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]') | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 79 | Untagged_response = re.compile(r'\* (?P<type>[A-Z-]+)( (?P<data>.*))?') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 80 | Untagged_status = re.compile(r'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?') | 
 | 81 |  | 
 | 82 |  | 
 | 83 |  | 
 | 84 | class IMAP4: | 
 | 85 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 86 |     """IMAP4 client class. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 87 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 88 |     Instantiate with: IMAP4([host[, port]]) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 89 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 90 |             host - host's name (default: localhost); | 
 | 91 |             port - port number (default: standard IMAP4 port). | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 92 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 93 |     All IMAP4rev1 commands are supported by methods of the same | 
 | 94 |     name (in lower-case). | 
| Guido van Rossum | 6884af7 | 1998-05-29 13:34:03 +0000 | [diff] [blame] | 95 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 96 |     All arguments to commands are converted to strings, except for | 
 | 97 |     AUTHENTICATE, and the last argument to APPEND which is passed as | 
 | 98 |     an IMAP4 literal.  If necessary (the string contains any | 
 | 99 |     non-printing characters or white-space and isn't enclosed with | 
 | 100 |     either parentheses or double quotes) each string is quoted. | 
 | 101 |     However, the 'password' argument to the LOGIN command is always | 
 | 102 |     quoted.  If you want to avoid having an argument string quoted | 
 | 103 |     (eg: the 'flags' argument to STORE) then enclose the string in | 
 | 104 |     parentheses (eg: "(\Deleted)"). | 
| Guido van Rossum | 6884af7 | 1998-05-29 13:34:03 +0000 | [diff] [blame] | 105 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 106 |     Each command returns a tuple: (type, [data, ...]) where 'type' | 
 | 107 |     is usually 'OK' or 'NO', and 'data' is either the text from the | 
 | 108 |     tagged response, or untagged results from command. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 109 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 110 |     Errors raise the exception class <instance>.error("<reason>"). | 
 | 111 |     IMAP4 server errors raise <instance>.abort("<reason>"), | 
 | 112 |     which is a sub-class of 'error'. Mailbox status changes | 
 | 113 |     from READ-WRITE to READ-ONLY raise the exception class | 
 | 114 |     <instance>.readonly("<reason>"), which is a sub-class of 'abort'. | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 115 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 116 |     "error" exceptions imply a program error. | 
 | 117 |     "abort" exceptions imply the connection should be reset, and | 
 | 118 |             the command re-tried. | 
 | 119 |     "readonly" exceptions imply the command should be re-tried. | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 120 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 121 |     Note: to use this module, you must read the RFCs pertaining | 
 | 122 |     to the IMAP4 protocol, as the semantics of the arguments to | 
 | 123 |     each IMAP4 command are left to the invoker, not to mention | 
 | 124 |     the results. | 
 | 125 |     """ | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 126 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 127 |     class error(Exception): pass    # Logical errors - debug required | 
 | 128 |     class abort(error): pass        # Service errors - close and retry | 
 | 129 |     class readonly(abort): pass     # Mailbox status changed to READ-ONLY | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 130 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 131 |     mustquote = re.compile(r"[^\w!#$%&'*+,.:;<=>?^`|~-]") | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 132 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 133 |     def __init__(self, host = '', port = IMAP4_PORT): | 
 | 134 |         self.host = host | 
 | 135 |         self.port = port | 
 | 136 |         self.debug = Debug | 
 | 137 |         self.state = 'LOGOUT' | 
 | 138 |         self.literal = None             # A literal argument to a command | 
 | 139 |         self.tagged_commands = {}       # Tagged commands awaiting response | 
 | 140 |         self.untagged_responses = {}    # {typ: [data, ...], ...} | 
 | 141 |         self.continuation_response = '' # Last continuation response | 
 | 142 |         self.is_readonly = None         # READ-ONLY desired state | 
 | 143 |         self.tagnum = 0 | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 144 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 145 |         # Open socket to server. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 146 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 147 |         self.open(host, port) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 148 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 149 |         # Create unique tag for this session, | 
 | 150 |         # and compile tagged response matcher. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 151 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 152 |         self.tagpre = Int2AP(random.randint(0, 31999)) | 
 | 153 |         self.tagre = re.compile(r'(?P<tag>' | 
 | 154 |                         + self.tagpre | 
 | 155 |                         + r'\d+) (?P<type>[A-Z]+) (?P<data>.*)') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 156 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 157 |         # Get server welcome message, | 
 | 158 |         # request and store CAPABILITY response. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 159 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 160 |         if __debug__: | 
 | 161 |             if self.debug >= 1: | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 162 |                 _mesg('imaplib version %s' % __version__) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 163 |                 _mesg('new IMAP4 connection, tag=%s' % self.tagpre) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 164 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 165 |         self.welcome = self._get_response() | 
 | 166 |         if self.untagged_responses.has_key('PREAUTH'): | 
 | 167 |             self.state = 'AUTH' | 
 | 168 |         elif self.untagged_responses.has_key('OK'): | 
 | 169 |             self.state = 'NONAUTH' | 
 | 170 |         else: | 
 | 171 |             raise self.error(self.welcome) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 172 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 173 |         cap = 'CAPABILITY' | 
 | 174 |         self._simple_command(cap) | 
 | 175 |         if not self.untagged_responses.has_key(cap): | 
 | 176 |             raise self.error('no CAPABILITY response from server') | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 177 |         self.capabilities = tuple(self.untagged_responses[cap][-1].upper().split()) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 178 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 179 |         if __debug__: | 
 | 180 |             if self.debug >= 3: | 
 | 181 |                 _mesg('CAPABILITIES: %s' % `self.capabilities`) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 182 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 183 |         for version in AllowedVersions: | 
 | 184 |             if not version in self.capabilities: | 
 | 185 |                 continue | 
 | 186 |             self.PROTOCOL_VERSION = version | 
 | 187 |             return | 
| Guido van Rossum | b1f0812 | 1998-06-25 02:22:16 +0000 | [diff] [blame] | 188 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 189 |         raise self.error('server not IMAP4 compliant') | 
| Guido van Rossum | 38d8f4e | 1998-04-11 01:22:34 +0000 | [diff] [blame] | 190 |  | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 191 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 192 |     def __getattr__(self, attr): | 
 | 193 |         #       Allow UPPERCASE variants of IMAP4 command methods. | 
 | 194 |         if Commands.has_key(attr): | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 195 |             return getattr(self, attr.lower()) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 196 |         raise AttributeError("Unknown IMAP4 command: '%s'" % attr) | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 197 |  | 
 | 198 |  | 
 | 199 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 200 |     #       Overridable methods | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 201 |  | 
 | 202 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 203 |     def open(self, host, port): | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 204 |         """Setup connection to remote server on "host:port". | 
 | 205 |         This connection will be used by the routines: | 
 | 206 |             read, readline, send, shutdown. | 
 | 207 |         """ | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 208 |         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
 | 209 |         self.sock.connect((self.host, self.port)) | 
 | 210 |         self.file = self.sock.makefile('r') | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 211 |  | 
 | 212 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 213 |     def read(self, size): | 
 | 214 |         """Read 'size' bytes from remote.""" | 
 | 215 |         return self.file.read(size) | 
 | 216 |  | 
 | 217 |  | 
 | 218 |     def readline(self): | 
 | 219 |         """Read line from remote.""" | 
 | 220 |         return self.file.readline() | 
 | 221 |  | 
 | 222 |  | 
 | 223 |     def send(self, data): | 
 | 224 |         """Send data to remote.""" | 
 | 225 |         self.sock.send(data) | 
 | 226 |  | 
 | 227 |  | 
 | 228 |     def shutdown(self): | 
 | 229 |         """Close I/O established in "open".""" | 
 | 230 |         self.file.close() | 
 | 231 |         self.sock.close() | 
 | 232 |  | 
 | 233 |  | 
 | 234 |     def socket(self): | 
 | 235 |         """Return socket instance used to connect to IMAP4 server. | 
 | 236 |  | 
 | 237 |         socket = <instance>.socket() | 
 | 238 |         """ | 
 | 239 |         return self.sock | 
 | 240 |  | 
 | 241 |  | 
 | 242 |  | 
 | 243 |     #       Utility methods | 
 | 244 |  | 
 | 245 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 246 |     def recent(self): | 
 | 247 |         """Return most recent 'RECENT' responses if any exist, | 
 | 248 |         else prompt server for an update using the 'NOOP' command. | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 249 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 250 |         (typ, [data]) = <instance>.recent() | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 251 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 252 |         'data' is None if no new messages, | 
 | 253 |         else list of RECENT responses, most recent last. | 
 | 254 |         """ | 
 | 255 |         name = 'RECENT' | 
 | 256 |         typ, dat = self._untagged_response('OK', [None], name) | 
 | 257 |         if dat[-1]: | 
 | 258 |             return typ, dat | 
 | 259 |         typ, dat = self.noop()  # Prod server for response | 
 | 260 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 261 |  | 
 | 262 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 263 |     def response(self, code): | 
 | 264 |         """Return data for response 'code' if received, or None. | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 265 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 266 |         Old value for response 'code' is cleared. | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 267 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 268 |         (code, [data]) = <instance>.response(code) | 
 | 269 |         """ | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 270 |         return self._untagged_response(code, [None], code.upper()) | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 271 |  | 
 | 272 |  | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 273 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 274 |     #       IMAP4 commands | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 275 |  | 
 | 276 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 277 |     def append(self, mailbox, flags, date_time, message): | 
 | 278 |         """Append message to named mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 279 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 280 |         (typ, [data]) = <instance>.append(mailbox, flags, date_time, message) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 281 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 282 |                 All args except `message' can be None. | 
 | 283 |         """ | 
 | 284 |         name = 'APPEND' | 
 | 285 |         if not mailbox: | 
 | 286 |             mailbox = 'INBOX' | 
 | 287 |         if flags: | 
 | 288 |             if (flags[0],flags[-1]) != ('(',')'): | 
 | 289 |                 flags = '(%s)' % flags | 
 | 290 |         else: | 
 | 291 |             flags = None | 
 | 292 |         if date_time: | 
 | 293 |             date_time = Time2Internaldate(date_time) | 
 | 294 |         else: | 
 | 295 |             date_time = None | 
 | 296 |         self.literal = message | 
 | 297 |         return self._simple_command(name, mailbox, flags, date_time) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 298 |  | 
 | 299 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 300 |     def authenticate(self, mechanism, authobject): | 
 | 301 |         """Authenticate command - requires response processing. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 302 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 303 |         'mechanism' specifies which authentication mechanism is to | 
 | 304 |         be used - it must appear in <instance>.capabilities in the | 
 | 305 |         form AUTH=<mechanism>. | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 306 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 307 |         'authobject' must be a callable object: | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 308 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 309 |                 data = authobject(response) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 310 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 311 |         It will be called to process server continuation responses. | 
 | 312 |         It should return data that will be encoded and sent to server. | 
 | 313 |         It should return None if the client abort response '*' should | 
 | 314 |         be sent instead. | 
 | 315 |         """ | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 316 |         mech = mechanism.upper() | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 317 |         cap = 'AUTH=%s' % mech | 
 | 318 |         if not cap in self.capabilities: | 
 | 319 |             raise self.error("Server doesn't allow %s authentication." % mech) | 
 | 320 |         self.literal = _Authenticator(authobject).process | 
 | 321 |         typ, dat = self._simple_command('AUTHENTICATE', mech) | 
 | 322 |         if typ != 'OK': | 
 | 323 |             raise self.error(dat[-1]) | 
 | 324 |         self.state = 'AUTH' | 
 | 325 |         return typ, dat | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 326 |  | 
 | 327 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 328 |     def check(self): | 
 | 329 |         """Checkpoint mailbox on server. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 330 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 331 |         (typ, [data]) = <instance>.check() | 
 | 332 |         """ | 
 | 333 |         return self._simple_command('CHECK') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 334 |  | 
 | 335 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 336 |     def close(self): | 
 | 337 |         """Close currently selected mailbox. | 
| Guido van Rossum | eeec0af | 1998-04-09 14:20:31 +0000 | [diff] [blame] | 338 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 339 |         Deleted messages are removed from writable mailbox. | 
 | 340 |         This is the recommended command before 'LOGOUT'. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 341 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 342 |         (typ, [data]) = <instance>.close() | 
 | 343 |         """ | 
 | 344 |         try: | 
 | 345 |             typ, dat = self._simple_command('CLOSE') | 
 | 346 |         finally: | 
 | 347 |             self.state = 'AUTH' | 
 | 348 |         return typ, dat | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 349 |  | 
 | 350 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 351 |     def copy(self, message_set, new_mailbox): | 
 | 352 |         """Copy 'message_set' messages onto end of 'new_mailbox'. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 353 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 354 |         (typ, [data]) = <instance>.copy(message_set, new_mailbox) | 
 | 355 |         """ | 
 | 356 |         return self._simple_command('COPY', message_set, new_mailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 357 |  | 
 | 358 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 359 |     def create(self, mailbox): | 
 | 360 |         """Create new mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 361 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 362 |         (typ, [data]) = <instance>.create(mailbox) | 
 | 363 |         """ | 
 | 364 |         return self._simple_command('CREATE', mailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 365 |  | 
 | 366 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 367 |     def delete(self, mailbox): | 
 | 368 |         """Delete old mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 369 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 370 |         (typ, [data]) = <instance>.delete(mailbox) | 
 | 371 |         """ | 
 | 372 |         return self._simple_command('DELETE', mailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 373 |  | 
 | 374 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 375 |     def expunge(self): | 
 | 376 |         """Permanently remove deleted items from selected mailbox. | 
| Guido van Rossum | eeec0af | 1998-04-09 14:20:31 +0000 | [diff] [blame] | 377 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 378 |         Generates 'EXPUNGE' response for each deleted message. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 379 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 380 |         (typ, [data]) = <instance>.expunge() | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 381 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 382 |         'data' is list of 'EXPUNGE'd message numbers in order received. | 
 | 383 |         """ | 
 | 384 |         name = 'EXPUNGE' | 
 | 385 |         typ, dat = self._simple_command(name) | 
 | 386 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 387 |  | 
 | 388 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 389 |     def fetch(self, message_set, message_parts): | 
 | 390 |         """Fetch (parts of) messages. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 391 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 392 |         (typ, [data, ...]) = <instance>.fetch(message_set, message_parts) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 393 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 394 |         'message_parts' should be a string of selected parts | 
 | 395 |         enclosed in parentheses, eg: "(UID BODY[TEXT])". | 
| Fred Drake | fd267d9 | 2000-05-25 03:25:26 +0000 | [diff] [blame] | 396 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 397 |         'data' are tuples of message part envelope and data. | 
 | 398 |         """ | 
 | 399 |         name = 'FETCH' | 
 | 400 |         typ, dat = self._simple_command(name, message_set, message_parts) | 
 | 401 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 402 |  | 
 | 403 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 404 |     def getacl(self, mailbox): | 
 | 405 |         """Get the ACLs for a mailbox. | 
 | 406 |  | 
 | 407 |         (typ, [data]) = <instance>.getacl(mailbox) | 
 | 408 |         """ | 
 | 409 |         typ, dat = self._simple_command('GETACL', mailbox) | 
 | 410 |         return self._untagged_response(typ, dat, 'ACL') | 
 | 411 |  | 
 | 412 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 413 |     def list(self, directory='""', pattern='*'): | 
 | 414 |         """List mailbox names in directory matching pattern. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 415 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 416 |         (typ, [data]) = <instance>.list(directory='""', pattern='*') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 417 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 418 |         'data' is list of LIST responses. | 
 | 419 |         """ | 
 | 420 |         name = 'LIST' | 
 | 421 |         typ, dat = self._simple_command(name, directory, pattern) | 
 | 422 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 423 |  | 
 | 424 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 425 |     def login(self, user, password): | 
 | 426 |         """Identify client using plaintext password. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 427 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 428 |         (typ, [data]) = <instance>.login(user, password) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 429 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 430 |         NB: 'password' will be quoted. | 
 | 431 |         """ | 
 | 432 |         #if not 'AUTH=LOGIN' in self.capabilities: | 
 | 433 |         #       raise self.error("Server doesn't allow LOGIN authentication." % mech) | 
 | 434 |         typ, dat = self._simple_command('LOGIN', user, self._quote(password)) | 
 | 435 |         if typ != 'OK': | 
 | 436 |             raise self.error(dat[-1]) | 
 | 437 |         self.state = 'AUTH' | 
 | 438 |         return typ, dat | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 439 |  | 
 | 440 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 441 |     def logout(self): | 
 | 442 |         """Shutdown connection to server. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 443 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 444 |         (typ, [data]) = <instance>.logout() | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 445 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 446 |         Returns server 'BYE' response. | 
 | 447 |         """ | 
 | 448 |         self.state = 'LOGOUT' | 
 | 449 |         try: typ, dat = self._simple_command('LOGOUT') | 
 | 450 |         except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]] | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 451 |         self.shutdown() | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 452 |         if self.untagged_responses.has_key('BYE'): | 
 | 453 |             return 'BYE', self.untagged_responses['BYE'] | 
 | 454 |         return typ, dat | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 455 |  | 
 | 456 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 457 |     def lsub(self, directory='""', pattern='*'): | 
 | 458 |         """List 'subscribed' mailbox names in directory matching pattern. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 459 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 460 |         (typ, [data, ...]) = <instance>.lsub(directory='""', pattern='*') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 461 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 462 |         'data' are tuples of message part envelope and data. | 
 | 463 |         """ | 
 | 464 |         name = 'LSUB' | 
 | 465 |         typ, dat = self._simple_command(name, directory, pattern) | 
 | 466 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 467 |  | 
 | 468 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 469 |     def namespace(self): | 
 | 470 |         """ Returns IMAP namespaces ala rfc2342 | 
 | 471 |  | 
 | 472 |         (typ, [data, ...]) = <instance>.namespace() | 
 | 473 |         """ | 
 | 474 |         name = 'NAMESPACE' | 
 | 475 |         typ, dat = self._simple_command(name) | 
 | 476 |         return self._untagged_response(typ, dat, name) | 
 | 477 |  | 
 | 478 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 479 |     def noop(self): | 
 | 480 |         """Send NOOP command. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 481 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 482 |         (typ, data) = <instance>.noop() | 
 | 483 |         """ | 
 | 484 |         if __debug__: | 
 | 485 |             if self.debug >= 3: | 
 | 486 |                 _dump_ur(self.untagged_responses) | 
 | 487 |         return self._simple_command('NOOP') | 
| Guido van Rossum | 6884af7 | 1998-05-29 13:34:03 +0000 | [diff] [blame] | 488 |  | 
 | 489 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 490 |     def partial(self, message_num, message_part, start, length): | 
 | 491 |         """Fetch truncated part of a message. | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 492 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 493 |         (typ, [data, ...]) = <instance>.partial(message_num, message_part, start, length) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 494 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 495 |         'data' is tuple of message part envelope and data. | 
 | 496 |         """ | 
 | 497 |         name = 'PARTIAL' | 
 | 498 |         typ, dat = self._simple_command(name, message_num, message_part, start, length) | 
 | 499 |         return self._untagged_response(typ, dat, 'FETCH') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 500 |  | 
 | 501 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 502 |     def rename(self, oldmailbox, newmailbox): | 
 | 503 |         """Rename old mailbox name to new. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 504 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 505 |         (typ, data) = <instance>.rename(oldmailbox, newmailbox) | 
 | 506 |         """ | 
 | 507 |         return self._simple_command('RENAME', oldmailbox, newmailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 508 |  | 
 | 509 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 510 |     def search(self, charset, *criteria): | 
 | 511 |         """Search mailbox for matching messages. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 512 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 513 |         (typ, [data]) = <instance>.search(charset, criterium, ...) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 514 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 515 |         'data' is space separated list of matching message numbers. | 
 | 516 |         """ | 
 | 517 |         name = 'SEARCH' | 
 | 518 |         if charset: | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 519 |             typ, dat = apply(self._simple_command, (name, 'CHARSET', charset) + criteria) | 
 | 520 |         else: | 
 | 521 |             typ, dat = apply(self._simple_command, (name,) + criteria) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 522 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 523 |  | 
 | 524 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 525 |     def select(self, mailbox='INBOX', readonly=None): | 
 | 526 |         """Select a mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 527 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 528 |         Flush all untagged responses. | 
| Guido van Rossum | 4658682 | 1998-05-18 14:39:42 +0000 | [diff] [blame] | 529 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 530 |         (typ, [data]) = <instance>.select(mailbox='INBOX', readonly=None) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 531 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 532 |         'data' is count of messages in mailbox ('EXISTS' response). | 
 | 533 |         """ | 
 | 534 |         # Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY') | 
 | 535 |         self.untagged_responses = {}    # Flush old responses. | 
 | 536 |         self.is_readonly = readonly | 
 | 537 |         if readonly: | 
 | 538 |             name = 'EXAMINE' | 
 | 539 |         else: | 
 | 540 |             name = 'SELECT' | 
 | 541 |         typ, dat = self._simple_command(name, mailbox) | 
 | 542 |         if typ != 'OK': | 
 | 543 |             self.state = 'AUTH'     # Might have been 'SELECTED' | 
 | 544 |             return typ, dat | 
 | 545 |         self.state = 'SELECTED' | 
 | 546 |         if self.untagged_responses.has_key('READ-ONLY') \ | 
 | 547 |                 and not readonly: | 
 | 548 |             if __debug__: | 
 | 549 |                 if self.debug >= 1: | 
 | 550 |                     _dump_ur(self.untagged_responses) | 
 | 551 |             raise self.readonly('%s is not writable' % mailbox) | 
 | 552 |         return typ, self.untagged_responses.get('EXISTS', [None]) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 553 |  | 
 | 554 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 555 |     def setacl(self, mailbox, who, what): | 
 | 556 |         """Set a mailbox acl. | 
 | 557 |  | 
 | 558 |         (typ, [data]) = <instance>.create(mailbox, who, what) | 
 | 559 |         """ | 
 | 560 |         return self._simple_command('SETACL', mailbox, who, what) | 
 | 561 |  | 
 | 562 |  | 
 | 563 |     def sort(self, sort_criteria, charset, *search_criteria): | 
 | 564 |         """IMAP4rev1 extension SORT command. | 
 | 565 |  | 
 | 566 |         (typ, [data]) = <instance>.sort(sort_criteria, charset, search_criteria, ...) | 
 | 567 |         """ | 
 | 568 |         name = 'SORT' | 
| Tim Peters | 87cc0c3 | 2001-07-21 01:41:30 +0000 | [diff] [blame] | 569 |         #if not name in self.capabilities:      # Let the server decide! | 
 | 570 |         #       raise self.error('unimplemented extension command: %s' % name) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 571 |         if (sort_criteria[0],sort_criteria[-1]) != ('(',')'): | 
| Tim Peters | 87cc0c3 | 2001-07-21 01:41:30 +0000 | [diff] [blame] | 572 |             sort_criteria = '(%s)' % sort_criteria | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 573 |         typ, dat = apply(self._simple_command, (name, sort_criteria, charset) + search_criteria) | 
 | 574 |         return self._untagged_response(typ, dat, name) | 
 | 575 |  | 
 | 576 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 577 |     def status(self, mailbox, names): | 
 | 578 |         """Request named status conditions for mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 579 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 580 |         (typ, [data]) = <instance>.status(mailbox, names) | 
 | 581 |         """ | 
 | 582 |         name = 'STATUS' | 
| Tim Peters | 87cc0c3 | 2001-07-21 01:41:30 +0000 | [diff] [blame] | 583 |         #if self.PROTOCOL_VERSION == 'IMAP4':   # Let the server decide! | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 584 |         #    raise self.error('%s unimplemented in IMAP4 (obtain IMAP4rev1 server, or re-code)' % name) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 585 |         typ, dat = self._simple_command(name, mailbox, names) | 
 | 586 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 587 |  | 
 | 588 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 589 |     def store(self, message_set, command, flags): | 
 | 590 |         """Alters flag dispositions for messages in mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 591 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 592 |         (typ, [data]) = <instance>.store(message_set, command, flags) | 
 | 593 |         """ | 
 | 594 |         if (flags[0],flags[-1]) != ('(',')'): | 
 | 595 |             flags = '(%s)' % flags  # Avoid quoting the flags | 
 | 596 |         typ, dat = self._simple_command('STORE', message_set, command, flags) | 
 | 597 |         return self._untagged_response(typ, dat, 'FETCH') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 598 |  | 
 | 599 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 600 |     def subscribe(self, mailbox): | 
 | 601 |         """Subscribe to new mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 602 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 603 |         (typ, [data]) = <instance>.subscribe(mailbox) | 
 | 604 |         """ | 
 | 605 |         return self._simple_command('SUBSCRIBE', mailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 606 |  | 
 | 607 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 608 |     def uid(self, command, *args): | 
 | 609 |         """Execute "command arg ..." with messages identified by UID, | 
 | 610 |                 rather than message number. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 611 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 612 |         (typ, [data]) = <instance>.uid(command, arg1, arg2, ...) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 613 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 614 |         Returns response appropriate to 'command'. | 
 | 615 |         """ | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 616 |         command = command.upper() | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 617 |         if not Commands.has_key(command): | 
 | 618 |             raise self.error("Unknown IMAP4 UID command: %s" % command) | 
 | 619 |         if self.state not in Commands[command]: | 
 | 620 |             raise self.error('command %s illegal in state %s' | 
 | 621 |                                     % (command, self.state)) | 
 | 622 |         name = 'UID' | 
 | 623 |         typ, dat = apply(self._simple_command, (name, command) + args) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 624 |         if command in ('SEARCH', 'SORT'): | 
 | 625 |             name = command | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 626 |         else: | 
 | 627 |             name = 'FETCH' | 
 | 628 |         return self._untagged_response(typ, dat, name) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 629 |  | 
 | 630 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 631 |     def unsubscribe(self, mailbox): | 
 | 632 |         """Unsubscribe from old mailbox. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 633 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 634 |         (typ, [data]) = <instance>.unsubscribe(mailbox) | 
 | 635 |         """ | 
 | 636 |         return self._simple_command('UNSUBSCRIBE', mailbox) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 637 |  | 
 | 638 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 639 |     def xatom(self, name, *args): | 
 | 640 |         """Allow simple extension commands | 
 | 641 |                 notified by server in CAPABILITY response. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 642 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 643 |         Assumes command is legal in current state. | 
 | 644 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 645 |         (typ, [data]) = <instance>.xatom(name, arg, ...) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 646 |  | 
 | 647 |         Returns response appropriate to extension command `name'. | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 648 |         """ | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 649 |         name = name.upper() | 
| Tim Peters | 87cc0c3 | 2001-07-21 01:41:30 +0000 | [diff] [blame] | 650 |         #if not name in self.capabilities:      # Let the server decide! | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 651 |         #    raise self.error('unknown extension command: %s' % name) | 
 | 652 |         if not Commands.has_key(name): | 
 | 653 |             Commands[name] = (self.state,) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 654 |         return apply(self._simple_command, (name,) + args) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 655 |  | 
 | 656 |  | 
 | 657 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 658 |     #       Private methods | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 659 |  | 
 | 660 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 661 |     def _append_untagged(self, typ, dat): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 662 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 663 |         if dat is None: dat = '' | 
 | 664 |         ur = self.untagged_responses | 
 | 665 |         if __debug__: | 
 | 666 |             if self.debug >= 5: | 
 | 667 |                 _mesg('untagged_responses[%s] %s += ["%s"]' % | 
 | 668 |                         (typ, len(ur.get(typ,'')), dat)) | 
 | 669 |         if ur.has_key(typ): | 
 | 670 |             ur[typ].append(dat) | 
 | 671 |         else: | 
 | 672 |             ur[typ] = [dat] | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 673 |  | 
 | 674 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 675 |     def _check_bye(self): | 
 | 676 |         bye = self.untagged_responses.get('BYE') | 
 | 677 |         if bye: | 
 | 678 |             raise self.abort(bye[-1]) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 679 |  | 
 | 680 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 681 |     def _command(self, name, *args): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 682 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 683 |         if self.state not in Commands[name]: | 
 | 684 |             self.literal = None | 
 | 685 |             raise self.error( | 
 | 686 |             'command %s illegal in state %s' % (name, self.state)) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 687 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 688 |         for typ in ('OK', 'NO', 'BAD'): | 
 | 689 |             if self.untagged_responses.has_key(typ): | 
 | 690 |                 del self.untagged_responses[typ] | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 691 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 692 |         if self.untagged_responses.has_key('READ-ONLY') \ | 
 | 693 |         and not self.is_readonly: | 
 | 694 |             raise self.readonly('mailbox status changed to READ-ONLY') | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 695 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 696 |         tag = self._new_tag() | 
 | 697 |         data = '%s %s' % (tag, name) | 
 | 698 |         for arg in args: | 
 | 699 |             if arg is None: continue | 
 | 700 |             data = '%s %s' % (data, self._checkquote(arg)) | 
| Guido van Rossum | 6884af7 | 1998-05-29 13:34:03 +0000 | [diff] [blame] | 701 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 702 |         literal = self.literal | 
 | 703 |         if literal is not None: | 
 | 704 |             self.literal = None | 
 | 705 |             if type(literal) is type(self._command): | 
 | 706 |                 literator = literal | 
 | 707 |             else: | 
 | 708 |                 literator = None | 
 | 709 |                 data = '%s {%s}' % (data, len(literal)) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 710 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 711 |         if __debug__: | 
 | 712 |             if self.debug >= 4: | 
 | 713 |                 _mesg('> %s' % data) | 
 | 714 |             else: | 
 | 715 |                 _log('> %s' % data) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 716 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 717 |         try: | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 718 |             self.send('%s%s' % (data, CRLF)) | 
 | 719 |         except (socket.error, OSError), val: | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 720 |             raise self.abort('socket error: %s' % val) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 721 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 722 |         if literal is None: | 
 | 723 |             return tag | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 724 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 725 |         while 1: | 
 | 726 |             # Wait for continuation response | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 727 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 728 |             while self._get_response(): | 
 | 729 |                 if self.tagged_commands[tag]:   # BAD/NO? | 
 | 730 |                     return tag | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 731 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 732 |             # Send literal | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 733 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 734 |             if literator: | 
 | 735 |                 literal = literator(self.continuation_response) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 736 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 737 |             if __debug__: | 
 | 738 |                 if self.debug >= 4: | 
 | 739 |                     _mesg('write literal size %s' % len(literal)) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 740 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 741 |             try: | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 742 |                 self.send(literal) | 
 | 743 |                 self.send(CRLF) | 
 | 744 |             except (socket.error, OSError), val: | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 745 |                 raise self.abort('socket error: %s' % val) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 746 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 747 |             if not literator: | 
 | 748 |                 break | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 749 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 750 |         return tag | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 751 |  | 
 | 752 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 753 |     def _command_complete(self, name, tag): | 
 | 754 |         self._check_bye() | 
 | 755 |         try: | 
 | 756 |             typ, data = self._get_tagged_response(tag) | 
 | 757 |         except self.abort, val: | 
 | 758 |             raise self.abort('command: %s => %s' % (name, val)) | 
 | 759 |         except self.error, val: | 
 | 760 |             raise self.error('command: %s => %s' % (name, val)) | 
 | 761 |         self._check_bye() | 
 | 762 |         if typ == 'BAD': | 
 | 763 |             raise self.error('%s command error: %s %s' % (name, typ, data)) | 
 | 764 |         return typ, data | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 765 |  | 
 | 766 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 767 |     def _get_response(self): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 768 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 769 |         # Read response and store. | 
 | 770 |         # | 
 | 771 |         # Returns None for continuation responses, | 
 | 772 |         # otherwise first response line received. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 773 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 774 |         resp = self._get_line() | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 775 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 776 |         # Command completion response? | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 777 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 778 |         if self._match(self.tagre, resp): | 
 | 779 |             tag = self.mo.group('tag') | 
 | 780 |             if not self.tagged_commands.has_key(tag): | 
 | 781 |                 raise self.abort('unexpected tagged response: %s' % resp) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 782 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 783 |             typ = self.mo.group('type') | 
 | 784 |             dat = self.mo.group('data') | 
 | 785 |             self.tagged_commands[tag] = (typ, [dat]) | 
 | 786 |         else: | 
 | 787 |             dat2 = None | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 788 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 789 |             # '*' (untagged) responses? | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 790 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 791 |             if not self._match(Untagged_response, resp): | 
 | 792 |                 if self._match(Untagged_status, resp): | 
 | 793 |                     dat2 = self.mo.group('data2') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 794 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 795 |             if self.mo is None: | 
 | 796 |                 # Only other possibility is '+' (continuation) response... | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 797 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 798 |                 if self._match(Continuation, resp): | 
 | 799 |                     self.continuation_response = self.mo.group('data') | 
 | 800 |                     return None     # NB: indicates continuation | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 801 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 802 |                 raise self.abort("unexpected response: '%s'" % resp) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 803 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 804 |             typ = self.mo.group('type') | 
 | 805 |             dat = self.mo.group('data') | 
 | 806 |             if dat is None: dat = ''        # Null untagged response | 
 | 807 |             if dat2: dat = dat + ' ' + dat2 | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 808 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 809 |             # Is there a literal to come? | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 810 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 811 |             while self._match(Literal, dat): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 812 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 813 |                 # Read literal direct from connection. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 814 |  | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 815 |                 size = int(self.mo.group('size')) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 816 |                 if __debug__: | 
 | 817 |                     if self.debug >= 4: | 
 | 818 |                         _mesg('read literal size %s' % size) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 819 |                 data = self.read(size) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 820 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 821 |                 # Store response with literal as tuple | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 822 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 823 |                 self._append_untagged(typ, (dat, data)) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 824 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 825 |                 # Read trailer - possibly containing another literal | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 826 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 827 |                 dat = self._get_line() | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 828 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 829 |             self._append_untagged(typ, dat) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 830 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 831 |         # Bracketed response information? | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 832 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 833 |         if typ in ('OK', 'NO', 'BAD') and self._match(Response_code, dat): | 
 | 834 |             self._append_untagged(self.mo.group('type'), self.mo.group('data')) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 835 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 836 |         if __debug__: | 
 | 837 |             if self.debug >= 1 and typ in ('NO', 'BAD', 'BYE'): | 
 | 838 |                 _mesg('%s response: %s' % (typ, dat)) | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 839 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 840 |         return resp | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 841 |  | 
 | 842 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 843 |     def _get_tagged_response(self, tag): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 844 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 845 |         while 1: | 
 | 846 |             result = self.tagged_commands[tag] | 
 | 847 |             if result is not None: | 
 | 848 |                 del self.tagged_commands[tag] | 
 | 849 |                 return result | 
| Guido van Rossum | f36b182 | 2000-02-17 17:12:39 +0000 | [diff] [blame] | 850 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 851 |             # Some have reported "unexpected response" exceptions. | 
 | 852 |             # Note that ignoring them here causes loops. | 
 | 853 |             # Instead, send me details of the unexpected response and | 
 | 854 |             # I'll update the code in `_get_response()'. | 
| Guido van Rossum | f36b182 | 2000-02-17 17:12:39 +0000 | [diff] [blame] | 855 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 856 |             try: | 
 | 857 |                 self._get_response() | 
 | 858 |             except self.abort, val: | 
 | 859 |                 if __debug__: | 
 | 860 |                     if self.debug >= 1: | 
 | 861 |                         print_log() | 
 | 862 |                 raise | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 863 |  | 
 | 864 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 865 |     def _get_line(self): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 866 |  | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 867 |         line = self.readline() | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 868 |         if not line: | 
 | 869 |             raise self.abort('socket error: EOF') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 870 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 871 |         # Protocol mandates all lines terminated by CRLF | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 872 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 873 |         line = line[:-2] | 
 | 874 |         if __debug__: | 
 | 875 |             if self.debug >= 4: | 
 | 876 |                 _mesg('< %s' % line) | 
 | 877 |             else: | 
 | 878 |                 _log('< %s' % line) | 
 | 879 |         return line | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 880 |  | 
 | 881 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 882 |     def _match(self, cre, s): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 883 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 884 |         # Run compiled regular expression match method on 's'. | 
 | 885 |         # Save result, return success. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 886 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 887 |         self.mo = cre.match(s) | 
 | 888 |         if __debug__: | 
 | 889 |             if self.mo is not None and self.debug >= 5: | 
 | 890 |                 _mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`)) | 
 | 891 |         return self.mo is not None | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 892 |  | 
 | 893 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 894 |     def _new_tag(self): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 895 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 896 |         tag = '%s%s' % (self.tagpre, self.tagnum) | 
 | 897 |         self.tagnum = self.tagnum + 1 | 
 | 898 |         self.tagged_commands[tag] = None | 
 | 899 |         return tag | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 900 |  | 
 | 901 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 902 |     def _checkquote(self, arg): | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 903 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 904 |         # Must quote command args if non-alphanumeric chars present, | 
 | 905 |         # and not already quoted. | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 906 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 907 |         if type(arg) is not type(''): | 
 | 908 |             return arg | 
 | 909 |         if (arg[0],arg[-1]) in (('(',')'),('"','"')): | 
 | 910 |             return arg | 
 | 911 |         if self.mustquote.search(arg) is None: | 
 | 912 |             return arg | 
 | 913 |         return self._quote(arg) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 914 |  | 
 | 915 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 916 |     def _quote(self, arg): | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 917 |  | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 918 |         arg = arg.replace('\\', '\\\\') | 
 | 919 |         arg = arg.replace('"', '\\"') | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 920 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 921 |         return '"%s"' % arg | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 922 |  | 
 | 923 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 924 |     def _simple_command(self, name, *args): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 925 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 926 |         return self._command_complete(name, apply(self._command, (name,) + args)) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 927 |  | 
 | 928 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 929 |     def _untagged_response(self, typ, dat, name): | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 930 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 931 |         if typ == 'NO': | 
 | 932 |             return typ, dat | 
 | 933 |         if not self.untagged_responses.has_key(name): | 
 | 934 |             return typ, [None] | 
 | 935 |         data = self.untagged_responses[name] | 
 | 936 |         if __debug__: | 
 | 937 |             if self.debug >= 5: | 
 | 938 |                 _mesg('untagged_responses[%s] => %s' % (name, data)) | 
 | 939 |         del self.untagged_responses[name] | 
 | 940 |         return typ, data | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 941 |  | 
 | 942 |  | 
 | 943 |  | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 944 | class _Authenticator: | 
 | 945 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 946 |     """Private class to provide en/decoding | 
 | 947 |             for base64-based authentication conversation. | 
 | 948 |     """ | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 949 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 950 |     def __init__(self, mechinst): | 
 | 951 |         self.mech = mechinst    # Callable object to provide/process data | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 952 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 953 |     def process(self, data): | 
 | 954 |         ret = self.mech(self.decode(data)) | 
 | 955 |         if ret is None: | 
 | 956 |             return '*'      # Abort conversation | 
 | 957 |         return self.encode(ret) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 958 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 959 |     def encode(self, inp): | 
 | 960 |         # | 
 | 961 |         #  Invoke binascii.b2a_base64 iteratively with | 
 | 962 |         #  short even length buffers, strip the trailing | 
 | 963 |         #  line feed from the result and append.  "Even" | 
 | 964 |         #  means a number that factors to both 6 and 8, | 
 | 965 |         #  so when it gets to the end of the 8-bit input | 
 | 966 |         #  there's no partial 6-bit output. | 
 | 967 |         # | 
 | 968 |         oup = '' | 
 | 969 |         while inp: | 
 | 970 |             if len(inp) > 48: | 
 | 971 |                 t = inp[:48] | 
 | 972 |                 inp = inp[48:] | 
 | 973 |             else: | 
 | 974 |                 t = inp | 
 | 975 |                 inp = '' | 
 | 976 |             e = binascii.b2a_base64(t) | 
 | 977 |             if e: | 
 | 978 |                 oup = oup + e[:-1] | 
 | 979 |         return oup | 
 | 980 |  | 
 | 981 |     def decode(self, inp): | 
 | 982 |         if not inp: | 
 | 983 |             return '' | 
 | 984 |         return binascii.a2b_base64(inp) | 
 | 985 |  | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 986 |  | 
 | 987 |  | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 988 | Mon2num = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 989 |         'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 990 |  | 
 | 991 | def Internaldate2tuple(resp): | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 992 |     """Convert IMAP4 INTERNALDATE to UT. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 993 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 994 |     Returns Python time module tuple. | 
 | 995 |     """ | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 996 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 997 |     mo = InternalDate.match(resp) | 
 | 998 |     if not mo: | 
 | 999 |         return None | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1000 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1001 |     mon = Mon2num[mo.group('mon')] | 
 | 1002 |     zonen = mo.group('zonen') | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1003 |  | 
| Jeremy Hylton | f5d3ea0 | 2001-02-22 13:24:27 +0000 | [diff] [blame] | 1004 |     day = int(mo.group('day')) | 
 | 1005 |     year = int(mo.group('year')) | 
 | 1006 |     hour = int(mo.group('hour')) | 
 | 1007 |     min = int(mo.group('min')) | 
 | 1008 |     sec = int(mo.group('sec')) | 
 | 1009 |     zoneh = int(mo.group('zoneh')) | 
 | 1010 |     zonem = int(mo.group('zonem')) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1011 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1012 |     # INTERNALDATE timezone must be subtracted to get UT | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1013 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1014 |     zone = (zoneh*60 + zonem)*60 | 
 | 1015 |     if zonen == '-': | 
 | 1016 |         zone = -zone | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1017 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1018 |     tt = (year, mon, day, hour, min, sec, -1, -1, -1) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1019 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1020 |     utc = time.mktime(tt) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1021 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1022 |     # Following is necessary because the time module has no 'mkgmtime'. | 
 | 1023 |     # 'mktime' assumes arg in local timezone, so adds timezone/altzone. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1024 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1025 |     lt = time.localtime(utc) | 
 | 1026 |     if time.daylight and lt[-1]: | 
 | 1027 |         zone = zone + time.altzone | 
 | 1028 |     else: | 
 | 1029 |         zone = zone + time.timezone | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1030 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1031 |     return time.localtime(utc - zone) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1032 |  | 
 | 1033 |  | 
 | 1034 |  | 
 | 1035 | def Int2AP(num): | 
 | 1036 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1037 |     """Convert integer to A-P string representation.""" | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1038 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1039 |     val = ''; AP = 'ABCDEFGHIJKLMNOP' | 
 | 1040 |     num = int(abs(num)) | 
 | 1041 |     while num: | 
 | 1042 |         num, mod = divmod(num, 16) | 
 | 1043 |         val = AP[mod] + val | 
 | 1044 |     return val | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1045 |  | 
 | 1046 |  | 
 | 1047 |  | 
 | 1048 | def ParseFlags(resp): | 
 | 1049 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1050 |     """Convert IMAP4 flags response to python tuple.""" | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1051 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1052 |     mo = Flags.match(resp) | 
 | 1053 |     if not mo: | 
 | 1054 |         return () | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1055 |  | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 1056 |     return tuple(mo.group('flags').split()) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1057 |  | 
 | 1058 |  | 
 | 1059 | def Time2Internaldate(date_time): | 
 | 1060 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1061 |     """Convert 'date_time' to IMAP4 INTERNALDATE representation. | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1062 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1063 |     Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"' | 
 | 1064 |     """ | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1065 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1066 |     dttype = type(date_time) | 
 | 1067 |     if dttype is type(1) or dttype is type(1.1): | 
 | 1068 |         tt = time.localtime(date_time) | 
 | 1069 |     elif dttype is type(()): | 
 | 1070 |         tt = date_time | 
 | 1071 |     elif dttype is type(""): | 
 | 1072 |         return date_time        # Assume in correct format | 
 | 1073 |     else: raise ValueError | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1074 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1075 |     dt = time.strftime("%d-%b-%Y %H:%M:%S", tt) | 
 | 1076 |     if dt[0] == '0': | 
 | 1077 |         dt = ' ' + dt[1:] | 
 | 1078 |     if time.daylight and tt[-1]: | 
 | 1079 |         zone = -time.altzone | 
 | 1080 |     else: | 
 | 1081 |         zone = -time.timezone | 
 | 1082 |     return '"' + dt + " %+02d%02d" % divmod(zone/60, 60) + '"' | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1083 |  | 
 | 1084 |  | 
 | 1085 |  | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 1086 | if __debug__: | 
 | 1087 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1088 |     def _mesg(s, secs=None): | 
 | 1089 |         if secs is None: | 
 | 1090 |             secs = time.time() | 
 | 1091 |         tm = time.strftime('%M:%S', time.localtime(secs)) | 
 | 1092 |         sys.stderr.write('  %s.%02d %s\n' % (tm, (secs*100)%100, s)) | 
 | 1093 |         sys.stderr.flush() | 
| Guido van Rossum | 26367a0 | 1998-09-28 15:34:46 +0000 | [diff] [blame] | 1094 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1095 |     def _dump_ur(dict): | 
 | 1096 |         # Dump untagged responses (in `dict'). | 
 | 1097 |         l = dict.items() | 
 | 1098 |         if not l: return | 
 | 1099 |         t = '\n\t\t' | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 1100 |         l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l) | 
| Piers Lauder | 34d9705 | 2001-07-20 10:28:51 +0000 | [diff] [blame] | 1101 |         _mesg('untagged responses dump:%s%s' % (t, t.join(l))) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 1102 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1103 |     _cmd_log = []           # Last `_cmd_log_len' interactions | 
 | 1104 |     _cmd_log_len = 10 | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 1105 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1106 |     def _log(line): | 
 | 1107 |         # Keep log of last `_cmd_log_len' interactions for debugging. | 
 | 1108 |         if len(_cmd_log) == _cmd_log_len: | 
 | 1109 |             del _cmd_log[0] | 
 | 1110 |         _cmd_log.append((time.time(), line)) | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 1111 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1112 |     def print_log(): | 
 | 1113 |         _mesg('last %d IMAP4 interactions:' % len(_cmd_log)) | 
 | 1114 |         for secs,line in _cmd_log: | 
 | 1115 |             _mesg(line, secs) | 
| Guido van Rossum | eda960a | 1998-06-18 14:24:28 +0000 | [diff] [blame] | 1116 |  | 
 | 1117 |  | 
| Guido van Rossum | 8c06221 | 1999-12-13 23:27:45 +0000 | [diff] [blame] | 1118 |  | 
 | 1119 | if __name__ == '__main__': | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1120 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1121 |     import getopt, getpass, sys | 
| Guido van Rossum | d659693 | 1998-05-29 18:08:48 +0000 | [diff] [blame] | 1122 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1123 |     try: | 
 | 1124 |         optlist, args = getopt.getopt(sys.argv[1:], 'd:') | 
 | 1125 |     except getopt.error, val: | 
 | 1126 |         pass | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1127 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1128 |     for opt,val in optlist: | 
 | 1129 |         if opt == '-d': | 
 | 1130 |             Debug = int(val) | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1131 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1132 |     if not args: args = ('',) | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1133 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1134 |     host = args[0] | 
| Guido van Rossum | b1f0812 | 1998-06-25 02:22:16 +0000 | [diff] [blame] | 1135 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1136 |     USER = getpass.getuser() | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 1137 |     PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost")) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1138 |  | 
| Piers Lauder | e02f904 | 2001-08-05 10:43:03 +0000 | [diff] [blame^] | 1139 |     test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)sdata...%(lf)s' % {'user':USER, 'lf':CRLF} | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1140 |     test_seq1 = ( | 
 | 1141 |     ('login', (USER, PASSWD)), | 
 | 1142 |     ('create', ('/tmp/xxx 1',)), | 
 | 1143 |     ('rename', ('/tmp/xxx 1', '/tmp/yyy')), | 
 | 1144 |     ('CREATE', ('/tmp/yyz 2',)), | 
 | 1145 |     ('append', ('/tmp/yyz 2', None, None, test_mesg)), | 
 | 1146 |     ('list', ('/tmp', 'yy*')), | 
 | 1147 |     ('select', ('/tmp/yyz 2',)), | 
 | 1148 |     ('search', (None, 'SUBJECT', 'test')), | 
 | 1149 |     ('partial', ('1', 'RFC822', 1, 1024)), | 
 | 1150 |     ('store', ('1', 'FLAGS', '(\Deleted)')), | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 1151 |     ('namespace', ()), | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1152 |     ('expunge', ()), | 
 | 1153 |     ('recent', ()), | 
 | 1154 |     ('close', ()), | 
 | 1155 |     ) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1156 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1157 |     test_seq2 = ( | 
 | 1158 |     ('select', ()), | 
 | 1159 |     ('response',('UIDVALIDITY',)), | 
 | 1160 |     ('uid', ('SEARCH', 'ALL')), | 
 | 1161 |     ('response', ('EXISTS',)), | 
 | 1162 |     ('append', (None, None, None, test_mesg)), | 
 | 1163 |     ('recent', ()), | 
 | 1164 |     ('logout', ()), | 
 | 1165 |     ) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1166 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1167 |     def run(cmd, args): | 
 | 1168 |         _mesg('%s %s' % (cmd, args)) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 1169 |         typ, dat = apply(getattr(M, cmd), args) | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1170 |         _mesg('%s => %s %s' % (cmd, typ, dat)) | 
 | 1171 |         return dat | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1172 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1173 |     try: | 
 | 1174 |         M = IMAP4(host) | 
 | 1175 |         _mesg('PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION) | 
| Piers Lauder | 15e5d53 | 2001-07-20 10:52:06 +0000 | [diff] [blame] | 1176 |         _mesg('CAPABILITIES = %s' % `M.capabilities`) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1177 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1178 |         for cmd,args in test_seq1: | 
 | 1179 |             run(cmd, args) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1180 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1181 |         for ml in run('list', ('/tmp/', 'yy%')): | 
 | 1182 |             mo = re.match(r'.*"([^"]+)"$', ml) | 
 | 1183 |             if mo: path = mo.group(1) | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 1184 |             else: path = ml.split()[-1] | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1185 |             run('delete', (path,)) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1186 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1187 |         for cmd,args in test_seq2: | 
 | 1188 |             dat = run(cmd, args) | 
| Guido van Rossum | c2c07fa | 1998-04-09 13:51:46 +0000 | [diff] [blame] | 1189 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1190 |             if (cmd,args) != ('uid', ('SEARCH', 'ALL')): | 
 | 1191 |                 continue | 
| Guido van Rossum | 38d8f4e | 1998-04-11 01:22:34 +0000 | [diff] [blame] | 1192 |  | 
| Eric S. Raymond | 25a0cbc | 2001-02-09 06:50:21 +0000 | [diff] [blame] | 1193 |             uid = dat[-1].split() | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1194 |             if not uid: continue | 
 | 1195 |             run('uid', ('FETCH', '%s' % uid[-1], | 
 | 1196 |                     '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)')) | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1197 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1198 |         print '\nAll tests OK.' | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1199 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1200 |     except: | 
 | 1201 |         print '\nTests failed.' | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1202 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1203 |         if not Debug: | 
 | 1204 |             print ''' | 
| Guido van Rossum | 66d4513 | 2000-03-28 20:20:53 +0000 | [diff] [blame] | 1205 | If you would like to see debugging output, | 
 | 1206 | try: %s -d5 | 
 | 1207 | ''' % sys.argv[0] | 
 | 1208 |  | 
| Tim Peters | 07e99cb | 2001-01-14 23:47:14 +0000 | [diff] [blame] | 1209 |         raise |