diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-03-18 07:01:14 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-03-18 07:01:14 +0000 |
commit | 2c160627e51650f98d6ef01ae36bb86d6e91045f (patch) | |
tree | e67051e051b0a315aebc0a511d242905272aecfb | |
parent | bcc0c2a9f0be2ca796ef5206a78e283fe15e6186 (diff) | |
download | luasocket-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.lua | 24 | ||||
-rw-r--r-- | src/luasocket.c | 2 | ||||
-rw-r--r-- | src/smtp.lua | 193 | ||||
-rw-r--r-- | src/tp.lua | 54 |
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(...) | |||
46 | end | 46 | end |
47 | 47 | ||
48 | -- create an empty source | 48 | -- create an empty source |
49 | function source.empty(err) | 49 | local function empty() |
50 | return nil | ||
51 | end | ||
52 | |||
53 | function source.empty() | ||
54 | return empty | ||
55 | end | ||
56 | |||
57 | -- returns a source that just outputs an error | ||
58 | function 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 |
64 | end | 73 | end |
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 |
87 | end | 96 | end |
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 |
170 | end | 179 | end |
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 |
179 | end | 188 | end |
180 | 189 | ||
190 | -- creates a sink that just returns an error | ||
191 | function sink.error(err) | ||
192 | return function() | ||
193 | return nil, err | ||
194 | end | ||
195 | end | ||
196 | |||
181 | -- chains a sink with a filter | 197 | -- chains a sink with a filter |
182 | function sink.chain(f, snk) | 198 | function 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) |
23 | end | 23 | end |
24 | 24 | ||
25 | -- tries to get a pattern from the server and closes socket on error | 25 | local function skip(a, b, c) |
26 | local 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 | ||
31 | end | 27 | end |
32 | 28 | ||
33 | -- tries to send data to server and closes socket on error | 29 | function psend(control, mailt) |
34 | local 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 |
39 | end | 35 | for i,v in ipairs(mailt.rcpt) do |
40 | 36 | socket.try(control:command("RCPT", "TO:" .. v)) | |
41 | -- gets server reply | 37 | end |
42 | local 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 | ||
62 | end | ||
63 | |||
64 | -- metatable for server connection object | ||
65 | local metatable = { __index = {} } | ||
66 | |||
67 | -- switch handler for execute function | ||
68 | local switch = {} | ||
69 | |||
70 | -- execute the "check" instruction | ||
71 | function 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..")) | ||
80 | end | 49 | end |
81 | 50 | ||
82 | -- stub for invalid instructions | 51 | local seqno = 0 |
83 | function switch.invalid(connection, instruction) | 52 | local function newboundary() |
84 | return nil, "invalid instruction" | 53 | seqno = seqno + 1 |
85 | end | 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 | ||
88 | function 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) | ||
94 | end | 56 | end |
95 | 57 | ||
96 | function switch.raw(connection, instruction) | 58 | local 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 |
108 | end | 94 | else |
109 | 95 | -- finish headers | |
110 | -- finds out what instruction are we dealing with | 96 | coroutine.yield("\r\n") |
111 | local 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" | ||
117 | end | ||
118 | |||
119 | -- execute a list of instructions | ||
120 | function 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 | ||
130 | end | 99 | end |
131 | 100 | ||
132 | -- closes the underlying connection | 101 | function message(mesgt) |
133 | function 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 |
135 | end | 104 | end |
136 | 105 | ||
137 | -- connect with server and return a smtp connection object | 106 | function send(mailt) |
138 | function 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 | ||
142 | end | 114 | end |
143 | 115 | ||
144 | -- simple test drive | ||
145 | |||
146 | --[[ | ||
147 | c, m = connect("localhost") | ||
148 | assert(c, m) | ||
149 | assert(c:execute {check = "2.." }) | ||
150 | assert(c:execute {{command = "EHLO", argument = "localhost"}, {check = "2.."}}) | ||
151 | assert(c:execute {command = "MAIL", argument = "FROM:<diego@princeton.edu>"}) | ||
152 | assert(c:execute {check = "2.."}) | ||
153 | assert(c:execute {command = "RCPT", argument = "TO:<diego@cs.princeton.edu>"}) | ||
154 | assert(c:execute {check = function (code) return code == "250" end}) | ||
155 | assert(c:execute {{command = "DATA"}, {check = "3.."}}) | ||
156 | assert(c:execute {{raw = "This is the message\r\n.\r\n"}, {check = "2.."}}) | ||
157 | assert(c:execute {{command = "QUIT"}, {check = "2.."}}) | ||
158 | c:close() | ||
159 | ]] | ||
160 | |||
161 | return smtp | 116 | return smtp |
@@ -18,32 +18,18 @@ setfenv(1, socket.tp) | |||
18 | 18 | ||
19 | TIMEOUT = 60 | 19 | TIMEOUT = 60 |
20 | 20 | ||
21 | -- tries to get a pattern from the server and closes socket on error | ||
22 | local function try_receiving(sock, pattern) | ||
23 | local data, message = sock:receive(pattern) | ||
24 | if not data then sock:close() end | ||
25 | return data, message | ||
26 | end | ||
27 | |||
28 | -- tries to send data to server and closes socket on error | ||
29 | local function try_sending(sock, data) | ||
30 | local sent, message = sock:send(data) | ||
31 | if not sent then sock:close() end | ||
32 | return sent, message | ||
33 | end | ||
34 | |||
35 | -- gets server reply | 21 | -- gets server reply |
36 | local function get_reply(sock) | 22 | local 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 |
59 | local metatable = { __index = {} } | 45 | local metatable = { __index = {} } |
60 | 46 | ||
61 | -- execute the "check" instr | ||
62 | function metatable.__index:check(ok) | 47 | function 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 |
72 | end | 61 | end |
73 | 62 | ||
74 | function 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) | ||
78 | end | ||
79 | |||
80 | -- execute the "command" instr | ||
81 | function metatable.__index:command(cmd, arg) | 63 | function 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 |
84 | end | 66 | end |
85 | 67 | ||
86 | function metatable.__index:sink(snk, pat) | 68 | function 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) |
89 | end | 71 | end |
90 | 72 | ||
73 | function metatable.__index:send(data) | ||
74 | return self.sock:send(data) | ||
75 | end | ||
76 | |||
77 | function metatable.__index:receive(pat) | ||
78 | return self.sock:receive(pat) | ||
79 | end | ||
80 | |||
91 | function metatable.__index:source(src, instr) | 81 | function metatable.__index:source(src, instr) |
92 | while true do | 82 | while true do |
93 | local chunk, err = src() | 83 | local chunk, err = src() |