aboutsummaryrefslogtreecommitdiff
path: root/src/smtp.lua
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-18 07:01:14 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-18 07:01:14 +0000
commit2c160627e51650f98d6ef01ae36bb86d6e91045f (patch)
treee67051e051b0a315aebc0a511d242905272aecfb /src/smtp.lua
parentbcc0c2a9f0be2ca796ef5206a78e283fe15e6186 (diff)
downloadluasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.tar.gz
luasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.tar.bz2
luasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.zip
Message source in smtp.lua is a work of art.
Diffstat (limited to 'src/smtp.lua')
-rw-r--r--src/smtp.lua193
1 files changed, 74 insertions, 119 deletions
diff --git a/src/smtp.lua b/src/smtp.lua
index 6b02d14..0bebce3 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -22,140 +22,95 @@ function stuff()
22 return ltn12.filter.cycle(dot, 2) 22 return ltn12.filter.cycle(dot, 2)
23end 23end
24 24
25-- tries to get a pattern from the server and closes socket on error 25local function skip(a, b, c)
26local function try_receiving(connection, pattern) 26 return b, c
27 local data, message = connection:receive(pattern)
28 if not data then connection:close() end
29 print(data)
30 return data, message
31end 27end
32 28
33-- tries to send data to server and closes socket on error 29function psend(control, mailt)
34local function try_sending(connection, data) 30 socket.try(control:command("EHLO", mailt.domain or DOMAIN))
35 local sent, message = connection:send(data) 31 socket.try(control:check("2.."))
36 if not sent then connection:close() end 32 socket.try(control:command("MAIL", "FROM:" .. mailt.from))
37 io.write(data) 33 socket.try(control:check("2.."))
38 return sent, message 34 if type(mailt.rcpt) == "table" then
39end 35 for i,v in ipairs(mailt.rcpt) do
40 36 socket.try(control:command("RCPT", "TO:" .. v))
41-- gets server reply 37 end
42local function get_reply(connection)
43 local code, current, separator, _
44 local line, message = try_receiving(connection)
45 local reply = line
46 if message then return nil, message end
47 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
48 if not code then return nil, "invalid server reply" end
49 if separator == "-" then -- reply is multiline
50 repeat
51 line, message = try_receiving(connection)
52 if message then return nil, message end
53 _,_, current, separator = string.find(line, "^(%d%d%d)(.)")
54 if not current or not separator then
55 return nil, "invalid server reply"
56 end
57 reply = reply .. "\n" .. line
58 -- reply ends with same code
59 until code == current and separator == " "
60 end
61 return code, reply
62end
63
64-- metatable for server connection object
65local metatable = { __index = {} }
66
67-- switch handler for execute function
68local switch = {}
69
70-- execute the "check" instruction
71function switch.check(connection, instruction)
72 local code, reply = get_reply(connection)
73 if not code then return nil, reply end
74 if type(instruction.check) == "function" then
75 return instruction.check(code, reply)
76 else 38 else
77 if string.find(code, instruction.check) then return code, reply 39 socket.try(control:command("RCPT", "TO:" .. mailt.rcpt))
78 else return nil, reply end
79 end 40 end
41 socket.try(control:check("2.."))
42 socket.try(control:command("DATA"))
43 socket.try(control:check("3.."))
44 socket.try(control:source(ltn12.source.chain(mailt.source, stuff())))
45 socket.try(control:send("\r\n.\r\n"))
46 socket.try(control:check("2.."))
47 socket.try(control:command("QUIT"))
48 socket.try(control:check("2.."))
80end 49end
81 50
82-- stub for invalid instructions 51local seqno = 0
83function switch.invalid(connection, instruction) 52local function newboundary()
84 return nil, "invalid instruction" 53 seqno = seqno + 1
85end 54 return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'),
86 55 math.random(0, 99999), seqno)
87-- execute the "command" instruction
88function switch.command(connection, instruction)
89 local line
90 if instruction.argument then
91 line = instruction.command .. " " .. instruction.argument .. "\r\n"
92 else line = instruction.command .. "\r\n" end
93 return try_sending(connection, line)
94end 56end
95 57
96function switch.raw(connection, instruction) 58local function sendmessage(mesgt)
97 if type(instruction.raw) == "function" then 59 -- send headers
98 local f = instruction.raw 60 if mesgt.headers then
61 for i,v in pairs(mesgt.headers) do
62 coroutine.yield(i .. ':' .. v .. "\r\n")
63 end
64 end
65 -- deal with multipart
66 if type(mesgt.body) == "table" then
67 local bd = newboundary()
68 -- define boundary and finish headers
69 coroutine.yield('mime-version: 1.0\r\n')
70 coroutine.yield('content-type: multipart/mixed; boundary="' ..
71 bd .. '"\r\n\r\n')
72 -- send preamble
73 if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) end
74 -- send each part separated by a boundary
75 for i, m in ipairs(mesgt.body) do
76 coroutine.yield("\r\n--" .. bd .. "\r\n")
77 sendmessage(m)
78 end
79 -- send last boundary
80 coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n")
81 -- send epilogue
82 if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) end
83 -- deal with a source
84 elseif type(mesgt.body) == "function" then
85 -- finish headers
86 coroutine.yield("\r\n")
99 while true do 87 while true do
100 local chunk, new_f = f() 88 local chunk, err = mesgt.body()
101 if not chunk then return nil, new_f end 89 if err then return nil, err
102 if chunk == "" then return true end 90 elseif chunk then coroutine.yield(chunk)
103 f = new_f or f 91 else break end
104 local code, message = try_sending(connection, chunk)
105 if not code then return nil, message end
106 end 92 end
107 else return try_sending(connection, instruction.raw) end 93 -- deal with a simple string
108end 94 else
109 95 -- finish headers
110-- finds out what instruction are we dealing with 96 coroutine.yield("\r\n")
111local function instruction_type(instruction) 97 coroutine.yield(mesgt.body)
112 if type(instruction) ~= "table" then return "invalid" end
113 if instruction.command then return "command" end
114 if instruction.check then return "check" end
115 if instruction.raw then return "raw" end
116 return "invalid"
117end
118
119-- execute a list of instructions
120function metatable.__index:execute(instructions)
121 if type(instructions) ~= "table" then error("instruction expected", 1) end
122 if not instructions[1] then instructions = { instructions } end
123 local code, message
124 for _, instruction in ipairs(instructions) do
125 local type = instruction_type(instruction)
126 code, message = switch[type](self.connection, instruction)
127 if not code then break end
128 end 98 end
129 return code, message
130end 99end
131 100
132-- closes the underlying connection 101function message(mesgt)
133function metatable.__index:close() 102 local co = coroutine.create(function() sendmessage(mesgt) end)
134 self.connection:close() 103 return function() return skip(coroutine.resume(co)) end
135end 104end
136 105
137-- connect with server and return a smtp connection object 106function send(mailt)
138function connect(host) 107 local control, err = socket.tp.connect(mailt.server or SERVER,
139 local connection, message = socket.connect(host, PORT) 108 mailt.port or PORT)
140 if not connection then return nil, message end 109 if not control then return nil, err end
141 return setmetatable({ connection = connection }, metatable) 110 local status, err = pcall(psend, control, mailt)
111 control:close()
112 if status then return true
113 else return nil, err end
142end 114end
143 115
144-- simple test drive
145
146--[[
147c, m = connect("localhost")
148assert(c, m)
149assert(c:execute {check = "2.." })
150assert(c:execute {{command = "EHLO", argument = "localhost"}, {check = "2.."}})
151assert(c:execute {command = "MAIL", argument = "FROM:<diego@princeton.edu>"})
152assert(c:execute {check = "2.."})
153assert(c:execute {command = "RCPT", argument = "TO:<diego@cs.princeton.edu>"})
154assert(c:execute {check = function (code) return code == "250" end})
155assert(c:execute {{command = "DATA"}, {check = "3.."}})
156assert(c:execute {{raw = "This is the message\r\n.\r\n"}, {check = "2.."}})
157assert(c:execute {{command = "QUIT"}, {check = "2.."}})
158c:close()
159]]
160
161return smtp 116return smtp