]> git.pld-linux.org Git - packages/awesome-plugin-delightful.git/blame - imap.lua
- initial
[packages/awesome-plugin-delightful.git] / imap.lua
CommitLineData
c70b624e
ZU
1--- Simple IMAP client library.
2--
3-- Copyright 2009 by David Maus <maus.david@gmail.com>
4--
5-- This program is free software: you can redistribute it and/or modify
6-- it under the terms of the GNU General Public License as published by
7-- the Free Software Foundation, either version 3 of the License, or
8-- (at your option) any later version.
9--
10-- This program is distributed in the hope that it will be useful,
11-- but WITHOUT ANY WARRANTY; without even the implied warranty of
12-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13-- GNU General Public License for more details.
14--
15-- You should have received a copy of the GNU General Public License
16-- along with this program. If not, see <http://www.gnu.org/licenses/>.
17--
18-- @release $Revision$
19--
20--
21
22local socket = require("socket")
23local ssl = require("ssl")
24
25local os = os
26local pairs = pairs
27local print = print
28local setmetatable = setmetatable
29local table = table
30
31module("imap")
32
33imap = {}
34imap.__index = imap
35
36--- Default values.
37PORT = 993
38SSL = "sslv3"
39MAILBOX = "Inbox"
40TIMEOUT = 5
41
42--- Create and return new imap client object.
43-- @param server Hostname or ip address of remote server
44-- @param port Port to connect to (Default: 993)
45-- @param ssl_proto SSL/TLS protocol to use (Default: "sslv3")
46-- @param mailbox Mailbox to check (Default: Inbox)
47-- @param timeout Timeout value for TCP connection (Default: 5s)
48-- @return A shiny new imap client control object
49function new(server, port, ssl_proto, mailbox, timeout)
50
51 _imap = {}
52 setmetatable(_imap, imap.__index)
53 _imap.server = server
54 _imap.port = port or PORT
55 _imap.mailbox = mailbox or MAILBOX
56 if ssl_proto == "none" then
57 _imap.ssl = nil
58 else
59 _imap.ssl = ssl_proto or SSL
60 end
61 _imap.timeout = timeout or TIMEOUT
62 _imap.cmd_count = 0
63 _imap.logged_in = false
64
65 return _imap
66
67end
68
69--- Connect to remote server.
70-- @return True in case of success, nil followed by the errormessage in case of failure.
71function imap:connect()
72
73 local res, msg = socket.connect(self.server, self.port)
74 if not res then return nil, msg end
75 self.socket = res
76
77 if self.ssl then
78 res, msg = ssl.wrap(self.socket, { mode = "client", protocol = self.ssl })
79 if not res then return nil, msg end
80 self.socket = res
81 res, msg = self.socket:dohandshake()
82 if not res then return nil, msg end
83 end
84
85 -- set socket timeout
86 self.socket:settimeout(self.timeout)
87
88 return true
89
90end
91
92--- Login using username and password.
93-- @param user Username
94-- @param pass Password
95-- @return True in case of success, nil followed by the errormessage in case of failure.
96function imap:login(user, pass)
97 local res, msg = self:request("LOGIN " .. user .. " \"" .. pass .. "\"")
98 if not res then return nil, msg end
99
100 -- select mailbox
101 local res, msg = self:request("EXAMINE " .. self.mailbox)
102 if not res then return nil, msg end
103
104 self.logged_in = true
105
106 return true
107end
108
109--- Logout.
110-- @return True in case of success, nil followed by the errormessage in case of failure.
111function imap:logout()
112 local res, msg = self:request("LOGOUT")
113 if not res then return nil, msg end
114
115 self.logged_in = false
116
117 return true
118end
119
120--- Send command to server and return answer.
121-- @param command Client command
122-- @param unprefixed If true, don't prefix command and don't increase command counter
123-- @return True in case of success, nil followed by the errormessage in case of failure.
124function imap:request(command, unprefixed)
125
126 -- check if we the socket exists, return error if not
127 if not self.socket then return nil, "Not connected" end
128
129 local prefix = ""
130 if not unprefixed then
131 self.cmd_count = self.cmd_count + 1
132 prefix = "0x0" .. self.cmd_count .. " "
133 end
134
135 local res, msg = self.socket:send(prefix .. command .. "\r\n")
136 if not res then return nil, msg end
137
138 local answer = {}
139
140 local res, msg = self.socket:receive("*l")
141 if not res then return nil, msg end
142 while not res:match(prefix) do
143 table.insert(answer, res)
144 res, msg = self.socket:receive("*l")
145 if not res then return nil, msg end
146 end
147
148 if not res:match(prefix .. "OK ") then
149 return nil, res
150 end
151
152 return true, answer
153end
154
155--- Return number of new messages in mailbox.
156-- @return Number of new messages in case of success, nil followed by the errormessage in case of failure.
157function imap:recent()
158
159 local res, msg = self:request("EXAMINE " .. self.mailbox)
160 if not res then return nil, msg end
161
162 local n = 0
163 local k,v
164 for k,v in pairs(msg) do
165 if v:match("^* %d+ RECENT") then n = v:match("^* (%d+) RECENT") end
166 end
167
168 return n
169
170end
171
172--- Return total number of message in mailbox.
173-- @return Total number of messages in case of success, nil followed by the errormessage in case of failure.
174function imap:total()
175
176 local res, msg = self:request("EXAMINE " .. self.mailbox)
177 if not res then return nil, msg end
178
179 local n = 0
180 local k,v
181 for k,v in pairs(msg) do
182 if v:match("^* %d+ EXISTS") then n = v:match("^* (%d+) EXISTS") end
183 end
184
185 return n
186
187end
188
189--- Return number of unread message in mailbox.
190-- Determining the number of unread messages requires to perform a
191-- SEARCH query.
192-- @return Number of unread messages in case of success, nil followed by the errormessage in case of failure.
193function imap:unread()
194
195 -- perform an EXAMINE to select the mailbox
196 local res, msg = self:request("SEARCH (UNSEEN)")
197 if not res then return nil, msg end
198
199 local n = 0
200 local k,v
201 for k,v in pairs(msg) do
202 if v:match("^* SEARCH %d+") then
203 while v:find("%d+") do
204 local s, e = v:find("%d+")
205
206 n = n + 1
207 v = v:sub(e + 1, #v)
208
209 end
210 end
211 end
212
213 return n
214
215end
216
217--- Check for total number, number of unread and new messages.
218-- @return Table with number of total, new and unread messages in case
219-- of success, nil followed by the errormessage in case of failure.
220function imap:check()
221
222 local messages = { total = 0, unread = 0, recent = 0 }
223
224 local res, msg = self:total()
225 if not res then return nil, msg end
226 messages.total = res
227
228 local res, msg = self:unread()
229 if not res then return nil, msg end
230 messages.unread = res
231
232 local res, msg = self:recent()
233 if not res then return nil, msg end
234 messages.recent = res
235
236 return messages
237
238end
239
240--- Return information about messages in mailbox.
241-- @param recent If true, return information an recent messages (Default: true)
242-- @param unread If true, return information on unread messages (Default: false)
243-- @param total If true, return information on all messages (Default: false)
244-- @return Table with information on all messages that matched the criteria.
245function imap:fetch(recent, unread, total)
246
247 if recent == nil then recent = true end
248 if unread == nil then unread = false end
249 if total == nil then total = false end
250
251 -- build a table with all search queries that we have to issue
252 local query = {}
253 if recent then table.insert(query, "RECENT") end
254 if unread then table.insert(query, "UNSEEN") end
255 if total then query = { "ALL" } end
256
257 local messages = {}
258 local _, q
259 for _, q in pairs(query) do
260 print ("Perform search for: " .. q)
261 local res, msg = self:request("SEARCH (" .. q .. ")")
262 if not res then return nil, msg end
263 local k,v
264 for k,v in pairs(msg) do
265 if v:match("^* SEARCH %d+") then
266 while v:find("%d+") do
267 local s, e = v:find("%d+")
268 local uid = v:sub(s, e)
269
270 messages[uid] = {}
271
272 local r,m = self:request("FETCH " .. uid .. " (FLAGS RFC822.SIZE BODY[HEADER.FIELDS (FROM SUBJECT)])")
273 if not r then return nil, m end
274 local l,w
275 for l,w in pairs(m) do
276 if w:match("RFC822.SIZE %d+") then messages[uid].size = w:match("RFC822.SIZE (%d+)") end
277 if w:match("^From:") then messages[uid].from = w:match("^From:%s+(.*)") end
278 if w:match("^Subject:") then messages[uid].subject = w:match("^Subject:%s+(.*)") end
279 if w:match("FLAGS") then
280 if w:match("\Recent") then messages[uid].recent = true end
281 if not w:match("\Seen") then messages[uid].unread = true end
282 end
283 end
284
285 v = v:sub(e + 1, #v)
286 end
287 end
288 end
289 end
290
291
292 return true, messages
293
294end
This page took 0.100995 seconds and 4 git commands to generate.