11-- Simple pop3 wrapper
22--
33-- @usage
4- -- local mbox = pop3: new('pop3 ://pop3.yandex.ru')
4+ -- local mbox = pop3. new('pop3s ://pop3.yandex.ru')
55--
66-- -- Yandex works only with tls
77-- print('Open: ', mbox:open_tls('***', '***'))
88-- print('NOOP: ', mbox:noop())
99-- print('RETR: ', mbox:retr(1))
1010--
11- -- list = mbox:list()
12- -- for no, size in ipairs(list)do
13- -- ...
11+ -- -- use message class from lua-pop3
12+ -- -- https://github.com/moteus/lua-pop3
13+ -- for k, msg in mbox:messages() do
14+ -- print"----------------------------------------------"
15+ -- print("subject: ", msg:subject())
16+ -- print("from: ", msg:from())
17+ -- print("to: ", msg:to())
18+ -- for i,v in ipairs(msg:full_content()) do
19+ -- if v.text then print(" ", i , "TEXT: ", v.type, #v.text)
20+ -- else print(" ", i , "FILE: ", v.type, v.file_name or v.name, #v.data) end
21+ -- end
1422-- end
1523--
1624-- mbox:close()
1725--
1826
19- local cURL = require " cURL.safe"
20-
27+ local cURL = require " cURL.safe"
2128local find_ca_bundle = require " cURL.utils" .find_ca_bundle
29+ local message do local ok
30+ ok , message = pcall (require , " pop3.message" )
31+ if not ok then message = nil end
32+ end
2233
2334local function split (str , sep , plain )
2435 local b , res = 1 , {}
@@ -35,16 +46,39 @@ local function split(str, sep, plain)
3546 return res
3647end
3748
49+ local function pars_response (resp )
50+ local code , info = string.match (resp ," %s*(%S*)(.*)%s*" )
51+ -- SASL GET ONLY "+"/"-"
52+ if code == ' +OK' or code == ' +' then
53+ return true , info
54+ elseif code == ' -ERR' or code == ' -' then
55+ return false , info
56+ end
57+ return nil , resp
58+ end
59+
60+ local function split_2_numbers (data )
61+ local n1 , n2 = string.match (data , " %s*(%S+)%s+(%S+)" )
62+ return tonumber (n1 ), tonumber (n2 )
63+ end
64+
65+ local function split_1_number (data )
66+ local n1 ,s = string.match (data , " %s*(%S+)%s*(%S*)" )
67+ return tonumber (n1 ),s
68+ end
69+
3870local crln = ' \r\n '
3971local function writer (cb , ctx )
4072 local tail
4173 return function (str )
74+ -- @check Is libcurl guarantee thant data will be arraived line by line?
75+ -- if so we can just call `cb`
4276 if str then
4377 local t = split (tail and (tail .. str ) or str , crln , true )
4478 if str :sub (- 2 ) == crln then tail = nil
4579 else tail = table.remove (t ) end
46- for _ , s in ipairs (t ) do cb (ctx , s ) end
47- elseif tail then cb (ctx , tail ) end
80+ for _ , s in ipairs (t ) do cb (s , ctx ) end
81+ elseif tail then cb (tail , ctx ) end
4882 end
4983end
5084
5993
6094local function open (self , user , password , ssl )
6195 self ._easy , err = cURL .easy {
62- url = self ._url ,
63- username = user ,
64- password = password ,
65- customrequest = ' NOOP' ,
66- nobody = true ,
96+ url = self ._url ,
97+ username = user ,
98+ password = password ,
99+ customrequest = ' NOOP' ,
100+ nobody = true ,
101+ headerfunction = function (h ) self ._response = h end
67102 }
68103 if not self ._easy then return nil , err end
69104
@@ -95,56 +130,202 @@ local function open(self, user, password, ssl)
95130 return self
96131end
97132
98- function pop3 :open (user , password )
99- return open (self , user , password , false )
100- end
101-
102- function pop3 :open_tls (user , password )
103- return open (self , user , password , true )
133+ local function exec (self )
134+ self ._response = nil
135+ local ok , err = self ._easy :perform ()
136+ if not ok then return nil , err end
137+ assert (self ._response )
138+ self ._response = self ._response :gsub (" %s+$" , " " )
139+ return self :response ()
104140end
105141
106- function pop3 : noop ( )
142+ local function exec_no_body ( self , cmd , url )
107143 local ok , err = self ._easy :setopt {
108- url = self ._url ,
109- customrequest = ' NOOP ' ,
144+ url = self ._url .. ( url and ( " / " .. url ) or " " ) ,
145+ customrequest = cmd or ' ' ,
110146 nobody = true ,
111147 }
112148 if not ok then return nil , err end
113- ok , err = self ._easy :perform ()
114- if not ok then return nil , err end
115- return self
149+ return exec (self )
116150end
117151
118- function pop3 :list ()
119- local t = {}
152+ local function exec_body_cb (self , cb , cmd , url )
120153 local ok , err = self ._easy :setopt {
121- url = self ._url ,
122- customrequest = ' ' ,
154+ url = self ._url .. ( url and ( " / " .. url ) or " " ) ,
155+ customrequest = cmd or ' ' ,
123156 nobody = false ,
124- writefunction = writer (function ( t , s ) t [ # t + 1 ] = s end , t )
157+ writefunction = writer (assert ( cb ) )
125158 }
126159 if not ok then return nil , err end
160+ ok , err = exec (self )
161+ if not ok then return nil , err end
162+ return true
163+ end
127164
128- ok , err = self ._easy :perform ()
129-
165+ local function exec_body (self , cmd , url )
166+ local t = {}
167+ local ok , err = exec_body_cb (self ,
168+ function (s ) t [# t + 1 ] = s end ,
169+ cmd , url
170+ )
130171 if not ok then return nil , err end
131172 return t
132173end
133174
134- function pop3 :retr (n )
135- local t = {}
136- local ok , err = self ._easy :setopt {
137- url = self ._url .. ' /' .. n ,
138- customrequest = ' ' ,
139- nobody = false ,
140- writefunction = writer (function (t , s ) t [# t + 1 ] = s end , t )
141- }
175+ local function exec_no_body_2_numbers (...)
176+ local ok , data = exec_no_body (... )
177+ if not ok then return ok , data end
178+ return split_2_numbers (data )
179+ end
180+
181+ function pop3 :open (user , password )
182+ return open (self , user , password , false )
183+ end
184+
185+ function pop3 :response ()
186+ if self ._response then
187+ return pars_response (self ._response )
188+ end
189+ end
190+
191+ function pop3 :verbose (...)
192+ local ok , err
193+ if select (" #" , ... ) == 0 then
194+ ok , err = self ._easy :setopt_verbose (true )
195+ else
196+ ok , err = self ._easy :setopt_verbose (not not ... )
197+ end
198+ if not ok then return nil , ok end
199+ return self
200+ end
201+
202+ function pop3 :open_tls (user , password )
203+ return open (self , user , password , true )
204+ end
205+
206+ function pop3 :noop ()
207+ return exec_no_body (self , ' NOOP' )
208+ end
209+
210+ function pop3 :stat ()
211+ return exec_no_body_2_numbers (self , ' STAT' )
212+ end
213+
214+ function pop3 :dele (id )
215+ return exec_no_body (self , ' DELE ' .. id )
216+ end
217+
218+ function pop3 :rset ()
219+ return exec_no_body_2_numbers (self , ' RSET' )
220+ end
221+
222+ function pop3 :list (id )
223+ if id then
224+ return exec_no_body_2_numbers (self , ' LIST ' .. id )
225+ end
226+
227+ local t ,i = {},0
228+ local fn = function (data )
229+ local no , size = split_2_numbers (data )
230+ if not (no and size ) then
231+ return nil , " Wrong Response: `" .. data .. " `"
232+ end
233+ t [no ] = size
234+ i = i + 1
235+ end
236+
237+ local ok , err = exec_body_cb (self , fn , ' ' )
238+ if not ok then return nil , err end
142239 if not ok then return nil , err end
240+ return t , i
241+ end
143242
144- ok , err = self ._easy :perform ()
243+ function pop3 :uidl (id )
244+ if id then
245+ local ok , data = exec_no_body (self , ' UIDL ' .. id )
246+ if not ok then return ok , data end
247+ local no , id = split_1_number (data )
248+ if not (no and id ) then
249+ return nil , " Wrong Response:" .. data
250+ end
251+ return no ,id
252+ end
145253
254+ local t ,i = {},0
255+ local fn = function (data )
256+ local no , id = split_1_number (data )
257+ if not (no and id ) then
258+ return nil , " Wrong Response:" .. data
259+ end
260+ t [no ]= id
261+ i = i + 1
262+ return true
263+ end
264+
265+ local ok , err = exec_body_cb (self , fn , ' UIDL' )
146266 if not ok then return nil , err end
147- return t
267+ if not ok then return nil , err end
268+ return t , i
269+ end
270+
271+ function pop3 :retr (id )
272+ assert (id )
273+ return exec_body (self , ' ' , tostring (id ))
274+ end
275+
276+ function pop3 :top (id , n )
277+ assert (id )
278+ assert (type (n ) == " number" )
279+ return exec_body (self , ' TOP ' .. id .. ' ' .. n )
280+ end
281+
282+ function pop3 :make_iter (fn )
283+ local lst , err = self :list ()
284+ if not lst then error (err ) end
285+ local k = nil
286+
287+ local iter
288+ iter = function ()
289+ k = next (lst , k )
290+ if not k then return nil end
291+
292+ -- skip deleted messages ?
293+ local no , size = self :list (k )
294+ if no == false then return iter () end -- next message
295+ if not no then return error (size ) end
296+ assert (no == k )
297+
298+ local data , err = fn (self , k , size )
299+ if not data then error (err ) end
300+
301+ return k , data
302+ end
303+
304+ return iter
305+ end
306+
307+ if message then
308+
309+ function pop3 :message (msgid )
310+ local msg , err = self :retr (msgid )
311+ if not msg then return nil , err end
312+ return message (msg )
313+ end
314+
315+ end
316+
317+ function pop3 :retrs ()
318+ return self :make_iter (self .retr )
319+ end
320+
321+ function pop3 :tops (n )
322+ return self :make_iter (function (self , msgid )
323+ return self :top (msgid , n )
324+ end )
325+ end
326+
327+ function pop3 :messages ()
328+ return self :make_iter (self .message )
148329end
149330
150331function pop3 :closed ()
0 commit comments