Imported Upstream version 12.1.0
[contrib/python-twisted.git] / doc / mail / examples / imap4client.py
1 #!/usr/bin/env python
2
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 """
8 Simple IMAP4 client which displays the subjects of all messages in a 
9 particular mailbox.
10 """
11
12 import sys
13
14 from twisted.internet import protocol
15 from twisted.internet import ssl
16 from twisted.internet import defer
17 from twisted.internet import stdio
18 from twisted.mail import imap4
19 from twisted.protocols import basic
20 from twisted.python import util
21 from twisted.python import log
22
23 class TrivialPrompter(basic.LineReceiver):
24     from os import linesep as delimiter
25
26     promptDeferred = None
27     
28     def prompt(self, msg):
29         assert self.promptDeferred is None
30         self.display(msg)
31         self.promptDeferred = defer.Deferred()
32         return self.promptDeferred
33     
34     def display(self, msg):
35         self.transport.write(msg)
36     
37     def lineReceived(self, line):    
38         if self.promptDeferred is None:
39             return
40         d, self.promptDeferred = self.promptDeferred, None
41         d.callback(line)
42
43 class SimpleIMAP4Client(imap4.IMAP4Client):
44     greetDeferred = None
45     
46     def serverGreeting(self, caps):
47         self.serverCapabilities = caps
48         if self.greetDeferred is not None:
49             d, self.greetDeferred = self.greetDeferred, None
50             d.callback(self)
51
52 class SimpleIMAP4ClientFactory(protocol.ClientFactory):
53     usedUp = False
54
55     protocol = SimpleIMAP4Client
56
57     def __init__(self, username, onConn):
58         self.ctx = ssl.ClientContextFactory()
59         
60         self.username = username
61         self.onConn = onConn
62
63     def buildProtocol(self, addr):
64         assert not self.usedUp
65         self.usedUp = True
66         
67         p = self.protocol(self.ctx)
68         p.factory = self
69         p.greetDeferred = self.onConn
70
71         auth = imap4.CramMD5ClientAuthenticator(self.username)
72         p.registerAuthenticator(auth)
73         
74         return p
75     
76     def clientConnectionFailed(self, connector, reason):
77         d, self.onConn = self.onConn, None
78         d.errback(reason)
79
80 # Initial callback - invoked after the server sends us its greet message
81 def cbServerGreeting(proto, username, password):
82     # Hook up stdio
83     tp = TrivialPrompter()
84     stdio.StandardIO(tp)
85     
86     # And make it easily accessible
87     proto.prompt = tp.prompt
88     proto.display = tp.display
89
90     # Try to authenticate securely
91     return proto.authenticate(password
92         ).addCallback(cbAuthentication, proto
93         ).addErrback(ebAuthentication, proto, username, password
94         )
95
96 # Fallback error-handler.  If anything goes wrong, log it and quit.
97 def ebConnection(reason):
98     log.startLogging(sys.stdout)
99     log.err(reason)
100     from twisted.internet import reactor
101     reactor.stop()
102
103 # Callback after authentication has succeeded
104 def cbAuthentication(result, proto):
105     # List a bunch of mailboxes
106     return proto.list("", "*"
107         ).addCallback(cbMailboxList, proto
108         )
109
110 # Errback invoked when authentication fails
111 def ebAuthentication(failure, proto, username, password):
112     # If it failed because no SASL mechanisms match, offer the user the choice
113     # of logging in insecurely.
114     failure.trap(imap4.NoSupportedAuthentication)
115     return proto.prompt("No secure authentication available.  Login insecurely? (y/N) "
116         ).addCallback(cbInsecureLogin, proto, username, password
117         )
118
119 # Callback for "insecure-login" prompt
120 def cbInsecureLogin(result, proto, username, password):
121     if result.lower() == "y":
122         # If they said yes, do it.
123         return proto.login(username, password
124             ).addCallback(cbAuthentication, proto
125             )
126     return defer.fail(Exception("Login failed for security reasons."))
127
128 # Callback invoked when a list of mailboxes has been retrieved
129 def cbMailboxList(result, proto):
130     result = [e[2] for e in result]
131     s = '\n'.join(['%d. %s' % (n + 1, m) for (n, m) in zip(range(len(result)), result)])
132     if not s:
133         return defer.fail(Exception("No mailboxes exist on server!"))
134     return proto.prompt(s + "\nWhich mailbox? [1] "
135         ).addCallback(cbPickMailbox, proto, result
136         )
137
138 # When the user selects a mailbox, "examine" it.
139 def cbPickMailbox(result, proto, mboxes):
140     mbox = mboxes[int(result or '1') - 1]
141     return proto.examine(mbox
142         ).addCallback(cbExamineMbox, proto
143         )
144
145 # Callback invoked when examine command completes.
146 def cbExamineMbox(result, proto):
147     # Retrieve the subject header of every message on the mailbox.
148     return proto.fetchSpecific('1:*',
149                                headerType='HEADER.FIELDS',
150                                headerArgs=['SUBJECT']
151         ).addCallback(cbFetch, proto
152         )
153
154 # Finally, display headers.
155 def cbFetch(result, proto):
156     keys = result.keys()
157     keys.sort()
158     for k in keys:
159         proto.display('%s %s' % (k, result[k][0][2]))
160     return proto.logout()
161
162 PORT = 143
163
164 def main():
165     hostname = raw_input('IMAP4 Server Hostname: ')
166     username = raw_input('IMAP4 Username: ')
167     password = util.getPassword('IMAP4 Password: ')
168     
169     onConn = defer.Deferred(
170         ).addCallback(cbServerGreeting, username, password
171         ).addErrback(ebConnection
172         )
173
174     factory = SimpleIMAP4ClientFactory(username, onConn)
175     
176     from twisted.internet import reactor
177     conn = reactor.connectTCP(hostname, PORT, factory)
178     reactor.run()
179
180 if __name__ == '__main__':
181     main()