aboutsummaryrefslogtreecommitdiff
path: root/src/tp.lua
blob: d8dabc03c81976b95bf2c4407dc8ad9bb23eaa8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
-----------------------------------------------------------------------------
-- Unified SMTP/FTP subsystem
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- Conforming to: RFC 2616, LTN7
-- RCS ID: $Id$
-----------------------------------------------------------------------------
-- make sure LuaSocket is loaded
if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
-- get LuaSocket namespace
local socket = _G[LUASOCKET_LIBNAME] 
if not socket then error('module requires LuaSocket') end
-- create namespace inside LuaSocket namespace
socket.tp  = socket.tp or {}
-- make all module globals fall into namespace
setmetatable(socket.tp, { __index = _G })
setfenv(1, socket.tp)

TIMEOUT = 60

-- tries to get a pattern from the server and closes socket on error
local function try_receiving(sock, pattern)
    local data, message = sock:receive(pattern)
    if not data then sock:close() end
    return data, message
end

-- tries to send data to server and closes socket on error
local function try_sending(sock, data)
    local sent, message = sock:send(data)
    if not sent then sock:close() end
    return sent, message
end

-- gets server reply
local function get_reply(sock)
    local code, current, separator, _
    local line, message = try_receiving(sock)
    local reply = line
    if message then return nil, message end
    _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
    if not code then return nil, "invalid server reply" end
    if separator == "-" then -- reply is multiline
        repeat
            line, message = try_receiving(sock)
            if message then return nil, message end
            _,_, current, separator = string.find(line, "^(%d%d%d)(.)")
            if not current or not separator then 
                return nil, "invalid server reply" 
            end
            reply = reply .. "\n" .. line
        -- reply ends with same code
        until code == current and separator == " " 
    end
    return code, reply
end

-- metatable for sock object
local metatable = { __index = {} }

-- execute the "check" instr
function metatable.__index:check(ok)
    local code, reply = get_reply(self.sock)
    if not code then return nil, reply end
    if type(ok) ~= "function" then
        if type(ok) ~= "table" then ok = {ok} end
        for i, v in ipairs(ok) do
            if string.find(code, v) then return code, reply end
        end
        return nil, reply
    else return ok(code, reply) end
end

function metatable.__index:cmdchk(cmd, arg, ok)
    local code, err = self:command(cmd, arg)
    if not code then return nil, err end
    return self:check(ok)
end

-- execute the "command" instr
function metatable.__index:command(cmd, arg)
    if arg then return try_sending(self.sock, cmd .. " " .. arg.. "\r\n")
    return try_sending(self.sock, cmd .. "\r\n") end
end

function metatable.__index:sink(snk, pat)
    local chunk, err = sock:receive(pat)
    return snk(chunk, err)
end

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)
        if not ret then return nil, err end
    end
end

-- closes the underlying sock
function metatable.__index:close()
    self.sock:close()
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
    sock:settimeout(TIMEOUT)
    return setmetatable({sock = sock}, metatable)
end