ยปCore Development>Code coverage>Lib/poplib.py

Python code coverage for Lib/poplib.py

#countcontent
1n/a"""A POP3 client class.
2n/a
3n/aBased on the J. Myers POP3 draft, Jan. 96
4n/a"""
5n/a
6n/a# Author: David Ascher <david_ascher@brown.edu>
7n/a# [heavily stealing from nntplib.py]
8n/a# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
9n/a# String method conversion and test jig improvements by ESR, February 2001.
10n/a# Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003
11n/a
12n/a# Example (see the test function at the end of this file)
13n/a
14n/a# Imports
15n/a
16n/aimport errno
17n/aimport re
18n/aimport socket
19n/a
20n/atry:
21n/a import ssl
22n/a HAVE_SSL = True
23n/aexcept ImportError:
24n/a HAVE_SSL = False
25n/a
26n/a__all__ = ["POP3","error_proto"]
27n/a
28n/a# Exception raised when an error or invalid response is received:
29n/a
30n/aclass error_proto(Exception): pass
31n/a
32n/a# Standard Port
33n/aPOP3_PORT = 110
34n/a
35n/a# POP SSL PORT
36n/aPOP3_SSL_PORT = 995
37n/a
38n/a# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
39n/aCR = b'\r'
40n/aLF = b'\n'
41n/aCRLF = CR+LF
42n/a
43n/a# maximal line length when calling readline(). This is to prevent
44n/a# reading arbitrary length lines. RFC 1939 limits POP3 line length to
45n/a# 512 characters, including CRLF. We have selected 2048 just to be on
46n/a# the safe side.
47n/a_MAXLINE = 2048
48n/a
49n/a
50n/aclass POP3:
51n/a
52n/a """This class supports both the minimal and optional command sets.
53n/a Arguments can be strings or integers (where appropriate)
54n/a (e.g.: retr(1) and retr('1') both work equally well.
55n/a
56n/a Minimal Command Set:
57n/a USER name user(name)
58n/a PASS string pass_(string)
59n/a STAT stat()
60n/a LIST [msg] list(msg = None)
61n/a RETR msg retr(msg)
62n/a DELE msg dele(msg)
63n/a NOOP noop()
64n/a RSET rset()
65n/a QUIT quit()
66n/a
67n/a Optional Commands (some servers support these):
68n/a RPOP name rpop(name)
69n/a APOP name digest apop(name, digest)
70n/a TOP msg n top(msg, n)
71n/a UIDL [msg] uidl(msg = None)
72n/a CAPA capa()
73n/a STLS stls()
74n/a UTF8 utf8()
75n/a
76n/a Raises one exception: 'error_proto'.
77n/a
78n/a Instantiate with:
79n/a POP3(hostname, port=110)
80n/a
81n/a NB: the POP protocol locks the mailbox from user
82n/a authorization until QUIT, so be sure to get in, suck
83n/a the messages, and quit, each time you access the
84n/a mailbox.
85n/a
86n/a POP is a line-based protocol, which means large mail
87n/a messages consume lots of python cycles reading them
88n/a line-by-line.
89n/a
90n/a If it's available on your mail server, use IMAP4
91n/a instead, it doesn't suffer from the two problems
92n/a above.
93n/a """
94n/a
95n/a encoding = 'UTF-8'
96n/a
97n/a def __init__(self, host, port=POP3_PORT,
98n/a timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
99n/a self.host = host
100n/a self.port = port
101n/a self._tls_established = False
102n/a self.sock = self._create_socket(timeout)
103n/a self.file = self.sock.makefile('rb')
104n/a self._debugging = 0
105n/a self.welcome = self._getresp()
106n/a
107n/a def _create_socket(self, timeout):
108n/a return socket.create_connection((self.host, self.port), timeout)
109n/a
110n/a def _putline(self, line):
111n/a if self._debugging > 1: print('*put*', repr(line))
112n/a self.sock.sendall(line + CRLF)
113n/a
114n/a
115n/a # Internal: send one command to the server (through _putline())
116n/a
117n/a def _putcmd(self, line):
118n/a if self._debugging: print('*cmd*', repr(line))
119n/a line = bytes(line, self.encoding)
120n/a self._putline(line)
121n/a
122n/a
123n/a # Internal: return one line from the server, stripping CRLF.
124n/a # This is where all the CPU time of this module is consumed.
125n/a # Raise error_proto('-ERR EOF') if the connection is closed.
126n/a
127n/a def _getline(self):
128n/a line = self.file.readline(_MAXLINE + 1)
129n/a if len(line) > _MAXLINE:
130n/a raise error_proto('line too long')
131n/a
132n/a if self._debugging > 1: print('*get*', repr(line))
133n/a if not line: raise error_proto('-ERR EOF')
134n/a octets = len(line)
135n/a # server can send any combination of CR & LF
136n/a # however, 'readline()' returns lines ending in LF
137n/a # so only possibilities are ...LF, ...CRLF, CR...LF
138n/a if line[-2:] == CRLF:
139n/a return line[:-2], octets
140n/a if line[:1] == CR:
141n/a return line[1:-1], octets
142n/a return line[:-1], octets
143n/a
144n/a
145n/a # Internal: get a response from the server.
146n/a # Raise 'error_proto' if the response doesn't start with '+'.
147n/a
148n/a def _getresp(self):
149n/a resp, o = self._getline()
150n/a if self._debugging > 1: print('*resp*', repr(resp))
151n/a if not resp.startswith(b'+'):
152n/a raise error_proto(resp)
153n/a return resp
154n/a
155n/a
156n/a # Internal: get a response plus following text from the server.
157n/a
158n/a def _getlongresp(self):
159n/a resp = self._getresp()
160n/a list = []; octets = 0
161n/a line, o = self._getline()
162n/a while line != b'.':
163n/a if line.startswith(b'..'):
164n/a o = o-1
165n/a line = line[1:]
166n/a octets = octets + o
167n/a list.append(line)
168n/a line, o = self._getline()
169n/a return resp, list, octets
170n/a
171n/a
172n/a # Internal: send a command and get the response
173n/a
174n/a def _shortcmd(self, line):
175n/a self._putcmd(line)
176n/a return self._getresp()
177n/a
178n/a
179n/a # Internal: send a command and get the response plus following text
180n/a
181n/a def _longcmd(self, line):
182n/a self._putcmd(line)
183n/a return self._getlongresp()
184n/a
185n/a
186n/a # These can be useful:
187n/a
188n/a def getwelcome(self):
189n/a return self.welcome
190n/a
191n/a
192n/a def set_debuglevel(self, level):
193n/a self._debugging = level
194n/a
195n/a
196n/a # Here are all the POP commands:
197n/a
198n/a def user(self, user):
199n/a """Send user name, return response
200n/a
201n/a (should indicate password required).
202n/a """
203n/a return self._shortcmd('USER %s' % user)
204n/a
205n/a
206n/a def pass_(self, pswd):
207n/a """Send password, return response
208n/a
209n/a (response includes message count, mailbox size).
210n/a
211n/a NB: mailbox is locked by server from here to 'quit()'
212n/a """
213n/a return self._shortcmd('PASS %s' % pswd)
214n/a
215n/a
216n/a def stat(self):
217n/a """Get mailbox status.
218n/a
219n/a Result is tuple of 2 ints (message count, mailbox size)
220n/a """
221n/a retval = self._shortcmd('STAT')
222n/a rets = retval.split()
223n/a if self._debugging: print('*stat*', repr(rets))
224n/a numMessages = int(rets[1])
225n/a sizeMessages = int(rets[2])
226n/a return (numMessages, sizeMessages)
227n/a
228n/a
229n/a def list(self, which=None):
230n/a """Request listing, return result.
231n/a
232n/a Result without a message number argument is in form
233n/a ['response', ['mesg_num octets', ...], octets].
234n/a
235n/a Result when a message number argument is given is a
236n/a single response: the "scan listing" for that message.
237n/a """
238n/a if which is not None:
239n/a return self._shortcmd('LIST %s' % which)
240n/a return self._longcmd('LIST')
241n/a
242n/a
243n/a def retr(self, which):
244n/a """Retrieve whole message number 'which'.
245n/a
246n/a Result is in form ['response', ['line', ...], octets].
247n/a """
248n/a return self._longcmd('RETR %s' % which)
249n/a
250n/a
251n/a def dele(self, which):
252n/a """Delete message number 'which'.
253n/a
254n/a Result is 'response'.
255n/a """
256n/a return self._shortcmd('DELE %s' % which)
257n/a
258n/a
259n/a def noop(self):
260n/a """Does nothing.
261n/a
262n/a One supposes the response indicates the server is alive.
263n/a """
264n/a return self._shortcmd('NOOP')
265n/a
266n/a
267n/a def rset(self):
268n/a """Unmark all messages marked for deletion."""
269n/a return self._shortcmd('RSET')
270n/a
271n/a
272n/a def quit(self):
273n/a """Signoff: commit changes on server, unlock mailbox, close connection."""
274n/a resp = self._shortcmd('QUIT')
275n/a self.close()
276n/a return resp
277n/a
278n/a def close(self):
279n/a """Close the connection without assuming anything about it."""
280n/a try:
281n/a file = self.file
282n/a self.file = None
283n/a if file is not None:
284n/a file.close()
285n/a finally:
286n/a sock = self.sock
287n/a self.sock = None
288n/a if sock is not None:
289n/a try:
290n/a sock.shutdown(socket.SHUT_RDWR)
291n/a except OSError as e:
292n/a # The server might already have closed the connection
293n/a if e.errno != errno.ENOTCONN:
294n/a raise
295n/a finally:
296n/a sock.close()
297n/a
298n/a #__del__ = quit
299n/a
300n/a
301n/a # optional commands:
302n/a
303n/a def rpop(self, user):
304n/a """Not sure what this does."""
305n/a return self._shortcmd('RPOP %s' % user)
306n/a
307n/a
308n/a timestamp = re.compile(br'\+OK.*(<[^>]+>)')
309n/a
310n/a def apop(self, user, password):
311n/a """Authorisation
312n/a
313n/a - only possible if server has supplied a timestamp in initial greeting.
314n/a
315n/a Args:
316n/a user - mailbox user;
317n/a password - mailbox password.
318n/a
319n/a NB: mailbox is locked by server from here to 'quit()'
320n/a """
321n/a secret = bytes(password, self.encoding)
322n/a m = self.timestamp.match(self.welcome)
323n/a if not m:
324n/a raise error_proto('-ERR APOP not supported by server')
325n/a import hashlib
326n/a digest = m.group(1)+secret
327n/a digest = hashlib.md5(digest).hexdigest()
328n/a return self._shortcmd('APOP %s %s' % (user, digest))
329n/a
330n/a
331n/a def top(self, which, howmuch):
332n/a """Retrieve message header of message number 'which'
333n/a and first 'howmuch' lines of message body.
334n/a
335n/a Result is in form ['response', ['line', ...], octets].
336n/a """
337n/a return self._longcmd('TOP %s %s' % (which, howmuch))
338n/a
339n/a
340n/a def uidl(self, which=None):
341n/a """Return message digest (unique id) list.
342n/a
343n/a If 'which', result contains unique id for that message
344n/a in the form 'response mesgnum uid', otherwise result is
345n/a the list ['response', ['mesgnum uid', ...], octets]
346n/a """
347n/a if which is not None:
348n/a return self._shortcmd('UIDL %s' % which)
349n/a return self._longcmd('UIDL')
350n/a
351n/a
352n/a def utf8(self):
353n/a """Try to enter UTF-8 mode (see RFC 6856). Returns server response.
354n/a """
355n/a return self._shortcmd('UTF8')
356n/a
357n/a
358n/a def capa(self):
359n/a """Return server capabilities (RFC 2449) as a dictionary
360n/a >>> c=poplib.POP3('localhost')
361n/a >>> c.capa()
362n/a {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],
363n/a 'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],
364n/a 'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],
365n/a 'UIDL': [], 'RESP-CODES': []}
366n/a >>>
367n/a
368n/a Really, according to RFC 2449, the cyrus folks should avoid
369n/a having the implementation split into multiple arguments...
370n/a """
371n/a def _parsecap(line):
372n/a lst = line.decode('ascii').split()
373n/a return lst[0], lst[1:]
374n/a
375n/a caps = {}
376n/a try:
377n/a resp = self._longcmd('CAPA')
378n/a rawcaps = resp[1]
379n/a for capline in rawcaps:
380n/a capnm, capargs = _parsecap(capline)
381n/a caps[capnm] = capargs
382n/a except error_proto as _err:
383n/a raise error_proto('-ERR CAPA not supported by server')
384n/a return caps
385n/a
386n/a
387n/a def stls(self, context=None):
388n/a """Start a TLS session on the active connection as specified in RFC 2595.
389n/a
390n/a context - a ssl.SSLContext
391n/a """
392n/a if not HAVE_SSL:
393n/a raise error_proto('-ERR TLS support missing')
394n/a if self._tls_established:
395n/a raise error_proto('-ERR TLS session already established')
396n/a caps = self.capa()
397n/a if not 'STLS' in caps:
398n/a raise error_proto('-ERR STLS not supported by server')
399n/a if context is None:
400n/a context = ssl._create_stdlib_context()
401n/a resp = self._shortcmd('STLS')
402n/a self.sock = context.wrap_socket(self.sock,
403n/a server_hostname=self.host)
404n/a self.file = self.sock.makefile('rb')
405n/a self._tls_established = True
406n/a return resp
407n/a
408n/a
409n/aif HAVE_SSL:
410n/a
411n/a class POP3_SSL(POP3):
412n/a """POP3 client class over SSL connection
413n/a
414n/a Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None,
415n/a context=None)
416n/a
417n/a hostname - the hostname of the pop3 over ssl server
418n/a port - port number
419n/a keyfile - PEM formatted file that contains your private key
420n/a certfile - PEM formatted certificate chain file
421n/a context - a ssl.SSLContext
422n/a
423n/a See the methods of the parent class POP3 for more documentation.
424n/a """
425n/a
426n/a def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None,
427n/a timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
428n/a if context is not None and keyfile is not None:
429n/a raise ValueError("context and keyfile arguments are mutually "
430n/a "exclusive")
431n/a if context is not None and certfile is not None:
432n/a raise ValueError("context and certfile arguments are mutually "
433n/a "exclusive")
434n/a if keyfile is not None or certfile is not None:
435n/a import warnings
436n/a warnings.warn("keyfile and certfile are deprecated, use a"
437n/a "custom context instead", DeprecationWarning, 2)
438n/a self.keyfile = keyfile
439n/a self.certfile = certfile
440n/a if context is None:
441n/a context = ssl._create_stdlib_context(certfile=certfile,
442n/a keyfile=keyfile)
443n/a self.context = context
444n/a POP3.__init__(self, host, port, timeout)
445n/a
446n/a def _create_socket(self, timeout):
447n/a sock = POP3._create_socket(self, timeout)
448n/a sock = self.context.wrap_socket(sock,
449n/a server_hostname=self.host)
450n/a return sock
451n/a
452n/a def stls(self, keyfile=None, certfile=None, context=None):
453n/a """The method unconditionally raises an exception since the
454n/a STLS command doesn't make any sense on an already established
455n/a SSL/TLS session.
456n/a """
457n/a raise error_proto('-ERR TLS session already established')
458n/a
459n/a __all__.append("POP3_SSL")
460n/a
461n/aif __name__ == "__main__":
462n/a import sys
463n/a a = POP3(sys.argv[1])
464n/a print(a.getwelcome())
465n/a a.user(sys.argv[2])
466n/a a.pass_(sys.argv[3])
467n/a a.list()
468n/a (numMsgs, totalSize) = a.stat()
469n/a for i in range(1, numMsgs + 1):
470n/a (header, msg, octets) = a.retr(i)
471n/a print("Message %d:" % i)
472n/a for line in msg:
473n/a print(' ' + line)
474n/a print('-----------------------')
475n/a a.quit()