Guido van Rossum | 484772d | 1998-04-06 18:27:27 +0000 | [diff] [blame] | 1 | """A POP3 client class. Based on the J. Myers POP3 draft, Jan. 96 |
| 2 | |
| 3 | Author: David Ascher <david_ascher@brown.edu> [heavily stealing from |
| 4 | nntplib.py] |
| 5 | |
| 6 | """ |
| 7 | |
| 8 | __version__ = "0.01a - Feb 1, 1996 (with formatting changes by GvR)" |
| 9 | |
| 10 | # Example (see the test function at the end of this file) |
| 11 | |
| 12 | TESTSERVER = "localhost" |
| 13 | TESTACCOUNT = "test" |
| 14 | TESTPASSWORD = "_passwd_" |
| 15 | |
| 16 | # Imports |
| 17 | |
| 18 | from types import StringType |
| 19 | import regex |
| 20 | import socket |
| 21 | import string |
| 22 | |
| 23 | # Exception raised when an error or invalid response is received: |
| 24 | error_proto = 'pop3.error_proto' # response does not begin with + |
| 25 | |
| 26 | # Standard Port |
| 27 | POP3_PORT = 110 |
| 28 | |
| 29 | # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) |
| 30 | CRLF = '\r\n' |
| 31 | |
| 32 | # This library supports both the minimal and optional command sets: |
| 33 | # Arguments can be strings or integers (where appropriate) |
| 34 | # (e.g.: retr(1) and retr('1') both work equally well. |
| 35 | # |
| 36 | # Minimal Command Set: |
| 37 | # USER name user(name) |
| 38 | # PASS string pass_(string) |
| 39 | # STAT stat() |
| 40 | # LIST [msg] list(msg = None) |
| 41 | # RETR msg retr(msg) |
| 42 | # DELE msg dele(msg) |
| 43 | # NOOP noop() |
| 44 | # RSET rset() |
| 45 | # QUIT quit() |
| 46 | # |
| 47 | # Optional Commands (some servers support these) |
| 48 | # APOP name digest apop(name, digest) |
| 49 | # TOP msg n top(msg, n) |
| 50 | # UIDL [msg] uidl(msg = None) |
| 51 | # |
| 52 | |
| 53 | |
| 54 | class POP3: |
| 55 | def __init__(self, host, port = POP3_PORT): |
| 56 | self.host = host |
| 57 | self.port = port |
| 58 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 59 | self.sock.connect(self.host, self.port) |
| 60 | self.file = self.sock.makefile('rb') |
| 61 | self._debugging = 0 |
| 62 | self.welcome = self._getresp() |
| 63 | |
| 64 | def _putline(self, line): |
| 65 | line = line + CRLF |
| 66 | if self._debugging > 1: print '*put*', `line` |
| 67 | self.sock.send(line) |
| 68 | |
| 69 | # Internal: send one command to the server (through _putline()) |
| 70 | def _putcmd(self, line): |
| 71 | if self._debugging: print '*cmd*', `line` |
| 72 | self._putline(line) |
| 73 | |
| 74 | # Internal: return one line from the server, stripping CRLF. |
| 75 | # Raise EOFError if the connection is closed |
| 76 | def _getline(self): |
| 77 | line = self.file.readline() |
| 78 | if self._debugging > 1: |
| 79 | print '*get*', `line` |
| 80 | if not line: raise EOFError |
| 81 | if line[-2:] == CRLF: line = line[:-2] |
| 82 | elif line[-1:] in CRLF: line = line[:-1] |
| 83 | return line |
| 84 | |
| 85 | # Internal: get a response from the server. |
| 86 | # Raise various errors if the response indicates an error |
| 87 | def _getresp(self): |
| 88 | resp = self._getline() |
| 89 | if self._debugging > 1: print '*resp*', `resp` |
| 90 | c = resp[:1] |
| 91 | if c != '+': |
| 92 | raise error_proto, resp |
| 93 | return resp |
| 94 | |
| 95 | # Internal: get a response plus following text from the server. |
| 96 | # Raise various errors if the response indicates an error |
| 97 | def _getlongresp(self): |
| 98 | resp = self._getresp() |
| 99 | list = [] |
| 100 | while 1: |
| 101 | line = self._getline() |
| 102 | if line == '.': |
| 103 | break |
| 104 | list.append(line) |
| 105 | return resp, list |
| 106 | |
| 107 | # Internal: send a command and get the response |
| 108 | def _shortcmd(self, line): |
| 109 | self._putcmd(line) |
| 110 | return self._getresp() |
| 111 | |
| 112 | # Internal: send a command and get the response plus following text |
| 113 | def _longcmd(self, line): |
| 114 | self._putcmd(line) |
| 115 | return self._getlongresp() |
| 116 | |
| 117 | # These can be useful: |
| 118 | |
| 119 | def getwelcome(self): |
| 120 | return self.welcome |
| 121 | |
| 122 | def set_debuglevel(self, level): |
| 123 | self._debugging = level |
| 124 | |
| 125 | # Here are all the POP commands: |
| 126 | |
| 127 | def user(self, user): |
| 128 | user = str(user) |
| 129 | return self._shortcmd('USER ' + user) |
| 130 | |
| 131 | def pass_(self, pswd): |
| 132 | pswd = str(pswd) |
| 133 | return self._shortcmd('PASS ' + pswd) |
| 134 | |
| 135 | def stat(self): |
| 136 | retval = self._shortcmd('STAT') |
| 137 | rets = string.split(retval) |
| 138 | numMessages = string.atoi(rets[1]) |
| 139 | sizeMessages = string.atoi(rets[2]) |
| 140 | return (numMessages, sizeMessages) |
| 141 | |
| 142 | def list(self, msg=None): |
| 143 | if msg: |
| 144 | msg = str(msg) |
| 145 | return self._longcmd('LIST ' + msg) |
| 146 | else: |
| 147 | return self._longcmd('LIST') |
| 148 | |
| 149 | def retr(self, which): |
| 150 | which = str(which) |
| 151 | return self._longcmd('RETR ' + which) |
| 152 | |
| 153 | def dele(self, which): |
| 154 | which = str(which) |
| 155 | return self._shortcmd('DELE ' + which) |
| 156 | |
| 157 | def noop(self): |
| 158 | return self._shortcmd('NOOP') |
| 159 | |
| 160 | def rset(self): |
| 161 | return self._shortcmd('RSET') |
| 162 | |
| 163 | # optional commands: |
| 164 | |
| 165 | def apop(self, digest): |
| 166 | digest = str(digest) |
| 167 | return self._shortcmd('APOP ' + digest) |
| 168 | |
| 169 | def top(self, which, howmuch): |
| 170 | which = str(which) |
| 171 | howmuch = str(howmuch) |
| 172 | return self._longcmd('TOP ' + which + ' ' + howmuch) |
| 173 | |
| 174 | def uidl(self, which = None): |
| 175 | if which: |
| 176 | which = str(which) |
| 177 | return self._longcmd('UIDL ' + which) |
| 178 | else: |
| 179 | return self._longcmd('UIDL') |
| 180 | |
| 181 | def quit(self): |
| 182 | resp = self._shortcmd('QUIT') |
| 183 | self.file.close() |
| 184 | self.sock.close() |
| 185 | del self.file, self.sock |
| 186 | return resp |
| 187 | |
| 188 | if __name__ == "__main__": |
| 189 | a = POP3(TESTSERVER) |
| 190 | print a.getwelcome() |
| 191 | a.user(TESTACCOUNT) |
| 192 | a.pass_(TESTPASSWORD) |
| 193 | a.list() |
| 194 | (numMsgs, totalSize) = a.stat() |
| 195 | for i in range(1, numMsgs + 1): |
| 196 | (header, msg, octets) = a.retr(i) |
| 197 | print "Message ", `i`, ':' |
| 198 | for line in msg: |
| 199 | print ' ' + line |
| 200 | print '-----------------------' |
| 201 | a.quit() |