aboutsummaryrefslogtreecommitdiff
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
parentbcc0c2a9f0be2ca796ef5206a78e283fe15e6186 (diff)
downloadluasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.tar.gz
luasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.tar.bz2
luasocket-2c160627e51650f98d6ef01ae36bb86d6e91045f.zip
Message source in smtp.lua is a work of art.
-rw-r--r--src/ltn12.lua24
-rw-r--r--src/luasocket.c2
-rw-r--r--src/smtp.lua193
-rw-r--r--src/tp.lua54
4 files changed, 118 insertions, 155 deletions
diff --git a/src/ltn12.lua b/src/ltn12.lua
index de7103d..f43e975 100644
--- a/src/ltn12.lua
+++ b/src/ltn12.lua
@@ -46,7 +46,16 @@ function filter.chain(...)
46end 46end
47 47
48-- create an empty source 48-- create an empty source
49function source.empty(err) 49local function empty()
50 return nil
51end
52
53function source.empty()
54 return empty
55end
56
57-- returns a source that just outputs an error
58function source.error(err)
50 return function() 59 return function()
51 return nil, err 60 return nil, err
52 end 61 end
@@ -60,7 +69,7 @@ function source.file(handle, io_err)
60 if not chunk then handle:close() end 69 if not chunk then handle:close() end
61 return chunk 70 return chunk
62 end 71 end
63 else source.empty(io_err or "unable to open file") end 72 else source.error(io_err or "unable to open file") end
64end 73end
65 74
66-- turns a fancy source into a simple source 75-- turns a fancy source into a simple source
@@ -83,7 +92,7 @@ function source.string(s)
83 if chunk ~= "" then return chunk 92 if chunk ~= "" then return chunk
84 else return nil end 93 else return nil end
85 end 94 end
86 else source.empty() end 95 else return source.empty() end
87end 96end
88 97
89-- creates rewindable source 98-- creates rewindable source
@@ -166,7 +175,7 @@ function sink.file(handle, io_err)
166 end 175 end
167 return handle:write(chunk) 176 return handle:write(chunk)
168 end 177 end
169 else sink.null() end 178 else return sink.error(io_err or "unable to open file") end
170end 179end
171 180
172-- creates a sink that discards data 181-- creates a sink that discards data
@@ -178,6 +187,13 @@ function sink.null()
178 return null 187 return null
179end 188end
180 189
190-- creates a sink that just returns an error
191function sink.error(err)
192 return function()
193 return nil, err
194 end
195end
196
181-- chains a sink with a filter 197-- chains a sink with a filter
182function sink.chain(f, snk) 198function sink.chain(f, snk)
183 return function(chunk, err) 199 return function(chunk, err)
diff --git a/src/luasocket.c b/src/luasocket.c
index 5b19696..eadb758 100644
--- a/src/luasocket.c
+++ b/src/luasocket.c
@@ -76,6 +76,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
76#include "auxiliar.lch" 76#include "auxiliar.lch"
77#include "url.lch" 77#include "url.lch"
78#include "mime.lch" 78#include "mime.lch"
79#include "tp.lch"
79#include "smtp.lch" 80#include "smtp.lch"
80#include "http.lch" 81#include "http.lch"
81#else 82#else
@@ -83,6 +84,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
83 lua_dofile(L, "auxiliar.lua"); 84 lua_dofile(L, "auxiliar.lua");
84 lua_dofile(L, "url.lua"); 85 lua_dofile(L, "url.lua");
85 lua_dofile(L, "mime.lua"); 86 lua_dofile(L, "mime.lua");
87 lua_dofile(L, "tp.lua");
86 lua_dofile(L, "smtp.lua"); 88 lua_dofile(L, "smtp.lua");
87 lua_dofile(L, "http.lua"); 89 lua_dofile(L, "http.lua");
88#endif 90#endif
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
diff --git a/src/tp.lua b/src/tp.lua
index d8dabc0..3912fab 100644
--- a/src/tp.lua
+++ b/src/tp.lua
@@ -18,32 +18,18 @@ setfenv(1, socket.tp)
18 18
19TIMEOUT = 60 19TIMEOUT = 60
20 20
21-- tries to get a pattern from the server and closes socket on error
22local function try_receiving(sock, pattern)
23 local data, message = sock:receive(pattern)
24 if not data then sock:close() end
25 return data, message
26end
27
28-- tries to send data to server and closes socket on error
29local function try_sending(sock, data)
30 local sent, message = sock:send(data)
31 if not sent then sock:close() end
32 return sent, message
33end
34
35-- gets server reply 21-- gets server reply
36local function get_reply(sock) 22local function get_reply(sock)
37 local code, current, separator, _ 23 local code, current, separator, _
38 local line, message = try_receiving(sock) 24 local line, err = sock:receive()
39 local reply = line 25 local reply = line
40 if message then return nil, message end 26 if err then return nil, err end
41 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)") 27 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
42 if not code then return nil, "invalid server reply" end 28 if not code then return nil, "invalid server reply" end
43 if separator == "-" then -- reply is multiline 29 if separator == "-" then -- reply is multiline
44 repeat 30 repeat
45 line, message = try_receiving(sock) 31 line, err = sock:receive()
46 if message then return nil, message end 32 if err then return nil, err end
47 _,_, current, separator = string.find(line, "^(%d%d%d)(.)") 33 _,_, current, separator = string.find(line, "^(%d%d%d)(.)")
48 if not current or not separator then 34 if not current or not separator then
49 return nil, "invalid server reply" 35 return nil, "invalid server reply"
@@ -58,29 +44,25 @@ end
58-- metatable for sock object 44-- metatable for sock object
59local metatable = { __index = {} } 45local metatable = { __index = {} }
60 46
61-- execute the "check" instr
62function metatable.__index:check(ok) 47function metatable.__index:check(ok)
63 local code, reply = get_reply(self.sock) 48 local code, reply = get_reply(self.sock)
64 if not code then return nil, reply end 49 if not code then return nil, reply end
65 if type(ok) ~= "function" then 50 if type(ok) ~= "function" then
66 if type(ok) ~= "table" then ok = {ok} end 51 if type(ok) == "table" then
67 for i, v in ipairs(ok) do 52 for i, v in ipairs(ok) do
68 if string.find(code, v) then return code, reply end 53 if string.find(code, v) then return code, reply end
54 end
55 return nil, reply
56 else
57 if string.find(code, ok) then return code, reply
58 else return nil, reply end
69 end 59 end
70 return nil, reply
71 else return ok(code, reply) end 60 else return ok(code, reply) end
72end 61end
73 62
74function metatable.__index:cmdchk(cmd, arg, ok)
75 local code, err = self:command(cmd, arg)
76 if not code then return nil, err end
77 return self:check(ok)
78end
79
80-- execute the "command" instr
81function metatable.__index:command(cmd, arg) 63function metatable.__index:command(cmd, arg)
82 if arg then return try_sending(self.sock, cmd .. " " .. arg.. "\r\n") 64 if arg then return self.sock:send(cmd .. " " .. arg.. "\r\n")
83 return try_sending(self.sock, cmd .. "\r\n") end 65 else return self.sock:send(cmd .. "\r\n") end
84end 66end
85 67
86function metatable.__index:sink(snk, pat) 68function metatable.__index:sink(snk, pat)
@@ -88,6 +70,14 @@ function metatable.__index:sink(snk, pat)
88 return snk(chunk, err) 70 return snk(chunk, err)
89end 71end
90 72
73function metatable.__index:send(data)
74 return self.sock:send(data)
75end
76
77function metatable.__index:receive(pat)
78 return self.sock:receive(pat)
79end
80
91function metatable.__index:source(src, instr) 81function metatable.__index:source(src, instr)
92 while true do 82 while true do
93 local chunk, err = src() 83 local chunk, err = src()