347 lines
7.6 KiB
Lua
347 lines
7.6 KiB
Lua
-- Simple pop3 wrapper
|
|
--
|
|
-- @usage
|
|
-- local mbox = pop3.new('pop3s://pop3.yandex.ru')
|
|
--
|
|
-- -- Yandex works only with tls
|
|
-- print('Open: ', mbox:open_tls('***', '***'))
|
|
-- print('NOOP: ', mbox:noop())
|
|
-- print('RETR: ', mbox:retr(1))
|
|
--
|
|
-- -- use message class from lua-pop3
|
|
-- -- https://github.com/moteus/lua-pop3
|
|
-- for k, msg in mbox:messages() do
|
|
-- print"----------------------------------------------"
|
|
-- print("subject: ", msg:subject())
|
|
-- print("from: ", msg:from())
|
|
-- print("to: ", msg:to())
|
|
-- for i,v in ipairs(msg:full_content()) do
|
|
-- if v.text then print(" ", i , "TEXT: ", v.type, #v.text)
|
|
-- else print(" ", i , "FILE: ", v.type, v.file_name or v.name, #v.data) end
|
|
-- end
|
|
-- end
|
|
--
|
|
-- mbox:close()
|
|
--
|
|
|
|
local cURL = require "cURL.safe"
|
|
local find_ca_bundle = require "cURL.utils".find_ca_bundle
|
|
local message do local ok
|
|
ok, message = pcall(require, "pop3.message")
|
|
if not ok then message = nil end
|
|
end
|
|
|
|
local function split(str, sep, plain)
|
|
local b, res = 1, {}
|
|
while b <= #str do
|
|
local e, e2 = string.find(str, sep, b, plain)
|
|
if e then
|
|
table.insert(res, (string.sub(str, b, e-1)))
|
|
b = e2 + 1
|
|
else
|
|
table.insert(res, (string.sub(str, b)))
|
|
break
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
local function pars_response(resp)
|
|
local code, info = string.match(resp,"%s*(%S*)(.*)%s*")
|
|
-- SASL GET ONLY "+"/"-"
|
|
if code == '+OK' or code == '+' then
|
|
return true, info
|
|
elseif code == '-ERR' or code == '-' then
|
|
return false, info
|
|
end
|
|
return nil, resp
|
|
end
|
|
|
|
local function split_2_numbers(data)
|
|
local n1, n2 = string.match(data, "%s*(%S+)%s+(%S+)")
|
|
return tonumber(n1), tonumber(n2)
|
|
end
|
|
|
|
local function split_1_number(data)
|
|
local n1,s= string.match(data, "%s*(%S+)%s*(%S*)")
|
|
return tonumber(n1),s
|
|
end
|
|
|
|
local crln = '\r\n'
|
|
local function writer(cb, ctx)
|
|
local tail
|
|
return function(str)
|
|
-- @check Is libcurl guarantee thant data will be arraived line by line?
|
|
-- if so we can just call `cb`
|
|
if str then
|
|
local t = split(tail and (tail .. str) or str, crln, true)
|
|
if str:sub(-2) == crln then tail = nil
|
|
else tail = table.remove(t) end
|
|
for _, s in ipairs(t) do cb(s, ctx) end
|
|
elseif tail then cb(tail, ctx) end
|
|
end
|
|
end
|
|
|
|
local pop3 = {} do
|
|
pop3.__index = pop3
|
|
|
|
function pop3:new(host)
|
|
return setmetatable({
|
|
_url = assert(host)
|
|
},self)
|
|
end
|
|
|
|
local function open(self, user, password, ssl)
|
|
self._easy, err = cURL.easy{
|
|
url = self._url,
|
|
username = user,
|
|
password = password,
|
|
customrequest = 'NOOP',
|
|
nobody = true,
|
|
headerfunction = function(h) self._response = h end
|
|
}
|
|
if not self._easy then return nil, err end
|
|
|
|
if ssl then
|
|
-- For AVAST
|
|
-- http://www.avast.com/en-eu/faq.php?article=AVKB91#artTitle
|
|
local cainfo, capath = find_ca_bundle('MailShield.crt')
|
|
if not cainfo then
|
|
-- On Windows try find ca_bundle
|
|
cainfo, capath = find_ca_bundle()
|
|
end
|
|
local ok, err = self._easy:setopt{
|
|
use_ssl = cURL.USESSL_ALL,
|
|
cainfo = cainfo,
|
|
capath = capath,
|
|
}
|
|
if not ok then
|
|
self:close()
|
|
return nil, err
|
|
end
|
|
end
|
|
|
|
local ok, err = self._easy:perform()
|
|
if not ok then
|
|
self:close()
|
|
return nil, err
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function exec(self)
|
|
self._response = nil
|
|
local ok, err = self._easy:perform()
|
|
if not ok then return nil, err end
|
|
assert(self._response)
|
|
self._response = self._response:gsub("%s+$", "")
|
|
return self:response()
|
|
end
|
|
|
|
local function exec_no_body(self, cmd, url)
|
|
local ok, err = self._easy:setopt{
|
|
url = self._url .. (url and ("/" .. url) or ""),
|
|
customrequest = cmd or '',
|
|
nobody = true,
|
|
}
|
|
if not ok then return nil, err end
|
|
return exec(self)
|
|
end
|
|
|
|
local function exec_body_cb(self, cb, cmd, url)
|
|
local ok, err = self._easy:setopt{
|
|
url = self._url .. (url and ("/" .. url) or ""),
|
|
customrequest = cmd or '',
|
|
nobody = false,
|
|
writefunction = writer(assert(cb))
|
|
}
|
|
if not ok then return nil, err end
|
|
ok, err = exec(self)
|
|
if not ok then return nil, err end
|
|
return true
|
|
end
|
|
|
|
local function exec_body(self, cmd, url)
|
|
local t = {}
|
|
local ok, err = exec_body_cb(self,
|
|
function(s) t[#t+1] = s end,
|
|
cmd, url
|
|
)
|
|
if not ok then return nil, err end
|
|
return t
|
|
end
|
|
|
|
local function exec_no_body_2_numbers(...)
|
|
local ok, data = exec_no_body(...)
|
|
if not ok then return ok, data end
|
|
return split_2_numbers(data)
|
|
end
|
|
|
|
function pop3:open(user, password)
|
|
return open(self, user, password, false)
|
|
end
|
|
|
|
function pop3:response()
|
|
if self._response then
|
|
return pars_response(self._response)
|
|
end
|
|
end
|
|
|
|
function pop3:verbose(...)
|
|
local ok, err
|
|
if select("#", ...) == 0 then
|
|
ok, err = self._easy:setopt_verbose(true)
|
|
else
|
|
ok, err = self._easy:setopt_verbose(not not ...)
|
|
end
|
|
if not ok then return nil, ok end
|
|
return self
|
|
end
|
|
|
|
function pop3:open_tls(user, password)
|
|
return open(self, user, password, true)
|
|
end
|
|
|
|
function pop3:noop()
|
|
return exec_no_body(self, 'NOOP')
|
|
end
|
|
|
|
function pop3:stat()
|
|
return exec_no_body_2_numbers(self, 'STAT')
|
|
end
|
|
|
|
function pop3:dele(id)
|
|
return exec_no_body(self, 'DELE ' .. id)
|
|
end
|
|
|
|
function pop3:rset()
|
|
return exec_no_body_2_numbers(self, 'RSET')
|
|
end
|
|
|
|
function pop3:list(id)
|
|
if id then
|
|
return exec_no_body_2_numbers(self, 'LIST ' .. id)
|
|
end
|
|
|
|
local t,i = {},0
|
|
local fn = function(data)
|
|
local no, size = split_2_numbers(data)
|
|
if not (no and size) then
|
|
return nil, "Wrong Response: `" .. data .. "`"
|
|
end
|
|
t[no] = size
|
|
i = i + 1
|
|
end
|
|
|
|
local ok, err = exec_body_cb(self, fn, '')
|
|
if not ok then return nil, err end
|
|
if not ok then return nil, err end
|
|
return t, i
|
|
end
|
|
|
|
function pop3:uidl(id)
|
|
if id then
|
|
local ok, data = exec_no_body(self, 'UIDL ' .. id)
|
|
if not ok then return ok, data end
|
|
local no, id = split_1_number(data)
|
|
if not (no and id) then
|
|
return nil, "Wrong Response:" .. data
|
|
end
|
|
return no,id
|
|
end
|
|
|
|
local t,i = {},0
|
|
local fn = function(data)
|
|
local no, id = split_1_number(data)
|
|
if not (no and id) then
|
|
return nil, "Wrong Response:" .. data
|
|
end
|
|
t[no]=id
|
|
i = i + 1
|
|
return true
|
|
end
|
|
|
|
local ok, err = exec_body_cb(self, fn, 'UIDL')
|
|
if not ok then return nil, err end
|
|
if not ok then return nil, err end
|
|
return t, i
|
|
end
|
|
|
|
function pop3:retr(id)
|
|
assert(id)
|
|
return exec_body(self, '', tostring(id))
|
|
end
|
|
|
|
function pop3:top(id, n)
|
|
assert(id)
|
|
assert(type(n) == "number")
|
|
return exec_body(self, 'TOP ' .. id .. ' ' .. n)
|
|
end
|
|
|
|
function pop3:make_iter(fn)
|
|
local lst, err = self:list()
|
|
if not lst then error(err) end
|
|
local k = nil
|
|
|
|
local iter
|
|
iter = function ()
|
|
k = next(lst, k)
|
|
if not k then return nil end
|
|
|
|
-- skip deleted messages ?
|
|
local no, size = self:list(k)
|
|
if no == false then return iter() end -- next message
|
|
if not no then return error(size) end
|
|
assert(no == k)
|
|
|
|
local data, err = fn(self, k, size)
|
|
if not data then error(err) end
|
|
|
|
return k, data
|
|
end
|
|
|
|
return iter
|
|
end
|
|
|
|
if message then
|
|
|
|
function pop3:message(msgid)
|
|
local msg, err = self:retr(msgid)
|
|
if not msg then return nil, err end
|
|
return message(msg)
|
|
end
|
|
|
|
end
|
|
|
|
function pop3:retrs()
|
|
return self:make_iter(self.retr)
|
|
end
|
|
|
|
function pop3:tops(n)
|
|
return self:make_iter(function(self, msgid)
|
|
return self:top(msgid, n)
|
|
end)
|
|
end
|
|
|
|
function pop3:messages()
|
|
return self:make_iter(self.message)
|
|
end
|
|
|
|
function pop3:closed()
|
|
return not not self._easy
|
|
end
|
|
|
|
function pop3:close()
|
|
if self._easy then
|
|
self._easy:close()
|
|
self._easy = false
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
return {
|
|
new = function(...) return pop3:new() end;
|
|
}
|