IMAP Library
If you’re new to the IMAP protocol, take a look at https://interfaceware.atlassian.net/wiki/spaces/EC/pages/2690908644 first!
The IMAP Library is used to interact with the email server using a pure Lua implementation of the IMAP protocol.
The IMAPclient uses https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2691891227 to make a simple IMAP adapter with discrete methods for different operations.
IMAPclient is https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2691891281 to pass all details required to authenticate with the email server.
To begin the TCP conversation, the IMAPclient initializes the TCP connection, gets the connection response and calls IMAPcommand to login and authenticate with the server.
https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2685534228 and the error() function are used to implement error handling for missing authentication details or a failed login attempt.
If the login is successful, the resultant meta table containing the starting Id, TCP connection and the IMAP methods is returned.
require 'IMAP.IMAPcommand'
local MetaTable = {}
MetaTable.__index = {}
MetaTable.__index.selectInbox = require 'IMAP.IMAPselectInbox'
MetaTable.__index.fetch = require 'IMAP.IMAPfetch'
MetaTable.__index.close = require 'IMAP.IMAPclose'
MetaTable.__index.fetchSummary = require 'IMAP.IMAPfetchSummary'
MetaTable.__index.fetchBatch = require 'IMAP.IMAPfetchBatch'
function IMAPclient(T)
local Result = {}
if T.email == "" or T.password == "" then
error("Missing login details", 2)
end
setmetatable(Result, MetaTable)
Result.Id = 0;
Result.Conn = net.tcp.connect{host=T.host, port=993, secure=true}
local Response, Success = IMAPreadResponse(Result.Conn, '')
local Command = "LOGIN "..T.email.." "..T.password:gsub(" ", "")
local Success = IMAPcommand(Result, Command)
trace(Success)
if (Success ~= 'OK') then
error("Failed login", 2)
end
return Result
endIMAPcommand is passed the connection and IMAP command as arguments. The command is sent with an incremented ID to manage the client-server interactions. The ID and command are formated by https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2689335358 together.
function IMAPcommand(C, Command)
C.Id = C.Id + 1
local ID = "a0"..C.Id
C.Conn:send(ID.." "..Command.."\r\n")
local Response, Ended = IMAPreadResponse(C.Conn, ID)
return Ended, ID, Response
endThe response is received and read by the IMAPreadResponse function. Anhttps://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2698543117 is used to continually receive responses until IMAPended returns an IMAP response code that does not equal nil.
function IMAPreadResponse(Conn, ID)
local Response = ''
while (true) do
Response = Response..Conn:recv()
local Ended = IMAPended(Response, ID)
if Ended ~= nil then
return Response,Ended
end
end
endThe IMAPended function is called by IMAPreadResponse to String:find()Preview and return the IMAP response code - OK, NO, or BAD.
function IMAPended(Response, ID)
if (Response:find(ID..' OK')) then
return "OK"
elseif Response:find(ID..' NO') then
return "NO"
elseif Response:find(ID..' BAD') then
return "BAD"
end
return nil
endIMAPselectInbox using the SELECT command to choose the INBOX to query. The IMAPhighestId function takes the response of the SELECT command, and uses https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2684322609to split the response into a https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2684486393. Using a https://interfaceware.atlassian.net/wiki/spaces/IXB/pages/2692120634 and the # Operator on Lua TablesPreview, each line of the response is checked to find the position of “EXISTS“ and return the extracted number value representing the number of emails in the Inbox.
local function IMAPhighestId(T)
local Lines = T:split("\n")
for i=1, #Lines do
if Lines[i]:find('EXISTS') then
return tonumber(Lines[i]:split(" ")[2])
end
end
end
local function IMAPselectInbox(C)
local R, ID, Response = IMAPcommand(C, "SELECT INBOX")
return IMAPhighestId(Response)
end
return IMAPselectInboxIMAPfetch gets the email body. The connection and command arguments are passed to IMAPcommand to send the command. To get the email body we are using FETCH with MailId and the “BODY.PEEK[]“ command.
String:find()Preview is used on the IMAP response, to find the positions of the first and last line that wrap the email body, which we want to capture.
String:sub()Preview extracts and returns the body content between the first and last line positions.
local function IMAPfetch(C, MailId)
local Success, ID, R = IMAPcommand(C, "FETCH "..MailId.." BODY.PEEK[]")
local FirstLine = R:find("\r\n")+2
local LastLine = R:find(ID)
return R:sub(FirstLine, LastLine-4)
end
return IMAPfetchIMAPfetchSummary gets the email header. The connection and command arguments are passed to IMAPcommand to send the command. It uses the same pattern matching strategy as described above in IMAPfetch, however this time is capturing and returning the email header information with the “BODY.PEEK[HEADER]“ command.
local function IMAPfetchSummary(C, MailId)
local Success, ID, R = IMAPcommand(C, "FETCH "..MailId.." BODY.PEEK[HEADER]")
local FirstLine = R:find("\r\n")+2
local LastLine = R:find(ID)
return R:sub(FirstLine, LastLine-4)
end
return IMAPfetchSummaryIMAPfetchBatch can be used to get a batch of emails - this can help with improving processing performance when you have a large volume of data. This function operates similarly to the other fetch modules, however is using the first and last MailId to be processed and the “BODY.PEEK[]” command to get the headers and bodies from the specified batch of emails.
local function IMAPfetchBatch(C, FirstMailId, LastMailId)
local Success, ID, R = IMAPcommand(C, "FETCH "..FirstMailId..":"..LastMailId.." BODY.PEEK[]")
local FirstLine = R:find("\r\n")+2
local LastLine = R:find(ID)
return R:sub(FirstLine, LastLine-4)
end
return IMAPfetchBatchSimple, but important function using Closing File ConnectionsPreview to close the TCP connection.
local function IMAPclose(C)
C.Conn:close()
end
return IMAPcloseBy separating out the IMAP operations, the IMAP Library becomes very extensible!