diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
| commit | 888496aa821cd09d925046250ea98b1512293fd5 (patch) | |
| tree | 217e2b532762a64b58c4fffcb2cba08ba2243462 /src | |
| parent | 4fc164b8eaa0453050a0a859321c327bb2c4f776 (diff) | |
| download | luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.gz luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.bz2 luasocket-888496aa821cd09d925046250ea98b1512293fd5.zip | |
FTP low-level working.
SMTP connection oriented working.
ltn12 improved.
Diffstat (limited to 'src')
| -rw-r--r-- | src/auxiliar.c | 38 | ||||
| -rw-r--r-- | src/ftp.lua | 557 | ||||
| -rw-r--r-- | src/http.lua | 14 | ||||
| -rw-r--r-- | src/ltn12.lua | 46 | ||||
| -rw-r--r-- | src/luasocket.c | 29 | ||||
| -rw-r--r-- | src/mime.c | 50 | ||||
| -rw-r--r-- | src/select.c | 25 | ||||
| -rw-r--r-- | src/smtp.lua | 90 | ||||
| -rw-r--r-- | src/tcp.c | 3 | ||||
| -rw-r--r-- | src/tp.lua | 76 | ||||
| -rw-r--r-- | src/udp.c | 2 |
11 files changed, 287 insertions, 643 deletions
diff --git a/src/auxiliar.c b/src/auxiliar.c index fe21d08..b1f9203 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c | |||
| @@ -14,27 +14,6 @@ | |||
| 14 | * Exported functions | 14 | * Exported functions |
| 15 | \*=========================================================================*/ | 15 | \*=========================================================================*/ |
| 16 | /*-------------------------------------------------------------------------*\ | 16 | /*-------------------------------------------------------------------------*\ |
| 17 | * Prints the value of a class in a nice way | ||
| 18 | \*-------------------------------------------------------------------------*/ | ||
| 19 | int aux_meth_tostring(lua_State *L) | ||
| 20 | { | ||
| 21 | char buf[32]; | ||
| 22 | if (!lua_getmetatable(L, 1)) goto error; | ||
| 23 | lua_pushstring(L, "__index"); | ||
| 24 | lua_gettable(L, -2); | ||
| 25 | if (!lua_istable(L, -1)) goto error; | ||
| 26 | lua_pushstring(L, "class"); | ||
| 27 | lua_gettable(L, -2); | ||
| 28 | if (!lua_isstring(L, -1)) goto error; | ||
| 29 | sprintf(buf, "%p", lua_touserdata(L, 1)); | ||
| 30 | lua_pushfstring(L, "socket: %s: %s", lua_tostring(L, -1), buf); | ||
| 31 | return 1; | ||
| 32 | error: | ||
| 33 | lua_pushnil(L); | ||
| 34 | return 1; | ||
| 35 | } | ||
| 36 | |||
| 37 | /*-------------------------------------------------------------------------*\ | ||
| 38 | * Initializes the module | 17 | * Initializes the module |
| 39 | \*-------------------------------------------------------------------------*/ | 18 | \*-------------------------------------------------------------------------*/ |
| 40 | int aux_open(lua_State *L) | 19 | int aux_open(lua_State *L) |
| @@ -48,23 +27,20 @@ int aux_open(lua_State *L) | |||
| 48 | void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) | 27 | void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) |
| 49 | { | 28 | { |
| 50 | luaL_newmetatable(L, classname); /* mt */ | 29 | luaL_newmetatable(L, classname); /* mt */ |
| 51 | /* set __tostring metamethod */ | ||
| 52 | lua_pushstring(L, "__tostring"); | ||
| 53 | lua_pushcfunction(L, aux_meth_tostring); | ||
| 54 | lua_rawset(L, -3); | ||
| 55 | /* create __index table to place methods */ | 30 | /* create __index table to place methods */ |
| 56 | lua_pushstring(L, "__index"); /* mt,"__index" */ | 31 | lua_pushstring(L, "__index"); /* mt,"__index" */ |
| 57 | lua_newtable(L); /* mt,"__index",it */ | 32 | lua_newtable(L); /* mt,"__index",it */ |
| 58 | luaL_openlib(L, NULL, func, 0); | ||
| 59 | /* put class name into class metatable */ | 33 | /* put class name into class metatable */ |
| 60 | lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ | 34 | lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ |
| 61 | lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ | 35 | lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ |
| 62 | lua_rawset(L, -3); /* mt,"__index",it */ | 36 | lua_rawset(L, -3); /* mt,"__index",it */ |
| 63 | /* get __gc method from class and use it for garbage collection */ | 37 | /* pass all methods that start with _ to the metatable, and all others |
| 64 | lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc" */ | 38 | * to the index table */ |
| 65 | lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc","__gc" */ | 39 | for (; func->name; func++) { /* mt,"__index",it */ |
| 66 | lua_rawget(L, -3); /* mt,"__index",it,"__gc",fn */ | 40 | lua_pushstring(L, func->name); |
| 67 | lua_rawset(L, -5); /* mt,"__index",it */ | 41 | lua_pushcfunction(L, func->func); |
| 42 | lua_rawset(L, func->name[0] == '_' ? -5: -3); | ||
| 43 | } | ||
| 68 | lua_rawset(L, -3); /* mt */ | 44 | lua_rawset(L, -3); /* mt */ |
| 69 | lua_pop(L, 1); | 45 | lua_pop(L, 1); |
| 70 | } | 46 | } |
diff --git a/src/ftp.lua b/src/ftp.lua index 18dab6d..6074623 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -30,474 +30,153 @@ EMAIL = "anonymous@anonymous.org" | |||
| 30 | BLOCKSIZE = 2048 | 30 | BLOCKSIZE = 2048 |
| 31 | 31 | ||
| 32 | ----------------------------------------------------------------------------- | 32 | ----------------------------------------------------------------------------- |
| 33 | -- Gets ip and port for data connection from PASV answer | 33 | -- Low level FTP API |
| 34 | -- Input | ||
| 35 | -- pasv: PASV command answer | ||
| 36 | -- Returns | ||
| 37 | -- ip: string containing ip for data connection | ||
| 38 | -- port: port for data connection | ||
| 39 | ----------------------------------------------------------------------------- | 34 | ----------------------------------------------------------------------------- |
| 40 | local function get_pasv(pasv) | 35 | local metat = { __index = {} } |
| 41 | local a, b, c, d, p1, p2, _ | ||
| 42 | local ip, port | ||
| 43 | _,_, a, b, c, d, p1, p2 = | ||
| 44 | string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") | ||
| 45 | if not (a and b and c and d and p1 and p2) then return nil, nil end | ||
| 46 | ip = string.format("%d.%d.%d.%d", a, b, c, d) | ||
| 47 | port = tonumber(p1)*256 + tonumber(p2) | ||
| 48 | return ip, port | ||
| 49 | end | ||
| 50 | |||
| 51 | ----------------------------------------------------------------------------- | ||
| 52 | -- Check server greeting | ||
| 53 | -- Input | ||
| 54 | -- control: control connection with server | ||
| 55 | -- Returns | ||
| 56 | -- code: nil if error | ||
| 57 | -- answer: server answer or error message | ||
| 58 | ----------------------------------------------------------------------------- | ||
| 59 | local function greet(control) | ||
| 60 | local code, answer = check_answer(control, {120, 220}) | ||
| 61 | if code == 120 then -- please try again, somewhat busy now... | ||
| 62 | return check_answer(control, {220}) | ||
| 63 | end | ||
| 64 | return code, answer | ||
| 65 | end | ||
| 66 | |||
| 67 | ----------------------------------------------------------------------------- | ||
| 68 | -- Log in on server | ||
| 69 | -- Input | ||
| 70 | -- control: control connection with server | ||
| 71 | -- user: user name | ||
| 72 | -- password: user password if any | ||
| 73 | -- Returns | ||
| 74 | -- code: nil if error | ||
| 75 | -- answer: server answer or error message | ||
| 76 | ----------------------------------------------------------------------------- | ||
| 77 | local function login(control, user, password) | ||
| 78 | local code, answer = command(control, "user", user, {230, 331}) | ||
| 79 | if code == 331 and password then -- need pass and we have pass | ||
| 80 | return command(control, "pass", password, {230, 202}) | ||
| 81 | end | ||
| 82 | return code, answer | ||
| 83 | end | ||
| 84 | |||
| 85 | ----------------------------------------------------------------------------- | ||
| 86 | -- Change to target directory | ||
| 87 | -- Input | ||
| 88 | -- control: socket for control connection with server | ||
| 89 | -- path: directory to change to | ||
| 90 | -- Returns | ||
| 91 | -- code: nil if error | ||
| 92 | -- answer: server answer or error message | ||
| 93 | ----------------------------------------------------------------------------- | ||
| 94 | local function cwd(control, path) | ||
| 95 | end | ||
| 96 | |||
| 97 | ----------------------------------------------------------------------------- | ||
| 98 | -- Change to target directory | ||
| 99 | -- Input | ||
| 100 | -- control: socket for control connection with server | ||
| 101 | -- Returns | ||
| 102 | -- server: server socket bound to local address, nil if error | ||
| 103 | -- answer: error message if any | ||
| 104 | ----------------------------------------------------------------------------- | ||
| 105 | local function port(control) | ||
| 106 | local code, answer | ||
| 107 | local server, ctl_ip | ||
| 108 | ctl_ip, answer = control:getsockname() | ||
| 109 | server, answer = socket.bind(ctl_ip, 0) | ||
| 110 | server:settimeout(TIMEOUT) | ||
| 111 | local ip, p, ph, pl | ||
| 112 | ip, p = server:getsockname() | ||
| 113 | pl = math.mod(p, 256) | ||
| 114 | ph = (p - pl)/256 | ||
| 115 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
| 116 | code, answer = command(control, "port", arg, {200}) | ||
| 117 | if not code then | ||
| 118 | server:close() | ||
| 119 | return nil, answer | ||
| 120 | else return server end | ||
| 121 | end | ||
| 122 | 36 | ||
| 123 | ----------------------------------------------------------------------------- | 37 | function open(server, port) |
| 124 | -- Closes control connection with server | 38 | local tp = socket.try(socket.tp.connect(server, port or PORT)) |
| 125 | -- Input | 39 | return setmetatable({tp = tp}, metat) |
| 126 | -- control: control connection with server | ||
| 127 | -- Returns | ||
| 128 | -- code: nil if error | ||
| 129 | -- answer: server answer or error message | ||
| 130 | ----------------------------------------------------------------------------- | ||
| 131 | local function logout(control) | ||
| 132 | local code, answer = command(control, "quit", nil, {221}) | ||
| 133 | if code then control:close() end | ||
| 134 | return code, answer | ||
| 135 | end | 40 | end |
| 136 | 41 | ||
| 137 | ----------------------------------------------------------------------------- | 42 | local function port(portt) |
| 138 | -- Receives data and send it to a callback | 43 | return portt.server:accept() |
| 139 | -- Input | ||
| 140 | -- data: data connection | ||
| 141 | -- callback: callback to return file contents | ||
| 142 | -- Returns | ||
| 143 | -- nil if successfull, or an error message in case of error | ||
| 144 | ----------------------------------------------------------------------------- | ||
| 145 | local function receive_indirect(data, callback) | ||
| 146 | local chunk, err, res | ||
| 147 | while not err do | ||
| 148 | chunk, err = try_receive(data, BLOCKSIZE) | ||
| 149 | if err == "closed" then err = "done" end | ||
| 150 | res = callback(chunk, err) | ||
| 151 | if not res then break end | ||
| 152 | end | ||
| 153 | end | 44 | end |
| 154 | 45 | ||
| 155 | ----------------------------------------------------------------------------- | 46 | local function pasv(pasvt) |
| 156 | -- Retrieves file or directory listing | 47 | return socket.connect(pasvt.ip, pasvt.port) |
| 157 | -- Input | ||
| 158 | -- control: control connection with server | ||
| 159 | -- server: server socket bound to local address | ||
| 160 | -- name: file name | ||
| 161 | -- is_directory: is file a directory name? | ||
| 162 | -- content_cb: callback to receive file contents | ||
| 163 | -- Returns | ||
| 164 | -- err: error message in case of error, nil otherwise | ||
| 165 | ----------------------------------------------------------------------------- | ||
| 166 | local function retrieve(control, server, name, is_directory, content_cb) | ||
| 167 | local code, answer | ||
| 168 | local data | ||
| 169 | -- ask server for file or directory listing accordingly | ||
| 170 | if is_directory then | ||
| 171 | code, answer = cwd(control, name) | ||
| 172 | if not code then return answer end | ||
| 173 | code, answer = command(control, "nlst", nil, {150, 125}) | ||
| 174 | else | ||
| 175 | code, answer = command(control, "retr", name, {150, 125}) | ||
| 176 | end | ||
| 177 | if not code then return nil, answer end | ||
| 178 | data, answer = server:accept() | ||
| 179 | server:close() | ||
| 180 | if not data then | ||
| 181 | control:close() | ||
| 182 | return answer | ||
| 183 | end | ||
| 184 | answer = receive_indirect(data, content_cb) | ||
| 185 | if answer then | ||
| 186 | control:close() | ||
| 187 | return answer | ||
| 188 | end | ||
| 189 | data:close() | ||
| 190 | -- make sure file transfered ok | ||
| 191 | return check_answer(control, {226, 250}) | ||
| 192 | end | 48 | end |
| 193 | 49 | ||
| 194 | ----------------------------------------------------------------------------- | 50 | function metat.__index:login(user, password) |
| 195 | -- Stores a file | 51 | socket.try(self.tp:command("USER", user)) |
| 196 | -- Input | 52 | local code, reply = socket.try(self.tp:check{"2..", 331}) |
| 197 | -- control: control connection with server | 53 | if code == 331 then |
| 198 | -- server: server socket bound to local address | 54 | socket.try(password, reply) |
| 199 | -- file: file name under current directory | 55 | socket.try(self.tp:command("PASS", password)) |
| 200 | -- send_cb: callback to produce the file contents | 56 | socket.try(self.tp:check("2..")) |
| 201 | -- Returns | 57 | end |
| 202 | -- code: return code, nil if error | 58 | return 1 |
| 203 | -- answer: server answer or error message | 59 | end |
| 204 | ----------------------------------------------------------------------------- | 60 | |
| 205 | local function store(control, server, file, send_cb) | 61 | function metat.__index:pasv() |
| 206 | local data, err | 62 | socket.try(self.tp:command("PASV")) |
| 207 | local code, answer = command(control, "stor", file, {150, 125}) | 63 | local code, reply = socket.try(self.tp:check("2..")) |
| 208 | if not code then | 64 | local _, _, a, b, c, d, p1, p2 = |
| 209 | control:close() | 65 | string.find(reply, "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)") |
| 210 | return nil, answer | 66 | socket.try(a and b and c and d and p1 and p2, reply) |
| 211 | end | 67 | self.pasvt = { |
| 212 | -- start data connection | 68 | ip = string.format("%d.%d.%d.%d", a, b, c, d), |
| 213 | data, answer = server:accept() | 69 | port = p1*256 + p2 |
| 214 | server:close() | 70 | } |
| 215 | if not data then | 71 | if self.portt then |
| 216 | control:close() | 72 | self.portt.server:close() |
| 217 | return nil, answer | 73 | self.portt = nil |
| 218 | end | 74 | end |
| 219 | -- send whole file | 75 | return self.pasvt.ip, self.pasvt.port |
| 220 | err = send_indirect(data, send_cb, send_cb()) | ||
| 221 | if err then | ||
| 222 | control:close() | ||
| 223 | return nil, err | ||
| 224 | end | ||
| 225 | -- close connection to inform that file transmission is complete | ||
| 226 | data:close() | ||
| 227 | -- check if file was received correctly | ||
| 228 | return check_answer(control, {226, 250}) | ||
| 229 | end | ||
| 230 | |||
| 231 | ----------------------------------------------------------------------------- | ||
| 232 | -- Change transfer type | ||
| 233 | -- Input | ||
| 234 | -- control: control connection with server | ||
| 235 | -- params: "type=i" for binary or "type=a" for ascii | ||
| 236 | -- Returns | ||
| 237 | -- err: error message if any | ||
| 238 | ----------------------------------------------------------------------------- | ||
| 239 | local function change_type(control, params) | ||
| 240 | local type, _ | ||
| 241 | _, _, type = string.find(params or "", "type=(.)") | ||
| 242 | if type == "a" or type == "i" then | ||
| 243 | local code, err = command(control, "type", type, {200}) | ||
| 244 | if not code then return err end | ||
| 245 | end | ||
| 246 | end | 76 | end |
| 247 | 77 | ||
| 248 | ----------------------------------------------------------------------------- | 78 | function metat.__index:port(ip, port) |
| 249 | -- Starts a control connection, checks the greeting and log on | 79 | self.pasvt = nil |
| 250 | -- Input | 80 | local server |
| 251 | -- parsed: parsed URL components | 81 | if not ip then |
| 252 | -- Returns | 82 | ip, port = socket.try(self.tp:getcontrol():getsockname()) |
| 253 | -- control: control connection with server, or nil if error | 83 | server = socket.try(socket.bind(ip, 0)) |
| 254 | -- err: error message if any | 84 | ip, port = socket.try(server:getsockname()) |
| 255 | ----------------------------------------------------------------------------- | 85 | socket.try(server:settimeout(TIMEOUT)) |
| 256 | local function open(parsed) | 86 | end |
| 257 | local control, err = socket.tp.connect(parsed.host, parsed.port) | 87 | local pl = math.mod(port, 256) |
| 258 | if not control then return nil, err end | 88 | local ph = (port - pl)/256 |
| 89 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
| 90 | socket.try(self.tp:command("port", arg)) | ||
| 91 | socket.try(self.tp:check("2..")) | ||
| 92 | self.portt = server and {ip = ip, port = port, server = server} | ||
| 93 | return 1 | ||
| 94 | end | ||
| 95 | |||
| 96 | function metat.__index:send(sendt) | ||
| 97 | local data | ||
| 98 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
| 99 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
| 100 | socket.try(self.tp:command(sendt.command, sendt.argument)) | ||
| 101 | if self.portt then data = socket.try(port(self.portt)) end | ||
| 102 | local step = sendt.step or ltn12.pump.step | ||
| 259 | local code, reply | 103 | local code, reply |
| 260 | -- greet | 104 | local checkstep = function(src, snk) |
| 261 | code, reply = control:check({120, 220}) | 105 | local readyt = socket.select(readt, nil, 0) |
| 262 | if code == 120 then -- busy, try again | 106 | if readyt[tp] then |
| 263 | code, reply = control:check(220) | 107 | code, reply = self.tp:check{"2..", "1.."} |
| 108 | if not code then | ||
| 109 | data:close() | ||
| 110 | return nil, reply | ||
| 111 | end | ||
| 112 | end | ||
| 113 | local ret, err = step(src, snk) | ||
| 114 | if err then data:close() end | ||
| 115 | return ret, err | ||
| 264 | end | 116 | end |
| 265 | -- authenticate | 117 | local sink = socket.sink("close-when-empty", data) |
| 266 | code, reply = control:command("user", user) | 118 | socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) |
| 267 | code, reply = control:check({230, 331}) | 119 | if not code then code = socket.try(self.tp:check{"1..", "2.."}) end |
| 268 | if code == 331 and password then -- need pass and we have pass | 120 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 269 | control:command("pass", password) | 121 | return 1 |
| 270 | code, reply = control:check({230, 202}) | 122 | end |
| 123 | |||
| 124 | function metat.__index:receive(recvt) | ||
| 125 | local data | ||
| 126 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
| 127 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
| 128 | socket.try(self.tp:command(recvt.command, recvt.argument)) | ||
| 129 | if self.portt then data = socket.try(port(self.portt)) end | ||
| 130 | local source = socket.source("until-closed", data) | ||
| 131 | local step = recvt.step or ltn12.pump.step | ||
| 132 | local checkstep = function(src, snk) | ||
| 133 | local ret, err = step(src, snk) | ||
| 134 | if err then data:close() end | ||
| 135 | return ret, err | ||
| 271 | end | 136 | end |
| 272 | -- change directory | 137 | socket.try(ltn12.pump.all(source, recvt.sink, checkstep)) |
| 273 | local segment = parse_path(parsed) | 138 | local code = socket.try(self.tp:check{"1..", "2.."}) |
| 274 | for i, v in ipairs(segment) do | 139 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 275 | code, reply = control:command("cwd") | 140 | return 1 |
| 276 | code, reply = control:check(250) | ||
| 277 | end | ||
| 278 | -- change type | ||
| 279 | local type = string.sub(params or "", 7, 7) | ||
| 280 | if type == "a" or type == "i" then | ||
| 281 | code, reply = control:command("type", type) | ||
| 282 | code, reply = control:check(200) | ||
| 283 | end | ||
| 284 | end | 141 | end |
| 285 | 142 | ||
| 286 | return change_dir(control, segment) or | 143 | function metat.__index:cwd(dir) |
| 287 | change_type(control, parsed.params) or | 144 | socket.try(self.tp:command("CWD", dir)) |
| 288 | download(control, request, segment) or | 145 | socket.try(self.tp:check(250)) |
| 289 | close(control) | 146 | return 1 |
| 290 | end | 147 | end |
| 291 | 148 | ||
| 149 | function metat.__index:type(type) | ||
| 150 | socket.try(self.tp:command("TYPE", type)) | ||
| 151 | socket.try(self.tp:check(200)) | ||
| 152 | return 1 | ||
| 292 | end | 153 | end |
| 293 | 154 | ||
| 294 | ----------------------------------------------------------------------------- | 155 | function metat.__index:greet() |
| 295 | -- Stores a file in current directory | 156 | local code = socket.try(self.tp:check{"1..", "2.."}) |
| 296 | -- Input | 157 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 297 | -- control: control connection with server | 158 | return 1 |
| 298 | -- request: a table with the fields: | ||
| 299 | -- content_cb: send callback to send file contents | ||
| 300 | -- segment: parsed URL path segments | ||
| 301 | -- Returns | ||
| 302 | -- err: error message if any | ||
| 303 | ----------------------------------------------------------------------------- | ||
| 304 | local function upload(control, request, segment) | ||
| 305 | local code, name, content_cb | ||
| 306 | -- get remote file name | ||
| 307 | name = segment[table.getn(segment)] | ||
| 308 | if not name then | ||
| 309 | control:close() | ||
| 310 | return "Invalid file path" | ||
| 311 | end | ||
| 312 | content_cb = request.content_cb | ||
| 313 | -- setup passive connection | ||
| 314 | local server, answer = port(control) | ||
| 315 | if not server then return answer end | ||
| 316 | -- ask server to receive file | ||
| 317 | code, answer = store(control, server, name, content_cb) | ||
| 318 | if not code then return answer end | ||
| 319 | end | 159 | end |
| 320 | 160 | ||
| 321 | ----------------------------------------------------------------------------- | 161 | function metat.__index:quit() |
| 322 | -- Download a file from current directory | 162 | socket.try(self.tp:command("QUIT")) |
| 323 | -- Input | 163 | socket.try(self.tp:check("2..")) |
| 324 | -- control: control connection with server | 164 | return 1 |
| 325 | -- request: a table with the fields: | ||
| 326 | -- content_cb: receive callback to receive file contents | ||
| 327 | -- segment: parsed URL path segments | ||
| 328 | -- Returns | ||
| 329 | -- err: error message if any | ||
| 330 | ----------------------------------------------------------------------------- | ||
| 331 | local function download(control, request, segment) | ||
| 332 | local code, name, is_directory, content_cb | ||
| 333 | is_directory = segment.is_directory | ||
| 334 | content_cb = request.content_cb | ||
| 335 | -- get remote file name | ||
| 336 | name = segment[table.getn(segment)] | ||
| 337 | if not name and not is_directory then | ||
| 338 | control:close() | ||
| 339 | return "Invalid file path" | ||
| 340 | end | ||
| 341 | -- setup passive connection | ||
| 342 | local server, answer = port(control) | ||
| 343 | if not server then return answer end | ||
| 344 | -- ask server to send file or directory listing | ||
| 345 | code, answer = retrieve(control, server, name, | ||
| 346 | is_directory, content_cb) | ||
| 347 | if not code then return answer end | ||
| 348 | end | 165 | end |
| 349 | 166 | ||
| 350 | ----------------------------------------------------------------------------- | 167 | function metat.__index:close() |
| 351 | -- Parses the FTP URL setting default values | 168 | socket.try(self.tp:close()) |
| 352 | -- Input | 169 | return 1 |
| 353 | -- request: a table with the fields: | ||
| 354 | -- url: the target URL | ||
| 355 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 356 | -- user: account user name | ||
| 357 | -- password: account password | ||
| 358 | -- Returns | ||
| 359 | -- parsed: a table with parsed components | ||
| 360 | ----------------------------------------------------------------------------- | ||
| 361 | local function parse_url(request) | ||
| 362 | local parsed = socket.url.parse(request.url, { | ||
| 363 | user = "anonymous", | ||
| 364 | port = 21, | ||
| 365 | path = "/", | ||
| 366 | password = EMAIL, | ||
| 367 | scheme = "ftp" | ||
| 368 | }) | ||
| 369 | -- explicit login information overrides that given by URL | ||
| 370 | parsed.user = request.user or parsed.user | ||
| 371 | parsed.password = request.password or parsed.password | ||
| 372 | -- explicit representation type overrides that given by URL | ||
| 373 | if request.type then parsed.params = "type=" .. request.type end | ||
| 374 | return parsed | ||
| 375 | end | 170 | end |
| 376 | 171 | ||
| 377 | ----------------------------------------------------------------------------- | 172 | ----------------------------------------------------------------------------- |
| 378 | -- Parses the FTP URL path setting default values | 173 | -- High level FTP API |
| 379 | -- Input | ||
| 380 | -- parsed: a table with the parsed URL components | ||
| 381 | -- Returns | ||
| 382 | -- dirs: a table with parsed directory components | ||
| 383 | ----------------------------------------------------------------------------- | 174 | ----------------------------------------------------------------------------- |
| 384 | local function parse_path(parsed_url) | ||
| 385 | local segment = socket.url.parse_path(parsed_url.path) | ||
| 386 | segment.is_directory = segment.is_directory or | ||
| 387 | (parsed_url.params == "type=d") | ||
| 388 | return segment | ||
| 389 | end | ||
| 390 | 175 | ||
| 391 | ----------------------------------------------------------------------------- | 176 | function put(putt) |
| 392 | -- Builds a request table from a URL or request table | ||
| 393 | -- Input | ||
| 394 | -- url_or_request: target url or request table (a table with the fields: | ||
| 395 | -- url: the target URL | ||
| 396 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 397 | -- user: account user name | ||
| 398 | -- password: account password) | ||
| 399 | -- Returns | ||
| 400 | -- request: request table | ||
| 401 | ----------------------------------------------------------------------------- | ||
| 402 | local function build_request(data) | ||
| 403 | local request = {} | ||
| 404 | if type(data) == "table" then for i, v in data do request[i] = v end | ||
| 405 | else request.url = data end | ||
| 406 | return request | ||
| 407 | end | 177 | end |
| 408 | 178 | ||
| 409 | ----------------------------------------------------------------------------- | 179 | function get(gett) |
| 410 | -- Downloads a file from a FTP server | ||
| 411 | -- Input | ||
| 412 | -- request: a table with the fields: | ||
| 413 | -- url: the target URL | ||
| 414 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 415 | -- user: account user name | ||
| 416 | -- password: account password | ||
| 417 | -- content_cb: receive callback to receive file contents | ||
| 418 | -- Returns | ||
| 419 | -- err: error message if any | ||
| 420 | ----------------------------------------------------------------------------- | ||
| 421 | function get_cb(request) | ||
| 422 | local parsed = parse_url(request) | ||
| 423 | if parsed.scheme ~= "ftp" then | ||
| 424 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
| 425 | end | ||
| 426 | local control, err = open(parsed) | ||
| 427 | if not control then return err end | ||
| 428 | local segment = parse_path(parsed) | ||
| 429 | return change_dir(control, segment) or | ||
| 430 | change_type(control, parsed.params) or | ||
| 431 | download(control, request, segment) or | ||
| 432 | close(control) | ||
| 433 | end | ||
| 434 | |||
| 435 | ----------------------------------------------------------------------------- | ||
| 436 | -- Uploads a file to a FTP server | ||
| 437 | -- Input | ||
| 438 | -- request: a table with the fields: | ||
| 439 | -- url: the target URL | ||
| 440 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 441 | -- user: account user name | ||
| 442 | -- password: account password | ||
| 443 | -- content_cb: send callback to send file contents | ||
| 444 | -- Returns | ||
| 445 | -- err: error message if any | ||
| 446 | ----------------------------------------------------------------------------- | ||
| 447 | function put_cb(request) | ||
| 448 | local parsed = parse_url(request) | ||
| 449 | if parsed.scheme ~= "ftp" then | ||
| 450 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
| 451 | end | ||
| 452 | local control, err = open(parsed) | ||
| 453 | if not control then return err end | ||
| 454 | local segment = parse_path(parsed) | ||
| 455 | err = change_dir(control, segment) or | ||
| 456 | change_type(control, parsed.params) or | ||
| 457 | upload(control, request, segment) or | ||
| 458 | close(control) | ||
| 459 | if err then return nil, err | ||
| 460 | else return 1 end | ||
| 461 | end | ||
| 462 | |||
| 463 | ----------------------------------------------------------------------------- | ||
| 464 | -- Uploads a file to a FTP server | ||
| 465 | -- Input | ||
| 466 | -- url_or_request: target url or request table (a table with the fields: | ||
| 467 | -- url: the target URL | ||
| 468 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 469 | -- user: account user name | ||
| 470 | -- password: account password) | ||
| 471 | -- content: file contents | ||
| 472 | -- content: file contents | ||
| 473 | -- Returns | ||
| 474 | -- err: error message if any | ||
| 475 | ----------------------------------------------------------------------------- | ||
| 476 | function put(url_or_request, content) | ||
| 477 | local request = build_request(url_or_request) | ||
| 478 | request.content = request.content or content | ||
| 479 | request.content_cb = socket.callback.send_string(request.content) | ||
| 480 | return put_cb(request) | ||
| 481 | end | ||
| 482 | |||
| 483 | ----------------------------------------------------------------------------- | ||
| 484 | -- Retrieve a file from a ftp server | ||
| 485 | -- Input | ||
| 486 | -- url_or_request: target url or request table (a table with the fields: | ||
| 487 | -- url: the target URL | ||
| 488 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 489 | -- user: account user name | ||
| 490 | -- password: account password) | ||
| 491 | -- Returns | ||
| 492 | -- data: file contents as a string | ||
| 493 | -- err: error message in case of error, nil otherwise | ||
| 494 | ----------------------------------------------------------------------------- | ||
| 495 | function get(url_or_request) | ||
| 496 | local concat = socket.concat.create() | ||
| 497 | local request = build_request(url_or_request) | ||
| 498 | request.content_cb = socket.callback.receive_concat(concat) | ||
| 499 | local err = get_cb(request) | ||
| 500 | return concat:getresult(), err | ||
| 501 | end | 180 | end |
| 502 | 181 | ||
| 503 | return socket.ftp | 182 | return ftp |
diff --git a/src/http.lua b/src/http.lua index da18aaf..f787b9d 100644 --- a/src/http.lua +++ b/src/http.lua | |||
| @@ -68,7 +68,7 @@ end | |||
| 68 | 68 | ||
| 69 | local function receive_body(reqt, respt, tmp) | 69 | local function receive_body(reqt, respt, tmp) |
| 70 | local sink = reqt.sink or ltn12.sink.null() | 70 | local sink = reqt.sink or ltn12.sink.null() |
| 71 | local pump = reqt.pump or ltn12.pump | 71 | local step = reqt.step or ltn12.pump.step |
| 72 | local source | 72 | local source |
| 73 | local te = respt.headers["transfer-encoding"] | 73 | local te = respt.headers["transfer-encoding"] |
| 74 | if te and te ~= "identity" then | 74 | if te and te ~= "identity" then |
| @@ -80,9 +80,9 @@ local function receive_body(reqt, respt, tmp) | |||
| 80 | source = socket.source("by-length", tmp.sock, length) | 80 | source = socket.source("by-length", tmp.sock, length) |
| 81 | else | 81 | else |
| 82 | -- get it all until connection closes | 82 | -- get it all until connection closes |
| 83 | source = socket.source("until-closed", tmp.sock) | 83 | source = socket.source(tmp.sock) |
| 84 | end | 84 | end |
| 85 | socket.try(pump(source, sink)) | 85 | socket.try(ltn12.pump.all(source, sink, step)) |
| 86 | end | 86 | end |
| 87 | 87 | ||
| 88 | local function send_headers(sock, headers) | 88 | local function send_headers(sock, headers) |
| @@ -125,7 +125,7 @@ end | |||
| 125 | local function send_request(reqt, respt, tmp) | 125 | local function send_request(reqt, respt, tmp) |
| 126 | local uri = request_uri(reqt, respt, tmp) | 126 | local uri = request_uri(reqt, respt, tmp) |
| 127 | local headers = tmp.headers | 127 | local headers = tmp.headers |
| 128 | local pump = reqt.pump or ltn12.pump | 128 | local step = reqt.step or ltn12.pump.step |
| 129 | -- send request line | 129 | -- send request line |
| 130 | socket.try(tmp.sock:send((reqt.method or "GET") | 130 | socket.try(tmp.sock:send((reqt.method or "GET") |
| 131 | .. " " .. uri .. " HTTP/1.1\r\n")) | 131 | .. " " .. uri .. " HTTP/1.1\r\n")) |
| @@ -136,9 +136,11 @@ local function send_request(reqt, respt, tmp) | |||
| 136 | -- send request message body, if any | 136 | -- send request message body, if any |
| 137 | if not reqt.source then return end | 137 | if not reqt.source then return end |
| 138 | if headers["content-length"] then | 138 | if headers["content-length"] then |
| 139 | socket.try(pump(reqt.source, socket.sink(tmp.sock))) | 139 | socket.try(ltn12.pump.all(reqt.source, |
| 140 | socket.sink(tmp.sock), step)) | ||
| 140 | else | 141 | else |
| 141 | socket.try(pump(reqt.source, socket.sink("http-chunked", tmp.sock))) | 142 | socket.try(ltn12.pump.all(reqt.source, |
| 143 | socket.sink("http-chunked", tmp.sock), step)) | ||
| 142 | end | 144 | end |
| 143 | end | 145 | end |
| 144 | 146 | ||
diff --git a/src/ltn12.lua b/src/ltn12.lua index dac932b..56e6043 100644 --- a/src/ltn12.lua +++ b/src/ltn12.lua | |||
| @@ -8,6 +8,7 @@ setfenv(1, ltn12) | |||
| 8 | filter = {} | 8 | filter = {} |
| 9 | source = {} | 9 | source = {} |
| 10 | sink = {} | 10 | sink = {} |
| 11 | pump = {} | ||
| 11 | 12 | ||
| 12 | -- 2048 seems to be better in windows... | 13 | -- 2048 seems to be better in windows... |
| 13 | BLOCKSIZE = 2048 | 14 | BLOCKSIZE = 2048 |
| @@ -22,7 +23,6 @@ end | |||
| 22 | 23 | ||
| 23 | -- returns a high level filter that cycles a cycles a low-level filter | 24 | -- returns a high level filter that cycles a cycles a low-level filter |
| 24 | function filter.cycle(low, ctx, extra) | 25 | function filter.cycle(low, ctx, extra) |
| 25 | if type(low) ~= 'function' then error('invalid low-level filter', 2) end | ||
| 26 | return function(chunk) | 26 | return function(chunk) |
| 27 | local ret | 27 | local ret |
| 28 | ret, ctx = low(ctx, chunk, extra) | 28 | ret, ctx = low(ctx, chunk, extra) |
| @@ -32,8 +32,6 @@ end | |||
| 32 | 32 | ||
| 33 | -- chains two filters together | 33 | -- chains two filters together |
| 34 | local function chain2(f1, f2) | 34 | local function chain2(f1, f2) |
| 35 | if type(f1) ~= 'function' then error('invalid filter', 2) end | ||
| 36 | if type(f2) ~= 'function' then error('invalid filter', 2) end | ||
| 37 | local co = coroutine.create(function(chunk) | 35 | local co = coroutine.create(function(chunk) |
| 38 | while true do | 36 | while true do |
| 39 | local filtered1 = f1(chunk) | 37 | local filtered1 = f1(chunk) |
| @@ -58,7 +56,6 @@ end | |||
| 58 | function filter.chain(...) | 56 | function filter.chain(...) |
| 59 | local f = arg[1] | 57 | local f = arg[1] |
| 60 | for i = 2, table.getn(arg) do | 58 | for i = 2, table.getn(arg) do |
| 61 | if type(arg[i]) ~= 'function' then error('invalid filter', 2) end | ||
| 62 | f = chain2(f, arg[i]) | 59 | f = chain2(f, arg[i]) |
| 63 | end | 60 | end |
| 64 | return f | 61 | return f |
| @@ -93,7 +90,6 @@ end | |||
| 93 | 90 | ||
| 94 | -- turns a fancy source into a simple source | 91 | -- turns a fancy source into a simple source |
| 95 | function source.simplify(src) | 92 | function source.simplify(src) |
| 96 | if type(src) ~= 'function' then error('invalid source', 2) end | ||
| 97 | return function() | 93 | return function() |
| 98 | local chunk, err_or_new = src() | 94 | local chunk, err_or_new = src() |
| 99 | src = err_or_new or src | 95 | src = err_or_new or src |
| @@ -117,7 +113,6 @@ end | |||
| 117 | 113 | ||
| 118 | -- creates rewindable source | 114 | -- creates rewindable source |
| 119 | function source.rewind(src) | 115 | function source.rewind(src) |
| 120 | if type(src) ~= 'function' then error('invalid source', 2) end | ||
| 121 | local t = {} | 116 | local t = {} |
| 122 | return function(chunk) | 117 | return function(chunk) |
| 123 | if not chunk then | 118 | if not chunk then |
| @@ -132,8 +127,6 @@ end | |||
| 132 | 127 | ||
| 133 | -- chains a source with a filter | 128 | -- chains a source with a filter |
| 134 | function source.chain(src, f) | 129 | function source.chain(src, f) |
| 135 | if type(src) ~= 'function' then error('invalid source', 2) end | ||
| 136 | if type(f) ~= 'function' then error('invalid filter', 2) end | ||
| 137 | local co = coroutine.create(function() | 130 | local co = coroutine.create(function() |
| 138 | while true do | 131 | while true do |
| 139 | local chunk, err = src() | 132 | local chunk, err = src() |
| @@ -152,20 +145,21 @@ function source.chain(src, f) | |||
| 152 | end | 145 | end |
| 153 | end | 146 | end |
| 154 | 147 | ||
| 155 | -- creates a source that produces contents of several files one after the | 148 | -- creates a source that produces contents of several sources, one after the |
| 156 | -- other, as if they were concatenated | 149 | -- other, as if they were concatenated |
| 157 | function source.cat(...) | 150 | function source.cat(...) |
| 158 | local co = coroutine.create(function() | 151 | local co = coroutine.create(function() |
| 159 | local i = 1 | 152 | local i = 1 |
| 160 | while i <= table.getn(arg) do | 153 | while i <= table.getn(arg) do |
| 161 | local chunk = arg[i]:read(2048) | 154 | local chunk, err = arg[i]() |
| 162 | if chunk then coroutine.yield(chunk) | 155 | if chunk then coroutine.yield(chunk) |
| 163 | else i = i + 1 end | 156 | elseif err then return nil, err |
| 157 | else i = i + 1 end | ||
| 164 | end | 158 | end |
| 165 | end) | 159 | end) |
| 166 | return source.simplify(function() | 160 | return function() |
| 167 | return shift(coroutine.resume(co)) | 161 | return shift(coroutine.resume(co)) |
| 168 | end) | 162 | end |
| 169 | end | 163 | end |
| 170 | 164 | ||
| 171 | -- creates a sink that stores into a table | 165 | -- creates a sink that stores into a table |
| @@ -180,7 +174,6 @@ end | |||
| 180 | 174 | ||
| 181 | -- turns a fancy sink into a simple sink | 175 | -- turns a fancy sink into a simple sink |
| 182 | function sink.simplify(snk) | 176 | function sink.simplify(snk) |
| 183 | if type(snk) ~= 'function' then error('invalid sink', 2) end | ||
| 184 | return function(chunk, err) | 177 | return function(chunk, err) |
| 185 | local ret, err_or_new = snk(chunk, err) | 178 | local ret, err_or_new = snk(chunk, err) |
| 186 | if not ret then return nil, err_or_new end | 179 | if not ret then return nil, err_or_new end |
| @@ -219,8 +212,6 @@ end | |||
| 219 | 212 | ||
| 220 | -- chains a sink with a filter | 213 | -- chains a sink with a filter |
| 221 | function sink.chain(f, snk) | 214 | function sink.chain(f, snk) |
| 222 | if type(snk) ~= 'function' then error('invalid sink', 2) end | ||
| 223 | if type(f) ~= 'function' then error('invalid filter', 2) end | ||
| 224 | return function(chunk, err) | 215 | return function(chunk, err) |
| 225 | local filtered = f(chunk) | 216 | local filtered = f(chunk) |
| 226 | local done = chunk and "" | 217 | local done = chunk and "" |
| @@ -233,15 +224,18 @@ function sink.chain(f, snk) | |||
| 233 | end | 224 | end |
| 234 | end | 225 | end |
| 235 | 226 | ||
| 236 | -- pumps all data from a source to a sink | 227 | -- pumps one chunk from the source to the sink |
| 237 | function pump(src, snk) | 228 | function pump.step(src, snk) |
| 238 | if type(src) ~= 'function' then error('invalid source', 2) end | 229 | local chunk, src_err = src() |
| 239 | if type(snk) ~= 'function' then error('invalid sink', 2) end | 230 | local ret, snk_err = snk(chunk, src_err) |
| 231 | return chunk and ret and not src_err and not snk_err, src_err or snk_err | ||
| 232 | end | ||
| 233 | |||
| 234 | -- pumps all data from a source to a sink, using a step function | ||
| 235 | function pump.all(src, snk, step) | ||
| 236 | step = step or pump.step | ||
| 240 | while true do | 237 | while true do |
| 241 | local chunk, src_err = src() | 238 | local ret, err = step(src, snk) |
| 242 | local ret, snk_err = snk(chunk, src_err) | 239 | if not ret then return not err, err end |
| 243 | if not chunk or not ret then | ||
| 244 | return not src_err and not snk_err, src_err or snk_err | ||
| 245 | end | ||
| 246 | end | 240 | end |
| 247 | end | 241 | end |
diff --git a/src/luasocket.c b/src/luasocket.c index eadb758..fe4c96c 100644 --- a/src/luasocket.c +++ b/src/luasocket.c | |||
| @@ -25,6 +25,7 @@ | |||
| 25 | \*=========================================================================*/ | 25 | \*=========================================================================*/ |
| 26 | #include "luasocket.h" | 26 | #include "luasocket.h" |
| 27 | 27 | ||
| 28 | #include "base.h" | ||
| 28 | #include "auxiliar.h" | 29 | #include "auxiliar.h" |
| 29 | #include "timeout.h" | 30 | #include "timeout.h" |
| 30 | #include "buffer.h" | 31 | #include "buffer.h" |
| @@ -39,34 +40,8 @@ | |||
| 39 | /*=========================================================================*\ | 40 | /*=========================================================================*\ |
| 40 | * Declarations | 41 | * Declarations |
| 41 | \*=========================================================================*/ | 42 | \*=========================================================================*/ |
| 42 | static int base_open(lua_State *L); | ||
| 43 | static int mod_open(lua_State *L, const luaL_reg *mod); | 43 | static int mod_open(lua_State *L, const luaL_reg *mod); |
| 44 | 44 | ||
| 45 | /*-------------------------------------------------------------------------*\ | ||
| 46 | * Setup basic stuff. | ||
| 47 | \*-------------------------------------------------------------------------*/ | ||
| 48 | static int base_open(lua_State *L) | ||
| 49 | { | ||
| 50 | /* create namespace table */ | ||
| 51 | lua_pushstring(L, LUASOCKET_LIBNAME); | ||
| 52 | lua_newtable(L); | ||
| 53 | #ifdef LUASOCKET_DEBUG | ||
| 54 | lua_pushstring(L, "debug"); | ||
| 55 | lua_pushnumber(L, 1); | ||
| 56 | lua_rawset(L, -3); | ||
| 57 | #endif | ||
| 58 | /* make version string available so scripts */ | ||
| 59 | lua_pushstring(L, "version"); | ||
| 60 | lua_pushstring(L, LUASOCKET_VERSION); | ||
| 61 | lua_rawset(L, -3); | ||
| 62 | /* store namespace as global */ | ||
| 63 | lua_settable(L, LUA_GLOBALSINDEX); | ||
| 64 | /* make sure modules know what is our namespace */ | ||
| 65 | lua_pushstring(L, "LUASOCKET_LIBNAME"); | ||
| 66 | lua_pushstring(L, LUASOCKET_LIBNAME); | ||
| 67 | lua_settable(L, LUA_GLOBALSINDEX); | ||
| 68 | return 0; | ||
| 69 | } | ||
| 70 | 45 | ||
| 71 | static int mod_open(lua_State *L, const luaL_reg *mod) | 46 | static int mod_open(lua_State *L, const luaL_reg *mod) |
| 72 | { | 47 | { |
| @@ -79,6 +54,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod) | |||
| 79 | #include "tp.lch" | 54 | #include "tp.lch" |
| 80 | #include "smtp.lch" | 55 | #include "smtp.lch" |
| 81 | #include "http.lch" | 56 | #include "http.lch" |
| 57 | #include "ftp.lch" | ||
| 82 | #else | 58 | #else |
| 83 | lua_dofile(L, "ltn12.lua"); | 59 | lua_dofile(L, "ltn12.lua"); |
| 84 | lua_dofile(L, "auxiliar.lua"); | 60 | lua_dofile(L, "auxiliar.lua"); |
| @@ -87,6 +63,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod) | |||
| 87 | lua_dofile(L, "tp.lua"); | 63 | lua_dofile(L, "tp.lua"); |
| 88 | lua_dofile(L, "smtp.lua"); | 64 | lua_dofile(L, "smtp.lua"); |
| 89 | lua_dofile(L, "http.lua"); | 65 | lua_dofile(L, "http.lua"); |
| 66 | lua_dofile(L, "ftp.lua"); | ||
| 90 | #endif | 67 | #endif |
| 91 | return 0; | 68 | return 0; |
| 92 | } | 69 | } |
| @@ -14,14 +14,9 @@ | |||
| 14 | /*=========================================================================*\ | 14 | /*=========================================================================*\ |
| 15 | * Don't want to trust escape character constants | 15 | * Don't want to trust escape character constants |
| 16 | \*=========================================================================*/ | 16 | \*=========================================================================*/ |
| 17 | #define CR 0x0D | ||
| 18 | #define LF 0x0A | ||
| 19 | #define HT 0x09 | ||
| 20 | #define SP 0x20 | ||
| 21 | |||
| 22 | typedef unsigned char UC; | 17 | typedef unsigned char UC; |
| 23 | static const char CRLF[] = {CR, LF, 0}; | 18 | static const char CRLF[] = "\r\n"; |
| 24 | static const char EQCRLF[] = {'=', CR, LF, 0}; | 19 | static const char EQCRLF[] = "=\r\n"; |
| 25 | 20 | ||
| 26 | /*=========================================================================*\ | 21 | /*=========================================================================*\ |
| 27 | * Internal function prototypes. | 22 | * Internal function prototypes. |
| @@ -121,9 +116,9 @@ static int mime_global_wrp(lua_State *L) | |||
| 121 | luaL_buffinit(L, &buffer); | 116 | luaL_buffinit(L, &buffer); |
| 122 | while (input < last) { | 117 | while (input < last) { |
| 123 | switch (*input) { | 118 | switch (*input) { |
| 124 | case CR: | 119 | case '\r': |
| 125 | break; | 120 | break; |
| 126 | case LF: | 121 | case '\n': |
| 127 | luaL_addstring(&buffer, CRLF); | 122 | luaL_addstring(&buffer, CRLF); |
| 128 | left = length; | 123 | left = length; |
| 129 | break; | 124 | break; |
| @@ -327,11 +322,10 @@ static int mime_global_unb64(lua_State *L) | |||
| 327 | * all (except CRLF in text) can be =XX | 322 | * all (except CRLF in text) can be =XX |
| 328 | * CLRL in not text must be =XX=XX | 323 | * CLRL in not text must be =XX=XX |
| 329 | * 33 through 60 inclusive can be plain | 324 | * 33 through 60 inclusive can be plain |
| 330 | * 62 through 120 inclusive can be plain | 325 | * 62 through 126 inclusive can be plain |
| 331 | * 9 and 32 can be plain, unless in the end of a line, where must be =XX | 326 | * 9 and 32 can be plain, unless in the end of a line, where must be =XX |
| 332 | * encoded lines must be no longer than 76 not counting CRLF | 327 | * encoded lines must be no longer than 76 not counting CRLF |
| 333 | * soft line-break are =CRLF | 328 | * soft line-break are =CRLF |
| 334 | * !"#$@[\]^`{|}~ should be =XX for EBCDIC compatibility | ||
| 335 | * To encode one byte, we need to see the next two. | 329 | * To encode one byte, we need to see the next two. |
| 336 | * Worst case is when we see a space, and wonder if a CRLF is comming | 330 | * Worst case is when we see a space, and wonder if a CRLF is comming |
| 337 | \*-------------------------------------------------------------------------*/ | 331 | \*-------------------------------------------------------------------------*/ |
| @@ -344,16 +338,10 @@ static void qpsetup(UC *qpclass, UC *qpunbase) | |||
| 344 | int i; | 338 | int i; |
| 345 | for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED; | 339 | for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED; |
| 346 | for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN; | 340 | for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN; |
| 347 | for (i = 62; i <= 120; i++) qpclass[i] = QP_PLAIN; | 341 | for (i = 62; i <= 126; i++) qpclass[i] = QP_PLAIN; |
| 348 | qpclass[HT] = QP_IF_LAST; qpclass[SP] = QP_IF_LAST; | 342 | qpclass['\t'] = QP_IF_LAST; |
| 349 | qpclass['!'] = QP_QUOTED; qpclass['"'] = QP_QUOTED; | 343 | qpclass[' '] = QP_IF_LAST; |
| 350 | qpclass['#'] = QP_QUOTED; qpclass['$'] = QP_QUOTED; | 344 | qpclass['\r'] = QP_CR; |
| 351 | qpclass['@'] = QP_QUOTED; qpclass['['] = QP_QUOTED; | ||
| 352 | qpclass['\\'] = QP_QUOTED; qpclass[']'] = QP_QUOTED; | ||
| 353 | qpclass['^'] = QP_QUOTED; qpclass['`'] = QP_QUOTED; | ||
| 354 | qpclass['{'] = QP_QUOTED; qpclass['|'] = QP_QUOTED; | ||
| 355 | qpclass['}'] = QP_QUOTED; qpclass['~'] = QP_QUOTED; | ||
| 356 | qpclass['}'] = QP_QUOTED; qpclass[CR] = QP_CR; | ||
| 357 | for (i = 0; i < 256; i++) qpunbase[i] = 255; | 345 | for (i = 0; i < 256; i++) qpunbase[i] = 255; |
| 358 | qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2; | 346 | qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2; |
| 359 | qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5; | 347 | qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5; |
| @@ -377,7 +365,7 @@ static void qpquote(UC c, luaL_Buffer *buffer) | |||
| 377 | 365 | ||
| 378 | /*-------------------------------------------------------------------------*\ | 366 | /*-------------------------------------------------------------------------*\ |
| 379 | * Accumulate characters until we are sure about how to deal with them. | 367 | * Accumulate characters until we are sure about how to deal with them. |
| 380 | * Once we are sure, output the to the buffer, in the correct form. | 368 | * Once we are sure, output to the buffer, in the correct form. |
| 381 | \*-------------------------------------------------------------------------*/ | 369 | \*-------------------------------------------------------------------------*/ |
| 382 | static size_t qpencode(UC c, UC *input, size_t size, | 370 | static size_t qpencode(UC c, UC *input, size_t size, |
| 383 | const char *marker, luaL_Buffer *buffer) | 371 | const char *marker, luaL_Buffer *buffer) |
| @@ -389,7 +377,7 @@ static size_t qpencode(UC c, UC *input, size_t size, | |||
| 389 | /* might be the CR of a CRLF sequence */ | 377 | /* might be the CR of a CRLF sequence */ |
| 390 | case QP_CR: | 378 | case QP_CR: |
| 391 | if (size < 2) return size; | 379 | if (size < 2) return size; |
| 392 | if (input[1] == LF) { | 380 | if (input[1] == '\n') { |
| 393 | luaL_addstring(buffer, marker); | 381 | luaL_addstring(buffer, marker); |
| 394 | return 0; | 382 | return 0; |
| 395 | } else qpquote(input[0], buffer); | 383 | } else qpquote(input[0], buffer); |
| @@ -398,7 +386,7 @@ static size_t qpencode(UC c, UC *input, size_t size, | |||
| 398 | case QP_IF_LAST: | 386 | case QP_IF_LAST: |
| 399 | if (size < 3) return size; | 387 | if (size < 3) return size; |
| 400 | /* if it is the last, quote it and we are done */ | 388 | /* if it is the last, quote it and we are done */ |
| 401 | if (input[1] == CR && input[2] == LF) { | 389 | if (input[1] == '\r' && input[2] == '\n') { |
| 402 | qpquote(input[0], buffer); | 390 | qpquote(input[0], buffer); |
| 403 | luaL_addstring(buffer, marker); | 391 | luaL_addstring(buffer, marker); |
| 404 | return 0; | 392 | return 0; |
| @@ -492,19 +480,19 @@ static size_t qpdecode(UC c, UC *input, size_t size, | |||
| 492 | case '=': | 480 | case '=': |
| 493 | if (size < 3) return size; | 481 | if (size < 3) return size; |
| 494 | /* eliminate soft line break */ | 482 | /* eliminate soft line break */ |
| 495 | if (input[1] == CR && input[2] == LF) return 0; | 483 | if (input[1] == '\r' && input[2] == '\n') return 0; |
| 496 | /* decode quoted representation */ | 484 | /* decode quoted representation */ |
| 497 | c = qpunbase[input[1]]; d = qpunbase[input[2]]; | 485 | c = qpunbase[input[1]]; d = qpunbase[input[2]]; |
| 498 | /* if it is an invalid, do not decode */ | 486 | /* if it is an invalid, do not decode */ |
| 499 | if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); | 487 | if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); |
| 500 | else luaL_putchar(buffer, (c << 4) + d); | 488 | else luaL_putchar(buffer, (c << 4) + d); |
| 501 | return 0; | 489 | return 0; |
| 502 | case CR: | 490 | case '\r': |
| 503 | if (size < 2) return size; | 491 | if (size < 2) return size; |
| 504 | if (input[1] == LF) luaL_addlstring(buffer, (char *)input, 2); | 492 | if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2); |
| 505 | return 0; | 493 | return 0; |
| 506 | default: | 494 | default: |
| 507 | if (input[0] == HT || (input[0] > 31 && input[0] < 127)) | 495 | if (input[0] == '\t' || (input[0] > 31 && input[0] < 127)) |
| 508 | luaL_putchar(buffer, input[0]); | 496 | luaL_putchar(buffer, input[0]); |
| 509 | return 0; | 497 | return 0; |
| 510 | } | 498 | } |
| @@ -582,9 +570,9 @@ static int mime_global_qpwrp(lua_State *L) | |||
| 582 | luaL_buffinit(L, &buffer); | 570 | luaL_buffinit(L, &buffer); |
| 583 | while (input < last) { | 571 | while (input < last) { |
| 584 | switch (*input) { | 572 | switch (*input) { |
| 585 | case CR: | 573 | case '\r': |
| 586 | break; | 574 | break; |
| 587 | case LF: | 575 | case '\n': |
| 588 | left = length; | 576 | left = length; |
| 589 | luaL_addstring(&buffer, CRLF); | 577 | luaL_addstring(&buffer, CRLF); |
| 590 | break; | 578 | break; |
| @@ -623,7 +611,7 @@ static int mime_global_qpwrp(lua_State *L) | |||
| 623 | * c is the current character being processed | 611 | * c is the current character being processed |
| 624 | * last is the previous character | 612 | * last is the previous character |
| 625 | \*-------------------------------------------------------------------------*/ | 613 | \*-------------------------------------------------------------------------*/ |
| 626 | #define eolcandidate(c) (c == CR || c == LF) | 614 | #define eolcandidate(c) (c == '\r' || c == '\n') |
| 627 | static int eolprocess(int c, int last, const char *marker, | 615 | static int eolprocess(int c, int last, const char *marker, |
| 628 | luaL_Buffer *buffer) | 616 | luaL_Buffer *buffer) |
| 629 | { | 617 | { |
diff --git a/src/select.c b/src/select.c index 8590b96..41bdaa4 100644 --- a/src/select.c +++ b/src/select.c | |||
| @@ -21,7 +21,6 @@ static int meth_set(lua_State *L); | |||
| 21 | static int meth_isset(lua_State *L); | 21 | static int meth_isset(lua_State *L); |
| 22 | static int c_select(lua_State *L); | 22 | static int c_select(lua_State *L); |
| 23 | static int global_select(lua_State *L); | 23 | static int global_select(lua_State *L); |
| 24 | static void check_obj_tab(lua_State *L, int tabidx); | ||
| 25 | 24 | ||
| 26 | /* fd_set object methods */ | 25 | /* fd_set object methods */ |
| 27 | static luaL_reg set[] = { | 26 | static luaL_reg set[] = { |
| @@ -68,9 +67,6 @@ static int global_select(lua_State *L) | |||
| 68 | fd_set *read_fd_set, *write_fd_set; | 67 | fd_set *read_fd_set, *write_fd_set; |
| 69 | /* make sure we have enough arguments (nil is the default) */ | 68 | /* make sure we have enough arguments (nil is the default) */ |
| 70 | lua_settop(L, 3); | 69 | lua_settop(L, 3); |
| 71 | /* check object tables */ | ||
| 72 | check_obj_tab(L, 1); | ||
| 73 | check_obj_tab(L, 2); | ||
| 74 | /* check timeout */ | 70 | /* check timeout */ |
| 75 | if (!lua_isnil(L, 3) && !lua_isnumber(L, 3)) | 71 | if (!lua_isnil(L, 3) && !lua_isnumber(L, 3)) |
| 76 | luaL_argerror(L, 3, "number or nil expected"); | 72 | luaL_argerror(L, 3, "number or nil expected"); |
| @@ -127,24 +123,3 @@ static int c_select(lua_State *L) | |||
| 127 | timeout < 0 ? NULL : &tv)); | 123 | timeout < 0 ? NULL : &tv)); |
| 128 | return 1; | 124 | return 1; |
| 129 | } | 125 | } |
| 130 | |||
| 131 | static void check_obj_tab(lua_State *L, int tabidx) | ||
| 132 | { | ||
| 133 | if (tabidx < 0) tabidx = lua_gettop(L) + tabidx + 1; | ||
| 134 | if (lua_istable(L, tabidx)) { | ||
| 135 | lua_pushnil(L); | ||
| 136 | while (lua_next(L, tabidx) != 0) { | ||
| 137 | if (aux_getgroupudata(L, "select{able}", -1) == NULL) { | ||
| 138 | char msg[45]; | ||
| 139 | if (lua_isnumber(L, -2)) | ||
| 140 | sprintf(msg, "table entry #%g is invalid", | ||
| 141 | lua_tonumber(L, -2)); | ||
| 142 | else | ||
| 143 | sprintf(msg, "invalid entry found in table"); | ||
| 144 | luaL_argerror(L, tabidx, msg); | ||
| 145 | } | ||
| 146 | lua_pop(L, 1); | ||
| 147 | } | ||
| 148 | } else if (!lua_isnil(L, tabidx)) | ||
| 149 | luaL_argerror(L, tabidx, "table or nil expected"); | ||
| 150 | } | ||
diff --git a/src/smtp.lua b/src/smtp.lua index ed8bd15..6ddeaae 100644 --- a/src/smtp.lua +++ b/src/smtp.lua | |||
| @@ -20,6 +20,7 @@ DOMAIN = os.getenv("SERVER_NAME") or "localhost" | |||
| 20 | -- default time zone (means we don't know) | 20 | -- default time zone (means we don't know) |
| 21 | ZONE = "-0000" | 21 | ZONE = "-0000" |
| 22 | 22 | ||
| 23 | |||
| 23 | local function shift(a, b, c) | 24 | local function shift(a, b, c) |
| 24 | return b, c | 25 | return b, c |
| 25 | end | 26 | end |
| @@ -29,31 +30,66 @@ function stuff() | |||
| 29 | return ltn12.filter.cycle(dot, 2) | 30 | return ltn12.filter.cycle(dot, 2) |
| 30 | end | 31 | end |
| 31 | 32 | ||
| 33 | --------------------------------------------------------------------------- | ||
| 34 | -- Low level SMTP API | ||
| 35 | ----------------------------------------------------------------------------- | ||
| 36 | local metat = { __index = {} } | ||
| 37 | |||
| 38 | function metat.__index:greet(domain) | ||
| 39 | socket.try(self.tp:check("2..")) | ||
| 40 | socket.try(self.tp:command("EHLO", domain or DOMAIN)) | ||
| 41 | return socket.try(self.tp:check("2..")) | ||
| 42 | end | ||
| 43 | |||
| 44 | function metat.__index:mail(from) | ||
| 45 | socket.try(self.tp:command("MAIL", "FROM:" .. from)) | ||
| 46 | return socket.try(self.tp:check("2..")) | ||
| 47 | end | ||
| 48 | |||
| 49 | function metat.__index:rcpt(to) | ||
| 50 | socket.try(self.tp:command("RCPT", "TO:" .. to)) | ||
| 51 | return socket.try(self.tp:check("2..")) | ||
| 52 | end | ||
| 53 | |||
| 54 | function metat.__index:data(src) | ||
| 55 | socket.try(self.tp:command("DATA")) | ||
| 56 | socket.try(self.tp:check("3..")) | ||
| 57 | socket.try(self.tp:source(src)) | ||
| 58 | socket.try(self.tp:send("\r\n.\r\n")) | ||
| 59 | return socket.try(self.tp:check("2..")) | ||
| 60 | end | ||
| 61 | |||
| 62 | function metat.__index:quit() | ||
| 63 | socket.try(self.tp:command("QUIT")) | ||
| 64 | return socket.try(self.tp:check("2..")) | ||
| 65 | end | ||
| 66 | |||
| 67 | function metat.__index:close() | ||
| 68 | return socket.try(self.tp:close()) | ||
| 69 | end | ||
| 70 | |||
| 32 | -- send message or throw an exception | 71 | -- send message or throw an exception |
| 33 | local function send_p(control, mailt) | 72 | function metat.__index:send(mailt) |
| 34 | socket.try(control:check("2..")) | 73 | self:mail(mailt.from) |
| 35 | socket.try(control:command("EHLO", mailt.domain or DOMAIN)) | ||
| 36 | socket.try(control:check("2..")) | ||
| 37 | socket.try(control:command("MAIL", "FROM:" .. mailt.from)) | ||
| 38 | socket.try(control:check("2..")) | ||
| 39 | if type(mailt.rcpt) == "table" then | 74 | if type(mailt.rcpt) == "table" then |
| 40 | for i,v in ipairs(mailt.rcpt) do | 75 | for i,v in ipairs(mailt.rcpt) do |
| 41 | socket.try(control:command("RCPT", "TO:" .. v)) | 76 | self:rcpt(v) |
| 42 | socket.try(control:check("2..")) | ||
| 43 | end | 77 | end |
| 44 | else | 78 | else |
| 45 | socket.try(control:command("RCPT", "TO:" .. mailt.rcpt)) | 79 | self:rcpt(mailt.rcpt) |
| 46 | socket.try(control:check("2..")) | ||
| 47 | end | 80 | end |
| 48 | socket.try(control:command("DATA")) | 81 | self:data(ltn12.source.chain(mailt.source, stuff())) |
| 49 | socket.try(control:check("3..")) | ||
| 50 | socket.try(control:source(ltn12.source.chain(mailt.source, stuff()))) | ||
| 51 | socket.try(control:send("\r\n.\r\n")) | ||
| 52 | socket.try(control:check("2..")) | ||
| 53 | socket.try(control:command("QUIT")) | ||
| 54 | socket.try(control:check("2..")) | ||
| 55 | end | 82 | end |
| 56 | 83 | ||
| 84 | function open(server, port) | ||
| 85 | local tp, error = socket.tp.connect(server or SERVER, port or PORT) | ||
| 86 | if not tp then return nil, error end | ||
| 87 | return setmetatable({tp = tp}, metat) | ||
| 88 | end | ||
| 89 | |||
| 90 | --------------------------------------------------------------------------- | ||
| 91 | -- Multipart message source | ||
| 92 | ----------------------------------------------------------------------------- | ||
| 57 | -- returns a hopefully unique mime boundary | 93 | -- returns a hopefully unique mime boundary |
| 58 | local seqno = 0 | 94 | local seqno = 0 |
| 59 | local function newboundary() | 95 | local function newboundary() |
| @@ -147,13 +183,17 @@ function message(mesgt) | |||
| 147 | return function() return shift(coroutine.resume(co)) end | 183 | return function() return shift(coroutine.resume(co)) end |
| 148 | end | 184 | end |
| 149 | 185 | ||
| 150 | function send(mailt) | 186 | --------------------------------------------------------------------------- |
| 151 | local c, e = socket.tp.connect(mailt.server or SERVER, mailt.port or PORT) | 187 | -- High level SMTP API |
| 152 | if not c then return nil, e end | 188 | ----------------------------------------------------------------------------- |
| 153 | local s, e = pcall(send_p, c, mailt) | 189 | send = socket.protect(function(mailt) |
| 154 | c:close() | 190 | local server = mailt.server or SERVER |
| 155 | if s then return true | 191 | local port = mailt.port or PORT |
| 156 | else return nil, e end | 192 | local smtp = socket.try(open(server, port)) |
| 157 | end | 193 | smtp:greet(mailt.domain or DOMAIN) |
| 194 | smtp:send(mailt) | ||
| 195 | smtp:quit() | ||
| 196 | return smtp:close() | ||
| 197 | end) | ||
| 158 | 198 | ||
| 159 | return smtp | 199 | return smtp |
| @@ -15,6 +15,7 @@ | |||
| 15 | #include "socket.h" | 15 | #include "socket.h" |
| 16 | #include "inet.h" | 16 | #include "inet.h" |
| 17 | #include "options.h" | 17 | #include "options.h" |
| 18 | #include "base.h" | ||
| 18 | #include "tcp.h" | 19 | #include "tcp.h" |
| 19 | 20 | ||
| 20 | /*=========================================================================*\ | 21 | /*=========================================================================*\ |
| @@ -40,6 +41,7 @@ static int meth_dirty(lua_State *L); | |||
| 40 | /* tcp object methods */ | 41 | /* tcp object methods */ |
| 41 | static luaL_reg tcp[] = { | 42 | static luaL_reg tcp[] = { |
| 42 | {"__gc", meth_close}, | 43 | {"__gc", meth_close}, |
| 44 | {"__tostring", base_meth_tostring}, | ||
| 43 | {"accept", meth_accept}, | 45 | {"accept", meth_accept}, |
| 44 | {"bind", meth_bind}, | 46 | {"bind", meth_bind}, |
| 45 | {"close", meth_close}, | 47 | {"close", meth_close}, |
| @@ -58,7 +60,6 @@ static luaL_reg tcp[] = { | |||
| 58 | {"settimeout", meth_settimeout}, | 60 | {"settimeout", meth_settimeout}, |
| 59 | {"shutdown", meth_shutdown}, | 61 | {"shutdown", meth_shutdown}, |
| 60 | {NULL, NULL} | 62 | {NULL, NULL} |
| 61 | |||
| 62 | }; | 63 | }; |
| 63 | 64 | ||
| 64 | /* socket option handlers */ | 65 | /* socket option handlers */ |
| @@ -18,22 +18,19 @@ setfenv(1, socket.tp) | |||
| 18 | 18 | ||
| 19 | TIMEOUT = 60 | 19 | TIMEOUT = 60 |
| 20 | 20 | ||
| 21 | -- gets server reply | 21 | -- gets server reply (works for SMTP and FTP) |
| 22 | local function get_reply(sock) | 22 | local function get_reply(control) |
| 23 | local code, current, separator, _ | 23 | local code, current, separator, _ |
| 24 | local line, err = sock:receive() | 24 | local line, err = control:receive() |
| 25 | local reply = line | 25 | local reply = line |
| 26 | if err then return nil, err end | 26 | if err then return nil, err end |
| 27 | _, _, code, separator = string.find(line, "^(%d%d%d)(.?)") | 27 | _, _, code, separator = string.find(line, "^(%d%d%d)(.?)") |
| 28 | if not code then return nil, "invalid server reply" end | 28 | if not code then return nil, "invalid server reply" end |
| 29 | if separator == "-" then -- reply is multiline | 29 | if separator == "-" then -- reply is multiline |
| 30 | repeat | 30 | repeat |
| 31 | line, err = sock:receive() | 31 | line, err = control:receive() |
| 32 | if err then return nil, err end | 32 | if err then return nil, err end |
| 33 | _,_, current, separator = string.find(line, "^(%d%d%d)(.)") | 33 | _,_, current, separator = string.find(line, "^(%d%d%d)(.?)") |
| 34 | if not current or not separator then | ||
| 35 | return nil, "invalid server reply" | ||
| 36 | end | ||
| 37 | reply = reply .. "\n" .. line | 34 | reply = reply .. "\n" .. line |
| 38 | -- reply ends with same code | 35 | -- reply ends with same code |
| 39 | until code == current and separator == " " | 36 | until code == current and separator == " " |
| @@ -42,60 +39,73 @@ local function get_reply(sock) | |||
| 42 | end | 39 | end |
| 43 | 40 | ||
| 44 | -- metatable for sock object | 41 | -- metatable for sock object |
| 45 | local metatable = { __index = {} } | 42 | local metat = { __index = {} } |
| 46 | 43 | ||
| 47 | function metatable.__index:check(ok) | 44 | function metat.__index:check(ok) |
| 48 | local code, reply = get_reply(self.sock) | 45 | local code, reply = get_reply(self.control) |
| 49 | if not code then return nil, reply end | 46 | if not code then return nil, reply end |
| 50 | if type(ok) ~= "function" then | 47 | if type(ok) ~= "function" then |
| 51 | if type(ok) == "table" then | 48 | if type(ok) == "table" then |
| 52 | for i, v in ipairs(ok) do | 49 | for i, v in ipairs(ok) do |
| 53 | if string.find(code, v) then return code, reply end | 50 | if string.find(code, v) then return tonumber(code), reply end |
| 54 | end | 51 | end |
| 55 | return nil, reply | 52 | return nil, reply |
| 56 | else | 53 | else |
| 57 | if string.find(code, ok) then return code, reply | 54 | if string.find(code, ok) then return tonumber(code), reply |
| 58 | else return nil, reply end | 55 | else return nil, reply end |
| 59 | end | 56 | end |
| 60 | else return ok(code, reply) end | 57 | else return ok(tonumber(code), reply) end |
| 61 | end | 58 | end |
| 62 | 59 | ||
| 63 | function metatable.__index:command(cmd, arg) | 60 | function metat.__index:command(cmd, arg) |
| 64 | if arg then return self.sock:send(cmd .. " " .. arg.. "\r\n") | 61 | if arg then return self.control:send(cmd .. " " .. arg.. "\r\n") |
| 65 | else return self.sock:send(cmd .. "\r\n") end | 62 | else return self.control:send(cmd .. "\r\n") end |
| 66 | end | 63 | end |
| 67 | 64 | ||
| 68 | function metatable.__index:sink(snk, pat) | 65 | function metat.__index:sink(snk, pat) |
| 69 | local chunk, err = sock:receive(pat) | 66 | local chunk, err = control:receive(pat) |
| 70 | return snk(chunk, err) | 67 | return snk(chunk, err) |
| 71 | end | 68 | end |
| 72 | 69 | ||
| 73 | function metatable.__index:send(data) | 70 | function metat.__index:send(data) |
| 74 | return self.sock:send(data) | 71 | return self.control:send(data) |
| 72 | end | ||
| 73 | |||
| 74 | function metat.__index:receive(pat) | ||
| 75 | return self.control:receive(pat) | ||
| 76 | end | ||
| 77 | |||
| 78 | function metat.__index:getfd() | ||
| 79 | return self.control:getfd() | ||
| 80 | end | ||
| 81 | |||
| 82 | function metat.__index:dirty() | ||
| 83 | return self.control:dirty() | ||
| 75 | end | 84 | end |
| 76 | 85 | ||
| 77 | function metatable.__index:receive(pat) | 86 | function metat.__index:getcontrol() |
| 78 | return self.sock:receive(pat) | 87 | return self.control |
| 79 | end | 88 | end |
| 80 | 89 | ||
| 81 | function metatable.__index:source(src, instr) | 90 | function metat.__index:source(src, instr) |
| 82 | while true do | 91 | while true do |
| 83 | local chunk, err = src() | 92 | local chunk, err = src() |
| 84 | if not chunk then return not err, err end | 93 | if not chunk then return not err, err end |
| 85 | local ret, err = self.sock:send(chunk) | 94 | local ret, err = self.control:send(chunk) |
| 86 | if not ret then return nil, err end | 95 | if not ret then return nil, err end |
| 87 | end | 96 | end |
| 88 | end | 97 | end |
| 89 | 98 | ||
| 90 | -- closes the underlying sock | 99 | -- closes the underlying control |
| 91 | function metatable.__index:close() | 100 | function metat.__index:close() |
| 92 | self.sock:close() | 101 | self.control:close() |
| 102 | return 1 | ||
| 93 | end | 103 | end |
| 94 | 104 | ||
| 95 | -- connect with server and return sock object | 105 | -- connect with server and return control object |
| 96 | function connect(host, port) | 106 | function connect(host, port) |
| 97 | local sock, err = socket.connect(host, port) | 107 | local control, err = socket.connect(host, port) |
| 98 | if not sock then return nil, err end | 108 | if not control then return nil, err end |
| 99 | sock:settimeout(TIMEOUT) | 109 | control:settimeout(TIMEOUT) |
| 100 | return setmetatable({sock = sock}, metatable) | 110 | return setmetatable({control = control}, metat) |
| 101 | end | 111 | end |
| @@ -15,6 +15,7 @@ | |||
| 15 | #include "socket.h" | 15 | #include "socket.h" |
| 16 | #include "inet.h" | 16 | #include "inet.h" |
| 17 | #include "options.h" | 17 | #include "options.h" |
| 18 | #include "base.h" | ||
| 18 | #include "udp.h" | 19 | #include "udp.h" |
| 19 | 20 | ||
| 20 | /*=========================================================================*\ | 21 | /*=========================================================================*\ |
| @@ -50,6 +51,7 @@ static luaL_reg udp[] = { | |||
| 50 | {"close", meth_close}, | 51 | {"close", meth_close}, |
| 51 | {"setoption", meth_setoption}, | 52 | {"setoption", meth_setoption}, |
| 52 | {"__gc", meth_close}, | 53 | {"__gc", meth_close}, |
| 54 | {"__tostring", base_meth_tostring}, | ||
| 53 | {"getfd", meth_getfd}, | 55 | {"getfd", meth_getfd}, |
| 54 | {"setfd", meth_setfd}, | 56 | {"setfd", meth_setfd}, |
| 55 | {"dirty", meth_dirty}, | 57 | {"dirty", meth_dirty}, |
