From e896454e6c7fb574f405729e1ce94c223578e078 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 19 Mar 2004 05:04:03 +0000 Subject: [PATCH] Seems to be working. --- src/ltn12.lua | 10 ++-- src/mime.lua | 28 +++++----- src/smtp.lua | 138 ++++++++++++++++++++++++++++++++------------------ src/tp.lua | 4 +- 4 files changed, 111 insertions(+), 69 deletions(-) diff --git a/src/ltn12.lua b/src/ltn12.lua index f43e975..ef6247d 100644 --- a/src/ltn12.lua +++ b/src/ltn12.lua @@ -16,7 +16,7 @@ local function second(a, b) return b end -local function skip(a, b, c) +local function shift(a, b, c) return b, c end @@ -69,7 +69,7 @@ function source.file(handle, io_err) if not chunk then handle:close() end return chunk end - else source.error(io_err or "unable to open file") end + else return source.error(io_err or "unable to open file") end end -- turns a fancy source into a simple source @@ -114,6 +114,7 @@ function source.chain(src, f) local co = coroutine.create(function() while true do local chunk, err = src() + if err then return nil, err end local filtered = f(chunk) local done = chunk and "" while true do @@ -121,11 +122,10 @@ function source.chain(src, f) if filtered == done then break end filtered = f(done) end - if not chunk then return nil, err end end end) return function() - return skip(coroutine.resume(co)) + return shift(coroutine.resume(co)) end end @@ -141,7 +141,7 @@ function source.cat(...) end end) return source.simplify(function() - return second(coroutine.resume(co)) + return shift(coroutine.resume(co)) end) end diff --git a/src/mime.lua b/src/mime.lua index 6db832d..8c2a5c0 100644 --- a/src/mime.lua +++ b/src/mime.lua @@ -6,9 +6,9 @@ setmetatable(mime, { __index = _G }) setfenv(1, mime) -- encode, decode and wrap algorithm tables -local et = {} -local dt = {} -local wt = {} +encodet = {} +decodet = {} +wrapt = {} -- creates a function that chooses a filter by name from a given table local function choose(table) @@ -20,40 +20,40 @@ local function choose(table) end -- define the encoding filters -et['base64'] = function() +encodet['base64'] = function() return ltn12.filter.cycle(b64, "") end -et['quoted-printable'] = function(mode) +encodet['quoted-printable'] = function(mode) return ltn12.filter.cycle(qp, "", (mode == "binary") and "=0D=0A" or "\13\10") end -- define the decoding filters -dt['base64'] = function() +decodet['base64'] = function() return ltn12.filter.cycle(unb64, "") end -dt['quoted-printable'] = function() +decodet['quoted-printable'] = function() return ltn12.filter.cycle(unqp, "") end -- define the line-wrap filters -wt['text'] = function(length) +wrapt['text'] = function(length) length = length or 76 return ltn12.filter.cycle(wrp, length, length) end -wt['base64'] = wt['text'] +wrapt['base64'] = wrapt['text'] -wt['quoted-printable'] = function() +wrapt['quoted-printable'] = function() return ltn12.filter.cycle(qpwrp, 76, 76) end -- function that choose the encoding, decoding or wrap algorithm -encode = choose(et) -decode = choose(dt) --- there is different because there is a default wrap filter -local cwt = choose(wt) +encode = choose(encodet) +decode = choose(decodet) +-- it's different because there is a default wrap filter +local cwt = choose(wrapt) function wrap(mode_or_length, length) if type(mode_or_length) ~= "string" then length = mode_or_length diff --git a/src/smtp.lua b/src/smtp.lua index 0bebce3..d256388 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -10,23 +10,27 @@ socket.smtp = smtp setmetatable(smtp, { __index = _G }) setfenv(1, smtp) +-- default server used to send e-mails +SERVER = "localhost" -- default port PORT = 25 -- domain used in HELO command and default sendmail -- If we are under a CGI, try to get from environment DOMAIN = os.getenv("SERVER_NAME") or "localhost" --- default server used to send e-mails -SERVER = "localhost" +-- default time zone (means we don't know) +ZONE = "-0000" function stuff() return ltn12.filter.cycle(dot, 2) end -local function skip(a, b, c) +local function shift(a, b, c) return b, c end +-- send message or throw an exception function psend(control, mailt) + socket.try(control:check("2..")) socket.try(control:command("EHLO", mailt.domain or DOMAIN)) socket.try(control:check("2..")) socket.try(control:command("MAIL", "FROM:" .. mailt.from)) @@ -34,11 +38,12 @@ function psend(control, mailt) if type(mailt.rcpt) == "table" then for i,v in ipairs(mailt.rcpt) do socket.try(control:command("RCPT", "TO:" .. v)) + socket.try(control:check("2..")) end else socket.try(control:command("RCPT", "TO:" .. mailt.rcpt)) + socket.try(control:check("2..")) end - socket.try(control:check("2..")) socket.try(control:command("DATA")) socket.try(control:check("3..")) socket.try(control:source(ltn12.source.chain(mailt.source, stuff()))) @@ -48,6 +53,7 @@ function psend(control, mailt) socket.try(control:check("2..")) end +-- returns a hopefully unique mime boundary local seqno = 0 local function newboundary() seqno = seqno + 1 @@ -55,62 +61,98 @@ local function newboundary() math.random(0, 99999), seqno) end -local function sendmessage(mesgt) - -- send headers +-- sendmessage forward declaration +local sendmessage + +-- yield multipart message body from a multipart message table +local function sendmultipart(mesgt) + local bd = newboundary() + -- define boundary and finish headers + coroutine.yield('content-type: multipart/mixed; boundary="' .. + bd .. '"\r\n\r\n') + -- send preamble + if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) end + -- send each part separated by a boundary + for i, m in ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + sendmessage(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) end +end + +-- yield message body from a source +local function sendsource(mesgt) + -- set content-type if user didn't override + if not mesgt.headers or not mesgt.headers["content-type"] then + coroutine.yield('content-type: text/plain; charset="iso-88591"\r\n') + end + -- finish headers + coroutine.yield("\r\n") + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function sendstring(mesgt) + -- set content-type if user didn't override + if not mesgt.headers or not mesgt.headers["content-type"] then + coroutine.yield('content-type: text/plain; charset="iso-88591"\r\n') + end + -- finish headers + coroutine.yield("\r\n") + -- send body from string + coroutine.yield(mesgt.body) + +end + +-- yield the headers one by one +local function sendheaders(mesgt) if mesgt.headers then for i,v in pairs(mesgt.headers) do coroutine.yield(i .. ':' .. v .. "\r\n") end end - -- deal with multipart - if type(mesgt.body) == "table" then - local bd = newboundary() - -- define boundary and finish headers - coroutine.yield('mime-version: 1.0\r\n') - coroutine.yield('content-type: multipart/mixed; boundary="' .. - bd .. '"\r\n\r\n') - -- send preamble - if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) end - -- send each part separated by a boundary - for i, m in ipairs(mesgt.body) do - coroutine.yield("\r\n--" .. bd .. "\r\n") - sendmessage(m) - end - -- send last boundary - coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") - -- send epilogue - if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) end - -- deal with a source - elseif type(mesgt.body) == "function" then - -- finish headers - coroutine.yield("\r\n") - while true do - local chunk, err = mesgt.body() - if err then return nil, err - elseif chunk then coroutine.yield(chunk) - else break end - end - -- deal with a simple string - else - -- finish headers - coroutine.yield("\r\n") - coroutine.yield(mesgt.body) - end +end + +-- message source +function sendmessage(mesgt) + sendheaders(mesgt) + if type(mesgt.body) == "table" then sendmultipart(mesgt) + elseif type(mesgt.body) == "function" then sendsource(mesgt) + else sendstring(mesgt) end +end + +-- set defaul headers +local function adjustheaders(mesgt) + mesgt.headers = mesgt.headers or {} + mesgt.headers["mime-version"] = "1.0" + mesgt.headers["date"] = mesgt.headers["date"] or + os.date("%a, %d %b %Y %H:%M:%S") .. (mesgt.zone or ZONE) + mesgt.headers["x-mailer"] = mesgt.headers["x-mailer"] or socket.version end function message(mesgt) + adjustheaders(mesgt) + -- create and return message source local co = coroutine.create(function() sendmessage(mesgt) end) - return function() return skip(coroutine.resume(co)) end + return function() return shift(coroutine.resume(co)) end end function send(mailt) - local control, err = socket.tp.connect(mailt.server or SERVER, - mailt.port or PORT) - if not control then return nil, err end - local status, err = pcall(psend, control, mailt) - control:close() - if status then return true - else return nil, err end + local c, e = socket.tp.connect(mailt.server or SERVER, mailt.port or PORT) + if not c then return nil, e end + local s, e = pcall(psend, c, mailt) + c:close() + if s then return true + else return nil, e end end return smtp diff --git a/src/tp.lua b/src/tp.lua index 3912fab..9365255 100644 --- a/src/tp.lua +++ b/src/tp.lua @@ -82,7 +82,7 @@ function metatable.__index:source(src, instr) while true do local chunk, err = src() if not chunk then return not err, err end - local ret, err = try_sending(self.sock, chunk) + local ret, err = self.sock:send(chunk) if not ret then return nil, err end end end @@ -95,7 +95,7 @@ end -- connect with server and return sock object function connect(host, port) local sock, err = socket.connect(host, port) - if not sock then return nil, message end + if not sock then return nil, err end sock:settimeout(TIMEOUT) return setmetatable({sock = sock}, metatable) end