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 /src/smtp.lua | |
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.
Diffstat (limited to 'src/smtp.lua')
-rw-r--r-- | src/smtp.lua | 193 |
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) |
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 |