]> git.pld-linux.org Git - packages/awesome-plugin-delightful.git/blob - imap.lua
- initial
[packages/awesome-plugin-delightful.git] / imap.lua
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
22 local socket = require("socket")
23 local ssl = require("ssl")
24
25 local os = os
26 local pairs = pairs
27 local print = print
28 local setmetatable = setmetatable
29 local table = table
30
31 module("imap")
32
33 imap = {}
34 imap.__index = imap
35
36 --- Default values.
37 PORT = 993
38 SSL = "sslv3"
39 MAILBOX = "Inbox"
40 TIMEOUT = 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
49 function 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
67 end
68
69 --- Connect to remote server.
70 -- @return True in case of success, nil followed by the errormessage in case of failure.
71 function 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
90 end
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.
96 function 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
107 end
108
109 --- Logout.
110 -- @return True in case of success, nil followed by the errormessage in case of failure.
111 function 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
118 end
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.
124 function 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
153 end
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.
157 function 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
170 end
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.
174 function 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
187 end
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.
193 function 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
215 end
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.
220 function 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
238 end
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.
245 function 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
294 end
This page took 0.070183 seconds and 3 git commands to generate.