initial
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								lualib/https.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lualib/https.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										292
									
								
								lualib/ltn12.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								lualib/ltn12.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- LTN12 - Filters, sources, sinks and pumps. | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module | ||||
| ----------------------------------------------------------------------------- | ||||
| local string = require("string") | ||||
| local table = require("table") | ||||
| local base = _G | ||||
| module("ltn12") | ||||
|  | ||||
| filter = {} | ||||
| source = {} | ||||
| sink = {} | ||||
| pump = {} | ||||
|  | ||||
| -- 2048 seems to be better in windows... | ||||
| BLOCKSIZE = 2048 | ||||
| _VERSION = "LTN12 1.0.1" | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Filter stuff | ||||
| ----------------------------------------------------------------------------- | ||||
| -- returns a high level filter that cycles a low-level filter | ||||
| function filter.cycle(low, ctx, extra) | ||||
|     base.assert(low) | ||||
|     return function(chunk) | ||||
|         local ret | ||||
|         ret, ctx = low(ctx, chunk, extra) | ||||
|         return ret | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- chains a bunch of filters together | ||||
| -- (thanks to Wim Couwenberg) | ||||
| function filter.chain(...) | ||||
|     local n = table.getn(arg) | ||||
|     local top, index = 1, 1 | ||||
|     local retry = "" | ||||
|     return function(chunk) | ||||
|         retry = chunk and retry | ||||
|         while true do | ||||
|             if index == top then | ||||
|                 chunk = arg[index](chunk) | ||||
|                 if chunk == "" or top == n then return chunk | ||||
|                 elseif chunk then index = index + 1 | ||||
|                 else | ||||
|                     top = top+1 | ||||
|                     index = top | ||||
|                 end | ||||
|             else | ||||
|                 chunk = arg[index](chunk or "") | ||||
|                 if chunk == "" then | ||||
|                     index = index - 1 | ||||
|                     chunk = retry | ||||
|                 elseif chunk then | ||||
|                     if index == n then return chunk | ||||
|                     else index = index + 1 end | ||||
|                 else base.error("filter returned inappropriate nil") end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Source stuff | ||||
| ----------------------------------------------------------------------------- | ||||
| -- create an empty source | ||||
| local function empty() | ||||
|     return nil | ||||
| end | ||||
|  | ||||
| function source.empty() | ||||
|     return empty | ||||
| end | ||||
|  | ||||
| -- returns a source that just outputs an error | ||||
| function source.error(err) | ||||
|     return function() | ||||
|         return nil, err | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- creates a file source | ||||
| function source.file(handle, io_err) | ||||
|     if handle then | ||||
|         return function() | ||||
|             local chunk = handle:read(BLOCKSIZE) | ||||
|             if not chunk then handle:close() end | ||||
|             return chunk | ||||
|         end | ||||
|     else return source.error(io_err or "unable to open file") end | ||||
| end | ||||
|  | ||||
| -- turns a fancy source into a simple source | ||||
| function source.simplify(src) | ||||
|     base.assert(src) | ||||
|     return function() | ||||
|         local chunk, err_or_new = src() | ||||
|         src = err_or_new or src | ||||
|         if not chunk then return nil, err_or_new | ||||
|         else return chunk end | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- creates string source | ||||
| function source.string(s) | ||||
|     if s then | ||||
|         local i = 1 | ||||
|         return function() | ||||
|             local chunk = string.sub(s, i, i+BLOCKSIZE-1) | ||||
|             i = i + BLOCKSIZE | ||||
|             if chunk ~= "" then return chunk | ||||
|             else return nil end | ||||
|         end | ||||
|     else return source.empty() end | ||||
| end | ||||
|  | ||||
| -- creates rewindable source | ||||
| function source.rewind(src) | ||||
|     base.assert(src) | ||||
|     local t = {} | ||||
|     return function(chunk) | ||||
|         if not chunk then | ||||
|             chunk = table.remove(t) | ||||
|             if not chunk then return src() | ||||
|             else return chunk end | ||||
|         else | ||||
|             table.insert(t, chunk) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function source.chain(src, f) | ||||
|     base.assert(src and f) | ||||
|     local last_in, last_out = "", "" | ||||
|     local state = "feeding" | ||||
|     local err | ||||
|     return function() | ||||
|         if not last_out then | ||||
|             base.error('source is empty!', 2) | ||||
|         end | ||||
|         while true do | ||||
|             if state == "feeding" then | ||||
|                 last_in, err = src() | ||||
|                 if err then return nil, err end | ||||
|                 last_out = f(last_in) | ||||
|                 if not last_out then | ||||
|                     if last_in then | ||||
|                         base.error('filter returned inappropriate nil') | ||||
|                     else | ||||
|                         return nil | ||||
|                     end | ||||
|                 elseif last_out ~= "" then | ||||
|                     state = "eating" | ||||
|                     if last_in then last_in = "" end | ||||
|                     return last_out | ||||
|                 end | ||||
|             else | ||||
|                 last_out = f(last_in) | ||||
|                 if last_out == "" then | ||||
|                     if last_in == "" then | ||||
|                         state = "feeding" | ||||
|                     else | ||||
|                         base.error('filter returned ""') | ||||
|                     end | ||||
|                 elseif not last_out then | ||||
|                     if last_in then | ||||
|                         base.error('filter returned inappropriate nil') | ||||
|                     else | ||||
|                         return nil | ||||
|                     end | ||||
|                 else | ||||
|                     return last_out | ||||
|                 end | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- creates a source that produces contents of several sources, one after the | ||||
| -- other, as if they were concatenated | ||||
| -- (thanks to Wim Couwenberg) | ||||
| function source.cat(...) | ||||
|     local src = table.remove(arg, 1) | ||||
|     return function() | ||||
|         while src do | ||||
|             local chunk, err = src() | ||||
|             if chunk then return chunk end | ||||
|             if err then return nil, err end | ||||
|             src = table.remove(arg, 1) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Sink stuff | ||||
| ----------------------------------------------------------------------------- | ||||
| -- creates a sink that stores into a table | ||||
| function sink.table(t) | ||||
|     t = t or {} | ||||
|     local f = function(chunk, err) | ||||
|         if chunk then table.insert(t, chunk) end | ||||
|         return 1 | ||||
|     end | ||||
|     return f, t | ||||
| end | ||||
|  | ||||
| -- turns a fancy sink into a simple sink | ||||
| function sink.simplify(snk) | ||||
|     base.assert(snk) | ||||
|     return function(chunk, err) | ||||
|         local ret, err_or_new = snk(chunk, err) | ||||
|         if not ret then return nil, err_or_new end | ||||
|         snk = err_or_new or snk | ||||
|         return 1 | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- creates a file sink | ||||
| function sink.file(handle, io_err) | ||||
|     if handle then | ||||
|         return function(chunk, err) | ||||
|             if not chunk then | ||||
|                 handle:close() | ||||
|                 return 1 | ||||
|             else return handle:write(chunk) end | ||||
|         end | ||||
|     else return sink.error(io_err or "unable to open file") end | ||||
| end | ||||
|  | ||||
| -- creates a sink that discards data | ||||
| local function null() | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function sink.null() | ||||
|     return null | ||||
| end | ||||
|  | ||||
| -- creates a sink that just returns an error | ||||
| function sink.error(err) | ||||
|     return function() | ||||
|         return nil, err | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- chains a sink with a filter | ||||
| function sink.chain(f, snk) | ||||
|     base.assert(f and snk) | ||||
|     return function(chunk, err) | ||||
|         if chunk ~= "" then | ||||
|             local filtered = f(chunk) | ||||
|             local done = chunk and "" | ||||
|             while true do | ||||
|                 local ret, snkerr = snk(filtered, err) | ||||
|                 if not ret then return nil, snkerr end | ||||
|                 if filtered == done then return 1 end | ||||
|                 filtered = f(done) | ||||
|             end | ||||
|         else return 1 end | ||||
|     end | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Pump stuff | ||||
| ----------------------------------------------------------------------------- | ||||
| -- pumps one chunk from the source to the sink | ||||
| function pump.step(src, snk) | ||||
|     local chunk, src_err = src() | ||||
|     local ret, snk_err = snk(chunk, src_err) | ||||
|     if chunk and ret then return 1 | ||||
|     else return nil, src_err or snk_err end | ||||
| end | ||||
|  | ||||
| -- pumps all data from a source to a sink, using a step function | ||||
| function pump.all(src, snk, step) | ||||
|     base.assert(src and snk) | ||||
|     step = step or pump.step | ||||
|     while true do | ||||
|         local ret, err = step(src, snk) | ||||
|         if not ret then | ||||
|             if err then return nil, err | ||||
|             else return 1 end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
							
								
								
									
										87
									
								
								lualib/mime.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								lualib/mime.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- MIME support for the Lua language. | ||||
| -- Author: Diego Nehab | ||||
| -- Conforming to RFCs 2045-2049 | ||||
| -- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ----------------------------------------------------------------------------- | ||||
| local base = _G | ||||
| local ltn12 = require("ltn12") | ||||
| local mime = require("mime.core") | ||||
| --local io = require("io") | ||||
| local string = require("string") | ||||
| module("mime") | ||||
|  | ||||
| -- encode, decode and wrap algorithm tables | ||||
| encodet = {} | ||||
| decodet = {} | ||||
| wrapt = {} | ||||
|  | ||||
| -- creates a function that chooses a filter by name from a given table | ||||
| local function choose(table) | ||||
|     return function(name, opt1, opt2) | ||||
|         if base.type(name) ~= "string" then | ||||
|             name, opt1, opt2 = "default", name, opt1 | ||||
|         end | ||||
|         local f = table[name or "nil"] | ||||
|         if not f then  | ||||
|             base.error("unknown key (" .. base.tostring(name) .. ")", 3) | ||||
|         else return f(opt1, opt2) end | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- define the encoding filters | ||||
| encodet['base64'] = function() | ||||
|     return ltn12.filter.cycle(b64, "") | ||||
| end | ||||
|  | ||||
| encodet['quoted-printable'] = function(mode) | ||||
|     return ltn12.filter.cycle(qp, "", | ||||
|         (mode == "binary") and "=0D=0A" or "\r\n") | ||||
| end | ||||
|  | ||||
| -- define the decoding filters | ||||
| decodet['base64'] = function() | ||||
|     return ltn12.filter.cycle(unb64, "") | ||||
| end | ||||
|  | ||||
| decodet['quoted-printable'] = function() | ||||
|     return ltn12.filter.cycle(unqp, "") | ||||
| end | ||||
|  | ||||
| local function format(chunk) | ||||
|     if chunk then | ||||
|         if chunk == "" then return "''" | ||||
|         else return string.len(chunk) end | ||||
|     else return "nil" end | ||||
| end | ||||
|  | ||||
| -- define the line-wrap filters | ||||
| wrapt['text'] = function(length) | ||||
|     length = length or 76 | ||||
|     return ltn12.filter.cycle(wrp, length, length) | ||||
| end | ||||
| wrapt['base64'] = wrapt['text'] | ||||
| wrapt['default'] = wrapt['text'] | ||||
|  | ||||
| wrapt['quoted-printable'] = function() | ||||
|     return ltn12.filter.cycle(qpwrp, 76, 76) | ||||
| end | ||||
|  | ||||
| -- function that choose the encoding, decoding or wrap algorithm | ||||
| encode = choose(encodet) | ||||
| decode = choose(decodet) | ||||
| wrap = choose(wrapt) | ||||
|  | ||||
| -- define the end-of-line normalization filter | ||||
| function normalize(marker) | ||||
|     return ltn12.filter.cycle(eol, 0, marker) | ||||
| end | ||||
|  | ||||
| -- high level stuffing filter | ||||
| function stuff() | ||||
|     return ltn12.filter.cycle(dot, 2) | ||||
| end | ||||
							
								
								
									
										
											BIN
										
									
								
								lualib/mime/core.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lualib/mime/core.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										133
									
								
								lualib/socket.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lualib/socket.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- LuaSocket helper module | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ----------------------------------------------------------------------------- | ||||
| local base = _G | ||||
| local string = require("string") | ||||
| local math = require("math") | ||||
| local socket = require("socket.core") | ||||
| module("socket") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Exported auxiliar functions | ||||
| ----------------------------------------------------------------------------- | ||||
| function connect(address, port, laddress, lport) | ||||
|     local sock, err = socket.tcp() | ||||
|     if not sock then return nil, err end | ||||
|     if laddress then | ||||
|         local res, err = sock:bind(laddress, lport, -1) | ||||
|         if not res then return nil, err end | ||||
|     end | ||||
|     local res, err = sock:connect(address, port) | ||||
|     if not res then return nil, err end | ||||
|     return sock | ||||
| end | ||||
|  | ||||
| function bind(host, port, backlog) | ||||
|     local sock, err = socket.tcp() | ||||
|     if not sock then return nil, err end | ||||
|     sock:setoption("reuseaddr", true) | ||||
|     local res, err = sock:bind(host, port) | ||||
|     if not res then return nil, err end | ||||
|     res, err = sock:listen(backlog) | ||||
|     if not res then return nil, err end | ||||
|     return sock | ||||
| end | ||||
|  | ||||
| try = newtry() | ||||
|  | ||||
| function choose(table) | ||||
|     return function(name, opt1, opt2) | ||||
|         if base.type(name) ~= "string" then | ||||
|             name, opt1, opt2 = "default", name, opt1 | ||||
|         end | ||||
|         local f = table[name or "nil"] | ||||
|         if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) | ||||
|         else return f(opt1, opt2) end | ||||
|     end | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Socket sources and sinks, conforming to LTN12 | ||||
| ----------------------------------------------------------------------------- | ||||
| -- create namespaces inside LuaSocket namespace | ||||
| sourcet = {} | ||||
| sinkt = {} | ||||
|  | ||||
| BLOCKSIZE = 2048 | ||||
|  | ||||
| sinkt["close-when-done"] = function(sock) | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function(self, chunk, err) | ||||
|             if not chunk then | ||||
|                 sock:close() | ||||
|                 return 1 | ||||
|             else return sock:send(chunk) end | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
| sinkt["keep-open"] = function(sock) | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function(self, chunk, err) | ||||
|             if chunk then return sock:send(chunk) | ||||
|             else return 1 end | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
| sinkt["default"] = sinkt["keep-open"] | ||||
|  | ||||
| sink = choose(sinkt) | ||||
|  | ||||
| sourcet["by-length"] = function(sock, length) | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function() | ||||
|             if length <= 0 then return nil end | ||||
|             local size = math.min(socket.BLOCKSIZE, length) | ||||
|             local chunk, err = sock:receive(size) | ||||
|             if err then return nil, err end | ||||
|             length = length - string.len(chunk) | ||||
|             return chunk | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
| sourcet["until-closed"] = function(sock) | ||||
|     local done | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function() | ||||
|             if done then return nil end | ||||
|             local chunk, err, partial = sock:receive(socket.BLOCKSIZE) | ||||
|             if not err then return chunk | ||||
|             elseif err == "closed" then | ||||
|                 sock:close() | ||||
|                 done = 1 | ||||
|                 return partial | ||||
|             else return nil, err end | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
|  | ||||
| sourcet["default"] = sourcet["until-closed"] | ||||
|  | ||||
| source = choose(sourcet) | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								lualib/socket/core.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								lualib/socket/core.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										281
									
								
								lualib/socket/ftp.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								lualib/socket/ftp.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,281 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- FTP support for the Lua language | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ----------------------------------------------------------------------------- | ||||
| local base = _G | ||||
| local table = require("table") | ||||
| local string = require("string") | ||||
| local math = require("math") | ||||
| local socket = require("socket") | ||||
| local url = require("socket.url") | ||||
| local tp = require("socket.tp") | ||||
| local ltn12 = require("ltn12") | ||||
| module("socket.ftp") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Program constants | ||||
| ----------------------------------------------------------------------------- | ||||
| -- timeout in seconds before the program gives up on a connection | ||||
| TIMEOUT = 60 | ||||
| -- default port for ftp service | ||||
| PORT = 21 | ||||
| -- this is the default anonymous password. used when no password is | ||||
| -- provided in url. should be changed to your e-mail. | ||||
| USER = "ftp" | ||||
| PASSWORD = "anonymous@anonymous.org" | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Low level FTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| local metat = { __index = {} } | ||||
|  | ||||
| function open(server, port, create) | ||||
|     local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) | ||||
|     local f = base.setmetatable({ tp = tp }, metat) | ||||
|     -- make sure everything gets closed in an exception | ||||
|     f.try = socket.newtry(function() f:close() end) | ||||
|     return f | ||||
| end | ||||
|  | ||||
| function metat.__index:portconnect() | ||||
|     self.try(self.server:settimeout(TIMEOUT)) | ||||
|     self.data = self.try(self.server:accept()) | ||||
|     self.try(self.data:settimeout(TIMEOUT)) | ||||
| end | ||||
|  | ||||
| function metat.__index:pasvconnect() | ||||
|     self.data = self.try(socket.tcp()) | ||||
|     self.try(self.data:settimeout(TIMEOUT)) | ||||
|     self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) | ||||
| end | ||||
|  | ||||
| function metat.__index:login(user, password) | ||||
|     self.try(self.tp:command("user", user or USER)) | ||||
|     local code, reply = self.try(self.tp:check{"2..", 331}) | ||||
|     if code == 331 then | ||||
|         self.try(self.tp:command("pass", password or PASSWORD)) | ||||
|         self.try(self.tp:check("2..")) | ||||
|     end | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:pasv() | ||||
|     self.try(self.tp:command("pasv")) | ||||
|     local code, reply = self.try(self.tp:check("2..")) | ||||
|     local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" | ||||
|     local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) | ||||
|     self.try(a and b and c and d and p1 and p2, reply) | ||||
|     self.pasvt = { | ||||
|         ip = string.format("%d.%d.%d.%d", a, b, c, d), | ||||
|         port = p1*256 + p2 | ||||
|     } | ||||
|     if self.server then | ||||
|         self.server:close() | ||||
|         self.server = nil | ||||
|     end | ||||
|     return self.pasvt.ip, self.pasvt.port | ||||
| end | ||||
|  | ||||
| function metat.__index:port(ip, port) | ||||
|     self.pasvt = nil | ||||
|     if not ip then | ||||
|         ip, port = self.try(self.tp:getcontrol():getsockname()) | ||||
|         self.server = self.try(socket.bind(ip, 0)) | ||||
|         ip, port = self.try(self.server:getsockname()) | ||||
|         self.try(self.server:settimeout(TIMEOUT)) | ||||
|     end | ||||
|     local pl = math.mod(port, 256) | ||||
|     local ph = (port - pl)/256 | ||||
|     local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||||
|     self.try(self.tp:command("port", arg)) | ||||
|     self.try(self.tp:check("2..")) | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:send(sendt) | ||||
|     self.try(self.pasvt or self.server, "need port or pasv first") | ||||
|     -- if there is a pasvt table, we already sent a PASV command | ||||
|     -- we just get the data connection into self.data | ||||
|     if self.pasvt then self:pasvconnect() end | ||||
|     -- get the transfer argument and command | ||||
|     local argument = sendt.argument or | ||||
|         url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) | ||||
|     if argument == "" then argument = nil end | ||||
|     local command = sendt.command or "stor" | ||||
|     -- send the transfer command and check the reply | ||||
|     self.try(self.tp:command(command, argument)) | ||||
|     local code, reply = self.try(self.tp:check{"2..", "1.."}) | ||||
|     -- if there is not a a pasvt table, then there is a server | ||||
|     -- and we already sent a PORT command | ||||
|     if not self.pasvt then self:portconnect() end | ||||
|     -- get the sink, source and step for the transfer | ||||
|     local step = sendt.step or ltn12.pump.step | ||||
|     local readt = {self.tp.c} | ||||
|     local checkstep = function(src, snk) | ||||
|         -- check status in control connection while downloading | ||||
|         local readyt = socket.select(readt, nil, 0) | ||||
|         if readyt[tp] then code = self.try(self.tp:check("2..")) end | ||||
|         return step(src, snk) | ||||
|     end | ||||
|     local sink = socket.sink("close-when-done", self.data) | ||||
|     -- transfer all data and check error | ||||
|     self.try(ltn12.pump.all(sendt.source, sink, checkstep)) | ||||
|     if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||||
|     -- done with data connection | ||||
|     self.data:close() | ||||
|     -- find out how many bytes were sent | ||||
|     local sent = socket.skip(1, self.data:getstats()) | ||||
|     self.data = nil | ||||
|     return sent | ||||
| end | ||||
|  | ||||
| function metat.__index:receive(recvt) | ||||
|     self.try(self.pasvt or self.server, "need port or pasv first") | ||||
|     if self.pasvt then self:pasvconnect() end | ||||
|     local argument = recvt.argument or | ||||
|         url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) | ||||
|     if argument == "" then argument = nil end | ||||
|     local command = recvt.command or "retr" | ||||
|     self.try(self.tp:command(command, argument)) | ||||
|     local code = self.try(self.tp:check{"1..", "2.."}) | ||||
|     if not self.pasvt then self:portconnect() end | ||||
|     local source = socket.source("until-closed", self.data) | ||||
|     local step = recvt.step or ltn12.pump.step | ||||
|     self.try(ltn12.pump.all(source, recvt.sink, step)) | ||||
|     if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||||
|     self.data:close() | ||||
|     self.data = nil | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:cwd(dir) | ||||
|     self.try(self.tp:command("cwd", dir)) | ||||
|     self.try(self.tp:check(250)) | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:type(type) | ||||
|     self.try(self.tp:command("type", type)) | ||||
|     self.try(self.tp:check(200)) | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:greet() | ||||
|     local code = self.try(self.tp:check{"1..", "2.."}) | ||||
|     if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:quit() | ||||
|     self.try(self.tp:command("quit")) | ||||
|     self.try(self.tp:check("2..")) | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:close() | ||||
|     if self.data then self.data:close() end | ||||
|     if self.server then self.server:close() end | ||||
|     return self.tp:close() | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- High level FTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| local function override(t) | ||||
|     if t.url then | ||||
|         local u = url.parse(t.url) | ||||
|         for i,v in base.pairs(t) do | ||||
|             u[i] = v | ||||
|         end | ||||
|         return u | ||||
|     else return t end | ||||
| end | ||||
|  | ||||
| local function tput(putt) | ||||
|     putt = override(putt) | ||||
|     socket.try(putt.host, "missing hostname") | ||||
|     local f = open(putt.host, putt.port, putt.create) | ||||
|     f:greet() | ||||
|     f:login(putt.user, putt.password) | ||||
|     if putt.type then f:type(putt.type) end | ||||
|     f:pasv() | ||||
|     local sent = f:send(putt) | ||||
|     f:quit() | ||||
|     f:close() | ||||
|     return sent | ||||
| end | ||||
|  | ||||
| local default = { | ||||
| 	path = "/", | ||||
| 	scheme = "ftp" | ||||
| } | ||||
|  | ||||
| local function parse(u) | ||||
|     local t = socket.try(url.parse(u, default)) | ||||
|     socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") | ||||
|     socket.try(t.host, "missing hostname") | ||||
|     local pat = "^type=(.)$" | ||||
|     if t.params then | ||||
|         t.type = socket.skip(2, string.find(t.params, pat)) | ||||
|         socket.try(t.type == "a" or t.type == "i", | ||||
|             "invalid type '" .. t.type .. "'") | ||||
|     end | ||||
|     return t | ||||
| end | ||||
|  | ||||
| local function sput(u, body) | ||||
|     local putt = parse(u) | ||||
|     putt.source = ltn12.source.string(body) | ||||
|     return tput(putt) | ||||
| end | ||||
|  | ||||
| put = socket.protect(function(putt, body) | ||||
|     if base.type(putt) == "string" then return sput(putt, body) | ||||
|     else return tput(putt) end | ||||
| end) | ||||
|  | ||||
| local function tget(gett) | ||||
|     gett = override(gett) | ||||
|     socket.try(gett.host, "missing hostname") | ||||
|     local f = open(gett.host, gett.port, gett.create) | ||||
|     f:greet() | ||||
|     f:login(gett.user, gett.password) | ||||
|     if gett.type then f:type(gett.type) end | ||||
|     f:pasv() | ||||
|     f:receive(gett) | ||||
|     f:quit() | ||||
|     return f:close() | ||||
| end | ||||
|  | ||||
| local function sget(u) | ||||
|     local gett = parse(u) | ||||
|     local t = {} | ||||
|     gett.sink = ltn12.sink.table(t) | ||||
|     tget(gett) | ||||
|     return table.concat(t) | ||||
| end | ||||
|  | ||||
| command = socket.protect(function(cmdt) | ||||
|     cmdt = override(cmdt) | ||||
|     socket.try(cmdt.host, "missing hostname") | ||||
|     socket.try(cmdt.command, "missing command") | ||||
|     local f = open(cmdt.host, cmdt.port, cmdt.create) | ||||
|     f:greet() | ||||
|     f:login(cmdt.user, cmdt.password) | ||||
|     f.try(f.tp:command(cmdt.command, cmdt.argument)) | ||||
|     if cmdt.check then f.try(f.tp:check(cmdt.check)) end | ||||
|     f:quit() | ||||
|     return f:close() | ||||
| end) | ||||
|  | ||||
| get = socket.protect(function(gett) | ||||
|     if base.type(gett) == "string" then return sget(gett) | ||||
|     else return tget(gett) end | ||||
| end) | ||||
|  | ||||
							
								
								
									
										350
									
								
								lualib/socket/http.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								lualib/socket/http.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,350 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- HTTP/1.1 client support for the Lua language. | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: http.lua,v 1.70 2007/03/12 04:08:40 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ------------------------------------------------------------------------------- | ||||
| local socket = require("socket") | ||||
| local url = require("socket.url") | ||||
| local ltn12 = require("ltn12") | ||||
| local mime = require("mime") | ||||
| local string = require("string") | ||||
| local base = _G | ||||
| local table = require("table") | ||||
| module("socket.http") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Program constants | ||||
| ----------------------------------------------------------------------------- | ||||
| -- connection timeout in seconds | ||||
| TIMEOUT = 60 | ||||
| -- default port for document retrieval | ||||
| PORT = 80 | ||||
| -- user agent field sent in request | ||||
| USERAGENT = socket._VERSION | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Reads MIME headers from a connection, unfolding where needed | ||||
| ----------------------------------------------------------------------------- | ||||
| local function receiveheaders(sock, headers) | ||||
|     local line, name, value, err | ||||
|     headers = headers or {} | ||||
|     -- get first line | ||||
|     line, err = sock:receive() | ||||
|     if err then return nil, err end | ||||
|     -- headers go until a blank line is found | ||||
|     while line ~= "" do | ||||
|         -- get field-name and value | ||||
|         name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) | ||||
|         if not (name and value) then return nil, "malformed reponse headers" end | ||||
|         name = string.lower(name) | ||||
|         -- get next line (value might be folded) | ||||
|         line, err  = sock:receive() | ||||
|         if err then return nil, err end | ||||
|         -- unfold any folded values | ||||
|         while string.find(line, "^%s") do | ||||
|             value = value .. line | ||||
|             line = sock:receive() | ||||
|             if err then return nil, err end | ||||
|         end | ||||
|         -- save pair in table | ||||
|         if headers[name] then headers[name] = headers[name] .. ", " .. value | ||||
|         else headers[name] = value end | ||||
|     end | ||||
|     return headers | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Extra sources and sinks | ||||
| ----------------------------------------------------------------------------- | ||||
| socket.sourcet["http-chunked"] = function(sock, headers) | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function() | ||||
|             -- get chunk size, skip extention | ||||
|             local line, err = sock:receive() | ||||
|             if err then return nil, err end | ||||
|             local size = base.tonumber(string.gsub(line, ";.*", ""), 16) | ||||
|             if not size then return nil, "invalid chunk size" end | ||||
|             -- was it the last chunk? | ||||
|             if size > 0 then | ||||
|                 -- if not, get chunk and skip terminating CRLF | ||||
|                 local chunk, err, part = sock:receive(size) | ||||
|                 if chunk then sock:receive() end | ||||
|                 return chunk, err | ||||
|             else | ||||
|                 -- if it was, read trailers into headers table | ||||
|                 headers, err = receiveheaders(sock, headers) | ||||
|                 if not headers then return nil, err end | ||||
|             end | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
| socket.sinkt["http-chunked"] = function(sock) | ||||
|     return base.setmetatable({ | ||||
|         getfd = function() return sock:getfd() end, | ||||
|         dirty = function() return sock:dirty() end | ||||
|     }, { | ||||
|         __call = function(self, chunk, err) | ||||
|             if not chunk then return sock:send("0\r\n\r\n") end | ||||
|             local size = string.format("%X\r\n", string.len(chunk)) | ||||
|             return sock:send(size ..  chunk .. "\r\n") | ||||
|         end | ||||
|     }) | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Low level HTTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| local metat = { __index = {} } | ||||
|  | ||||
| function open(host, port, create) | ||||
|     -- create socket with user connect function, or with default | ||||
|     local c = socket.try((create or socket.tcp)()) | ||||
|     local h = base.setmetatable({ c = c }, metat) | ||||
|     -- create finalized try | ||||
|     h.try = socket.newtry(function() h:close() end) | ||||
|     -- set timeout before connecting | ||||
|     h.try(c:settimeout(TIMEOUT)) | ||||
|     h.try(c:connect(host, port or PORT)) | ||||
|     -- here everything worked | ||||
|     return h | ||||
| end | ||||
|  | ||||
| function metat.__index:sendrequestline(method, uri) | ||||
|     local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) | ||||
|     return self.try(self.c:send(reqline)) | ||||
| end | ||||
|  | ||||
| function metat.__index:sendheaders(headers) | ||||
|     local h = "\r\n" | ||||
|     for i, v in base.pairs(headers) do | ||||
|         h = i .. ": " .. v .. "\r\n" .. h | ||||
|     end | ||||
|     self.try(self.c:send(h)) | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| function metat.__index:sendbody(headers, source, step) | ||||
|     source = source or ltn12.source.empty() | ||||
|     step = step or ltn12.pump.step | ||||
|     -- if we don't know the size in advance, send chunked and hope for the best | ||||
|     local mode = "http-chunked" | ||||
|     if headers["content-length"] then mode = "keep-open" end | ||||
|     return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) | ||||
| end | ||||
|  | ||||
| function metat.__index:receivestatusline() | ||||
|     local status = self.try(self.c:receive(5)) | ||||
|     -- identify HTTP/0.9 responses, which do not contain a status line | ||||
|     -- this is just a heuristic, but is what the RFC recommends | ||||
|     if status ~= "HTTP/" then return nil, status end | ||||
|     -- otherwise proceed reading a status line | ||||
|     status = self.try(self.c:receive("*l", status)) | ||||
|     local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) | ||||
|     return self.try(base.tonumber(code), status) | ||||
| end | ||||
|  | ||||
| function metat.__index:receiveheaders() | ||||
|     return self.try(receiveheaders(self.c)) | ||||
| end | ||||
|  | ||||
| function metat.__index:receivebody(headers, sink, step) | ||||
|     sink = sink or ltn12.sink.null() | ||||
|     step = step or ltn12.pump.step | ||||
|     local length = base.tonumber(headers["content-length"]) | ||||
|     local t = headers["transfer-encoding"] -- shortcut | ||||
|     local mode = "default" -- connection close | ||||
|     if t and t ~= "identity" then mode = "http-chunked" | ||||
|     elseif base.tonumber(headers["content-length"]) then mode = "by-length" end | ||||
|     return self.try(ltn12.pump.all(socket.source(mode, self.c, length), | ||||
|         sink, step)) | ||||
| end | ||||
|  | ||||
| function metat.__index:receive09body(status, sink, step) | ||||
|     local source = ltn12.source.rewind(socket.source("until-closed", self.c)) | ||||
|     source(status) | ||||
|     return self.try(ltn12.pump.all(source, sink, step)) | ||||
| end | ||||
|  | ||||
| function metat.__index:close() | ||||
|     return self.c:close() | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- High level HTTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| local function adjusturi(reqt) | ||||
|     local u = reqt | ||||
|     -- if there is a proxy, we need the full url. otherwise, just a part. | ||||
|     if not reqt.proxy and not PROXY then | ||||
|         u = { | ||||
|            path = socket.try(reqt.path, "invalid path 'nil'"), | ||||
|            params = reqt.params, | ||||
|            query = reqt.query, | ||||
|            fragment = reqt.fragment | ||||
|         } | ||||
|     end | ||||
|     return url.build(u) | ||||
| end | ||||
|  | ||||
| local function adjustproxy(reqt) | ||||
|     local proxy = reqt.proxy or PROXY | ||||
|     if proxy then | ||||
|         proxy = url.parse(proxy) | ||||
|         return proxy.host, proxy.port or 3128 | ||||
|     else | ||||
|         return reqt.host, reqt.port | ||||
|     end | ||||
| end | ||||
|  | ||||
| local function adjustheaders(reqt) | ||||
|     -- default headers | ||||
|     local lower = { | ||||
|         ["user-agent"] = USERAGENT, | ||||
|         ["host"] = reqt.host, | ||||
|         ["connection"] = "close, TE", | ||||
|         ["te"] = "trailers" | ||||
|     } | ||||
|     -- if we have authentication information, pass it along | ||||
|     if reqt.user and reqt.password then | ||||
|         lower["authorization"] =  | ||||
|             "Basic " ..  (mime.b64(reqt.user .. ":" .. reqt.password)) | ||||
|     end | ||||
|     -- override with user headers | ||||
|     for i,v in base.pairs(reqt.headers or lower) do | ||||
|         lower[string.lower(i)] = v | ||||
|     end | ||||
|     return lower | ||||
| end | ||||
|  | ||||
| -- default url parts | ||||
| local default = { | ||||
|     host = "", | ||||
|     port = PORT, | ||||
|     path ="/", | ||||
|     scheme = "http" | ||||
| } | ||||
|  | ||||
| local function adjustrequest(reqt) | ||||
|     -- parse url if provided | ||||
|     local nreqt = reqt.url and url.parse(reqt.url, default) or {} | ||||
|     -- explicit components override url | ||||
|     for i,v in base.pairs(reqt) do nreqt[i] = v end | ||||
|     if nreqt.port == "" then nreqt.port = 80 end | ||||
|     socket.try(nreqt.host and nreqt.host ~= "",  | ||||
|         "invalid host '" .. base.tostring(nreqt.host) .. "'") | ||||
|     -- compute uri if user hasn't overriden | ||||
|     nreqt.uri = reqt.uri or adjusturi(nreqt) | ||||
|     -- ajust host and port if there is a proxy | ||||
|     nreqt.host, nreqt.port = adjustproxy(nreqt) | ||||
|     -- adjust headers in request | ||||
|     nreqt.headers = adjustheaders(nreqt) | ||||
|     return nreqt | ||||
| end | ||||
|  | ||||
| local function shouldredirect(reqt, code, headers) | ||||
|     return headers.location and | ||||
|            string.gsub(headers.location, "%s", "") ~= "" and | ||||
|            (reqt.redirect ~= false) and | ||||
|            (code == 301 or code == 302) and | ||||
|            (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") | ||||
|            and (not reqt.nredirects or reqt.nredirects < 5) | ||||
| end | ||||
|  | ||||
| local function shouldreceivebody(reqt, code) | ||||
|     if reqt.method == "HEAD" then return nil end | ||||
|     if code == 204 or code == 304 then return nil end | ||||
|     if code >= 100 and code < 200 then return nil end | ||||
|     return 1 | ||||
| end | ||||
|  | ||||
| -- forward declarations | ||||
| local trequest, tredirect | ||||
|  | ||||
| function tredirect(reqt, location) | ||||
|     local result, code, headers, status = trequest { | ||||
|         -- the RFC says the redirect URL has to be absolute, but some | ||||
|         -- servers do not respect that | ||||
|         url = url.absolute(reqt.url, location), | ||||
|         source = reqt.source, | ||||
|         sink = reqt.sink, | ||||
|         headers = reqt.headers, | ||||
|         proxy = reqt.proxy,  | ||||
|         nredirects = (reqt.nredirects or 0) + 1, | ||||
|         create = reqt.create | ||||
|     }    | ||||
|     -- pass location header back as a hint we redirected | ||||
|     headers = headers or {} | ||||
|     headers.location = headers.location or location | ||||
|     return result, code, headers, status | ||||
| end | ||||
|  | ||||
| function trequest(reqt) | ||||
|     -- we loop until we get what we want, or | ||||
|     -- until we are sure there is no way to get it | ||||
|     local nreqt = adjustrequest(reqt) | ||||
|     local h = open(nreqt.host, nreqt.port, nreqt.create) | ||||
|     -- send request line and headers | ||||
|     h:sendrequestline(nreqt.method, nreqt.uri) | ||||
|     h:sendheaders(nreqt.headers) | ||||
|     -- if there is a body, send it | ||||
|     if nreqt.source then | ||||
|         h:sendbody(nreqt.headers, nreqt.source, nreqt.step)  | ||||
|     end | ||||
|     local code, status = h:receivestatusline() | ||||
|     -- if it is an HTTP/0.9 server, simply get the body and we are done | ||||
|     if not code then | ||||
|         h:receive09body(status, nreqt.sink, nreqt.step) | ||||
|         return 1, 200 | ||||
|     end | ||||
|     local headers | ||||
|     -- ignore any 100-continue messages | ||||
|     while code == 100 do  | ||||
|         headers = h:receiveheaders() | ||||
|         code, status = h:receivestatusline() | ||||
|     end | ||||
|     headers = h:receiveheaders() | ||||
|     -- at this point we should have a honest reply from the server | ||||
|     -- we can't redirect if we already used the source, so we report the error  | ||||
|     if shouldredirect(nreqt, code, headers) and not nreqt.source then | ||||
|         h:close() | ||||
|         return tredirect(reqt, headers.location) | ||||
|     end | ||||
|     -- here we are finally done | ||||
|     if shouldreceivebody(nreqt, code) then | ||||
|         h:receivebody(headers, nreqt.sink, nreqt.step) | ||||
|     end | ||||
|     h:close() | ||||
|     return 1, code, headers, status | ||||
| end | ||||
|  | ||||
| local function srequest(u, b) | ||||
|     local t = {} | ||||
|     local reqt = { | ||||
|         url = u, | ||||
|         sink = ltn12.sink.table(t) | ||||
|     } | ||||
|     if b then | ||||
|         reqt.source = ltn12.source.string(b) | ||||
|         reqt.headers = { | ||||
|             ["content-length"] = string.len(b), | ||||
|             ["content-type"] = "application/x-www-form-urlencoded" | ||||
|         } | ||||
|         reqt.method = "POST" | ||||
|     end | ||||
|     local code, headers, status = socket.skip(1, trequest(reqt)) | ||||
|     return table.concat(t), code, headers, status | ||||
| end | ||||
|  | ||||
| request = socket.protect(function(reqt, body) | ||||
|     if base.type(reqt) == "string" then return srequest(reqt, body) | ||||
|     else return trequest(reqt) end | ||||
| end) | ||||
							
								
								
									
										251
									
								
								lualib/socket/smtp.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								lualib/socket/smtp.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- SMTP client support for the Lua language. | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: smtp.lua,v 1.46 2007/03/12 04:08:40 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ----------------------------------------------------------------------------- | ||||
| local base = _G | ||||
| local coroutine = require("coroutine") | ||||
| local string = require("string") | ||||
| local math = require("math") | ||||
| local os = require("os") | ||||
| local socket = require("socket") | ||||
| local tp = require("socket.tp") | ||||
| local ltn12 = require("ltn12") | ||||
| local mime = require("mime") | ||||
| module("socket.smtp") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Program constants | ||||
| ----------------------------------------------------------------------------- | ||||
| -- timeout for connection | ||||
| TIMEOUT = 60 | ||||
| -- default server used to send e-mails | ||||
| SERVER = "localhost" | ||||
| -- default port | ||||
| PORT = 25 | ||||
| -- domain used in HELO command and default sendmail | ||||
| -- If we are under a CGI, try to get from environment | ||||
| DOMAIN = os.getenv("SERVER_NAME") or "localhost" | ||||
| -- default time zone (means we don't know) | ||||
| ZONE = "-0000" | ||||
|  | ||||
| --------------------------------------------------------------------------- | ||||
| -- Low level SMTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| local metat = { __index = {} } | ||||
|  | ||||
| function metat.__index:greet(domain) | ||||
|     self.try(self.tp:check("2..")) | ||||
|     self.try(self.tp:command("EHLO", domain or DOMAIN)) | ||||
|     return socket.skip(1, self.try(self.tp:check("2.."))) | ||||
| end | ||||
|  | ||||
| function metat.__index:mail(from) | ||||
|     self.try(self.tp:command("MAIL", "FROM:" .. from)) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:rcpt(to) | ||||
|     self.try(self.tp:command("RCPT", "TO:" .. to)) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:data(src, step) | ||||
|     self.try(self.tp:command("DATA")) | ||||
|     self.try(self.tp:check("3..")) | ||||
|     self.try(self.tp:source(src, step)) | ||||
|     self.try(self.tp:send("\r\n.\r\n")) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:quit() | ||||
|     self.try(self.tp:command("QUIT")) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:close() | ||||
|     return self.tp:close() | ||||
| end | ||||
|  | ||||
| function metat.__index:login(user, password) | ||||
|     self.try(self.tp:command("AUTH", "LOGIN")) | ||||
|     self.try(self.tp:check("3..")) | ||||
|     self.try(self.tp:command(mime.b64(user))) | ||||
|     self.try(self.tp:check("3..")) | ||||
|     self.try(self.tp:command(mime.b64(password))) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:plain(user, password) | ||||
|     local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) | ||||
|     self.try(self.tp:command("AUTH", auth)) | ||||
|     return self.try(self.tp:check("2..")) | ||||
| end | ||||
|  | ||||
| function metat.__index:auth(user, password, ext) | ||||
|     if not user or not password then return 1 end | ||||
|     if string.find(ext, "AUTH[^\n]+LOGIN") then | ||||
|         return self:login(user, password) | ||||
|     elseif string.find(ext, "AUTH[^\n]+PLAIN") then | ||||
|         return self:plain(user, password) | ||||
|     else | ||||
|         self.try(nil, "authentication not supported") | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- send message or throw an exception | ||||
| function metat.__index:send(mailt) | ||||
|     self:mail(mailt.from) | ||||
|     if base.type(mailt.rcpt) == "table" then | ||||
|         for i,v in base.ipairs(mailt.rcpt) do | ||||
|             self:rcpt(v) | ||||
|         end | ||||
|     else | ||||
|         self:rcpt(mailt.rcpt) | ||||
|     end | ||||
|     self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) | ||||
| end | ||||
|  | ||||
| function open(server, port, create) | ||||
|     local tp = socket.try(tp.connect(server or SERVER, port or PORT, | ||||
|         TIMEOUT, create)) | ||||
|     local s = base.setmetatable({tp = tp}, metat) | ||||
|     -- make sure tp is closed if we get an exception | ||||
|     s.try = socket.newtry(function() | ||||
|         s:close() | ||||
|     end) | ||||
|     return s | ||||
| end | ||||
|  | ||||
| -- convert headers to lowercase | ||||
| local function lower_headers(headers) | ||||
|     local lower = {} | ||||
|     for i,v in base.pairs(headers or lower) do | ||||
|         lower[string.lower(i)] = v | ||||
|     end | ||||
|     return lower | ||||
| end | ||||
|  | ||||
| --------------------------------------------------------------------------- | ||||
| -- Multipart message source | ||||
| ----------------------------------------------------------------------------- | ||||
| -- returns a hopefully unique mime boundary | ||||
| local seqno = 0 | ||||
| local function newboundary() | ||||
|     seqno = seqno + 1 | ||||
|     return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), | ||||
|         math.random(0, 99999), seqno) | ||||
| end | ||||
|  | ||||
| -- send_message forward declaration | ||||
| local send_message | ||||
|  | ||||
| -- yield the headers all at once, it's faster | ||||
| local function send_headers(headers) | ||||
|     local h = "\r\n" | ||||
|     for i,v in base.pairs(headers) do | ||||
|         h = i .. ': ' .. v .. "\r\n" .. h | ||||
|     end | ||||
|     coroutine.yield(h) | ||||
| end | ||||
|  | ||||
| -- yield multipart message body from a multipart message table | ||||
| local function send_multipart(mesgt) | ||||
|     -- make sure we have our boundary and send headers | ||||
|     local bd = newboundary() | ||||
|     local headers = lower_headers(mesgt.headers or {}) | ||||
|     headers['content-type'] = headers['content-type'] or 'multipart/mixed' | ||||
|     headers['content-type'] = headers['content-type'] .. | ||||
|         '; boundary="' ..  bd .. '"' | ||||
|     send_headers(headers) | ||||
|     -- send preamble | ||||
|     if mesgt.body.preamble then | ||||
|         coroutine.yield(mesgt.body.preamble) | ||||
|         coroutine.yield("\r\n") | ||||
|     end | ||||
|     -- send each part separated by a boundary | ||||
|     for i, m in base.ipairs(mesgt.body) do | ||||
|         coroutine.yield("\r\n--" .. bd .. "\r\n") | ||||
|         send_message(m) | ||||
|     end | ||||
|     -- send last boundary | ||||
|     coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") | ||||
|     -- send epilogue | ||||
|     if mesgt.body.epilogue then | ||||
|         coroutine.yield(mesgt.body.epilogue) | ||||
|         coroutine.yield("\r\n") | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- yield message body from a source | ||||
| local function send_source(mesgt) | ||||
|     -- make sure we have a content-type | ||||
|     local headers = lower_headers(mesgt.headers or {}) | ||||
|     headers['content-type'] = headers['content-type'] or | ||||
|         'text/plain; charset="iso-8859-1"' | ||||
|     send_headers(headers) | ||||
|     -- send body from source | ||||
|     while true do | ||||
|         local chunk, err = mesgt.body() | ||||
|         if err then coroutine.yield(nil, err) | ||||
|         elseif chunk then coroutine.yield(chunk) | ||||
|         else break end | ||||
|     end | ||||
| end | ||||
|  | ||||
| -- yield message body from a string | ||||
| local function send_string(mesgt) | ||||
|     -- make sure we have a content-type | ||||
|     local headers = lower_headers(mesgt.headers or {}) | ||||
|     headers['content-type'] = headers['content-type'] or | ||||
|         'text/plain; charset="iso-8859-1"' | ||||
|     send_headers(headers) | ||||
|     -- send body from string | ||||
|     coroutine.yield(mesgt.body) | ||||
| end | ||||
|  | ||||
| -- message source | ||||
| function send_message(mesgt) | ||||
|     if base.type(mesgt.body) == "table" then send_multipart(mesgt) | ||||
|     elseif base.type(mesgt.body) == "function" then send_source(mesgt) | ||||
|     else send_string(mesgt) end | ||||
| end | ||||
|  | ||||
| -- set defaul headers | ||||
| local function adjust_headers(mesgt) | ||||
|     local lower = lower_headers(mesgt.headers) | ||||
|     lower["date"] = lower["date"] or | ||||
|         os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) | ||||
|     lower["x-mailer"] = lower["x-mailer"] or socket._VERSION | ||||
|     -- this can't be overriden | ||||
|     lower["mime-version"] = "1.0" | ||||
|     return lower | ||||
| end | ||||
|  | ||||
| function message(mesgt) | ||||
|     mesgt.headers = adjust_headers(mesgt) | ||||
|     -- create and return message source | ||||
|     local co = coroutine.create(function() send_message(mesgt) end) | ||||
|     return function() | ||||
|         local ret, a, b = coroutine.resume(co) | ||||
|         if ret then return a, b | ||||
|         else return nil, a end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --------------------------------------------------------------------------- | ||||
| -- High level SMTP API | ||||
| ----------------------------------------------------------------------------- | ||||
| send = socket.protect(function(mailt) | ||||
|     local s = open(mailt.server, mailt.port, mailt.create) | ||||
|     local ext = s:greet(mailt.domain) | ||||
|     s:auth(mailt.user, mailt.password, ext) | ||||
|     s:send(mailt) | ||||
|     s:quit() | ||||
|     return s:close() | ||||
| end) | ||||
							
								
								
									
										123
									
								
								lualib/socket/tp.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								lualib/socket/tp.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Unified SMTP/FTP subsystem | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: tp.lua,v 1.22 2006/03/14 09:04:15 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module and import dependencies | ||||
| ----------------------------------------------------------------------------- | ||||
| local base = _G | ||||
| local string = require("string") | ||||
| local socket = require("socket") | ||||
| local ltn12 = require("ltn12") | ||||
| module("socket.tp") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Program constants | ||||
| ----------------------------------------------------------------------------- | ||||
| TIMEOUT = 60 | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Implementation | ||||
| ----------------------------------------------------------------------------- | ||||
| -- gets server reply (works for SMTP and FTP) | ||||
| local function get_reply(c) | ||||
|     local code, current, sep | ||||
|     local line, err = c:receive() | ||||
|     local reply = line | ||||
|     if err then return nil, err end | ||||
|     code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) | ||||
|     if not code then return nil, "invalid server reply" end | ||||
|     if sep == "-" then -- reply is multiline | ||||
|         repeat | ||||
|             line, err = c:receive() | ||||
|             if err then return nil, err end | ||||
|             current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) | ||||
|             reply = reply .. "\n" .. line | ||||
|         -- reply ends with same code | ||||
|         until code == current and sep == " " | ||||
|     end | ||||
|     return code, reply | ||||
| end | ||||
|  | ||||
| -- metatable for sock object | ||||
| local metat = { __index = {} } | ||||
|  | ||||
| function metat.__index:check(ok) | ||||
|     local code, reply = get_reply(self.c) | ||||
|     if not code then return nil, reply end | ||||
|     if base.type(ok) ~= "function" then | ||||
|         if base.type(ok) == "table" then | ||||
|             for i, v in base.ipairs(ok) do | ||||
|                 if string.find(code, v) then | ||||
|                     return base.tonumber(code), reply | ||||
|                 end | ||||
|             end | ||||
|             return nil, reply | ||||
|         else | ||||
|             if string.find(code, ok) then return base.tonumber(code), reply | ||||
|             else return nil, reply end | ||||
|         end | ||||
|     else return ok(base.tonumber(code), reply) end | ||||
| end | ||||
|  | ||||
| function metat.__index:command(cmd, arg) | ||||
|     if arg then | ||||
|         return self.c:send(cmd .. " " .. arg.. "\r\n") | ||||
|     else | ||||
|         return self.c:send(cmd .. "\r\n") | ||||
|     end | ||||
| end | ||||
|  | ||||
| function metat.__index:sink(snk, pat) | ||||
|     local chunk, err = c:receive(pat) | ||||
|     return snk(chunk, err) | ||||
| end | ||||
|  | ||||
| function metat.__index:send(data) | ||||
|     return self.c:send(data) | ||||
| end | ||||
|  | ||||
| function metat.__index:receive(pat) | ||||
|     return self.c:receive(pat) | ||||
| end | ||||
|  | ||||
| function metat.__index:getfd() | ||||
|     return self.c:getfd() | ||||
| end | ||||
|  | ||||
| function metat.__index:dirty() | ||||
|     return self.c:dirty() | ||||
| end | ||||
|  | ||||
| function metat.__index:getcontrol() | ||||
|     return self.c | ||||
| end | ||||
|  | ||||
| function metat.__index:source(source, step) | ||||
|     local sink = socket.sink("keep-open", self.c) | ||||
|     local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) | ||||
|     return ret, err | ||||
| end | ||||
|  | ||||
| -- closes the underlying c | ||||
| function metat.__index:close() | ||||
|     self.c:close() | ||||
| 	return 1 | ||||
| end | ||||
|  | ||||
| -- connect with server and return c object | ||||
| function connect(host, port, timeout, create) | ||||
|     local c, e = (create or socket.tcp)() | ||||
|     if not c then return nil, e end | ||||
|     c:settimeout(timeout or TIMEOUT) | ||||
|     local r, e = c:connect(host, port) | ||||
|     if not r then | ||||
|         c:close() | ||||
|         return nil, e | ||||
|     end | ||||
|     return base.setmetatable({c = c}, metat) | ||||
| end | ||||
|  | ||||
							
								
								
									
										297
									
								
								lualib/socket/url.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								lualib/socket/url.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,297 @@ | ||||
| ----------------------------------------------------------------------------- | ||||
| -- URI parsing, composition and relative URL resolution | ||||
| -- LuaSocket toolkit. | ||||
| -- Author: Diego Nehab | ||||
| -- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ | ||||
| ----------------------------------------------------------------------------- | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Declare module | ||||
| ----------------------------------------------------------------------------- | ||||
| local string = require("string") | ||||
| local base = _G | ||||
| local table = require("table") | ||||
| module("socket.url") | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Module version | ||||
| ----------------------------------------------------------------------------- | ||||
| _VERSION = "URL 1.0.1" | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Encodes a string into its escaped hexadecimal representation | ||||
| -- Input | ||||
| --   s: binary string to be encoded | ||||
| -- Returns | ||||
| --   escaped representation of string binary | ||||
| ----------------------------------------------------------------------------- | ||||
| function escape(s) | ||||
|     return string.gsub(s, "([^A-Za-z0-9_])", function(c) | ||||
|         return string.format("%%%02x", string.byte(c)) | ||||
|     end) | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Protects a path segment, to prevent it from interfering with the | ||||
| -- url parsing. | ||||
| -- Input | ||||
| --   s: binary string to be encoded | ||||
| -- Returns | ||||
| --   escaped representation of string binary | ||||
| ----------------------------------------------------------------------------- | ||||
| local function make_set(t) | ||||
| 	local s = {} | ||||
| 	for i,v in base.ipairs(t) do | ||||
| 		s[t[i]] = 1 | ||||
| 	end | ||||
| 	return s | ||||
| end | ||||
|  | ||||
| -- these are allowed withing a path segment, along with alphanum | ||||
| -- other characters must be escaped | ||||
| local segment_set = make_set { | ||||
|     "-", "_", ".", "!", "~", "*", "'", "(", | ||||
| 	")", ":", "@", "&", "=", "+", "$", ",", | ||||
| } | ||||
|  | ||||
| local function protect_segment(s) | ||||
| 	return string.gsub(s, "([^A-Za-z0-9_])", function (c) | ||||
| 		if segment_set[c] then return c | ||||
| 		else return string.format("%%%02x", string.byte(c)) end | ||||
| 	end) | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Encodes a string into its escaped hexadecimal representation | ||||
| -- Input | ||||
| --   s: binary string to be encoded | ||||
| -- Returns | ||||
| --   escaped representation of string binary | ||||
| ----------------------------------------------------------------------------- | ||||
| function unescape(s) | ||||
|     return string.gsub(s, "%%(%x%x)", function(hex) | ||||
|         return string.char(base.tonumber(hex, 16)) | ||||
|     end) | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Builds a path from a base path and a relative path | ||||
| -- Input | ||||
| --   base_path | ||||
| --   relative_path | ||||
| -- Returns | ||||
| --   corresponding absolute path | ||||
| ----------------------------------------------------------------------------- | ||||
| local function absolute_path(base_path, relative_path) | ||||
|     if string.sub(relative_path, 1, 1) == "/" then return relative_path end | ||||
|     local path = string.gsub(base_path, "[^/]*$", "") | ||||
|     path = path .. relative_path | ||||
|     path = string.gsub(path, "([^/]*%./)", function (s) | ||||
|         if s ~= "./" then return s else return "" end | ||||
|     end) | ||||
|     path = string.gsub(path, "/%.$", "/") | ||||
|     local reduced | ||||
|     while reduced ~= path do | ||||
|         reduced = path | ||||
|         path = string.gsub(reduced, "([^/]*/%.%./)", function (s) | ||||
|             if s ~= "../../" then return "" else return s end | ||||
|         end) | ||||
|     end | ||||
|     path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) | ||||
|         if s ~= "../.." then return "" else return s end | ||||
|     end) | ||||
|     return path | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Parses a url and returns a table with all its parts according to RFC 2396 | ||||
| -- The following grammar describes the names given to the URL parts | ||||
| -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment> | ||||
| -- <authority> ::= <userinfo>@<host>:<port> | ||||
| -- <userinfo> ::= <user>[:<password>] | ||||
| -- <path> :: = {<segment>/}<segment> | ||||
| -- Input | ||||
| --   url: uniform resource locator of request | ||||
| --   default: table with default values for each field | ||||
| -- Returns | ||||
| --   table with the following fields, where RFC naming conventions have | ||||
| --   been preserved: | ||||
| --     scheme, authority, userinfo, user, password, host, port, | ||||
| --     path, params, query, fragment | ||||
| -- Obs: | ||||
| --   the leading '/' in {/<path>} is considered part of <path> | ||||
| ----------------------------------------------------------------------------- | ||||
| function parse(url, default) | ||||
|     -- initialize default parameters | ||||
|     local parsed = {} | ||||
|     for i,v in base.pairs(default or parsed) do parsed[i] = v end | ||||
|     -- empty url is parsed to nil | ||||
|     if not url or url == "" then return nil, "invalid url" end | ||||
|     -- remove whitespace | ||||
|     -- url = string.gsub(url, "%s", "") | ||||
|     -- get fragment | ||||
|     url = string.gsub(url, "#(.*)$", function(f) | ||||
|         parsed.fragment = f | ||||
|         return "" | ||||
|     end) | ||||
|     -- get scheme | ||||
|     url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", | ||||
|         function(s) parsed.scheme = s; return "" end) | ||||
|     -- get authority | ||||
|     url = string.gsub(url, "^//([^/]*)", function(n) | ||||
|         parsed.authority = n | ||||
|         return "" | ||||
|     end) | ||||
|     -- get query stringing | ||||
|     url = string.gsub(url, "%?(.*)", function(q) | ||||
|         parsed.query = q | ||||
|         return "" | ||||
|     end) | ||||
|     -- get params | ||||
|     url = string.gsub(url, "%;(.*)", function(p) | ||||
|         parsed.params = p | ||||
|         return "" | ||||
|     end) | ||||
|     -- path is whatever was left | ||||
|     if url ~= "" then parsed.path = url end | ||||
|     local authority = parsed.authority | ||||
|     if not authority then return parsed end | ||||
|     authority = string.gsub(authority,"^([^@]*)@", | ||||
|         function(u) parsed.userinfo = u; return "" end) | ||||
|     authority = string.gsub(authority, ":([^:]*)$", | ||||
|         function(p) parsed.port = p; return "" end) | ||||
|     if authority ~= "" then parsed.host = authority end | ||||
|     local userinfo = parsed.userinfo | ||||
|     if not userinfo then return parsed end | ||||
|     userinfo = string.gsub(userinfo, ":([^:]*)$", | ||||
|         function(p) parsed.password = p; return "" end) | ||||
|     parsed.user = userinfo | ||||
|     return parsed | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Rebuilds a parsed URL from its components. | ||||
| -- Components are protected if any reserved or unallowed characters are found | ||||
| -- Input | ||||
| --   parsed: parsed URL, as returned by parse | ||||
| -- Returns | ||||
| --   a stringing with the corresponding URL | ||||
| ----------------------------------------------------------------------------- | ||||
| function build(parsed) | ||||
|     local ppath = parse_path(parsed.path or "") | ||||
|     local url = build_path(ppath) | ||||
|     if parsed.params then url = url .. ";" .. parsed.params end | ||||
|     if parsed.query then url = url .. "?" .. parsed.query end | ||||
| 	local authority = parsed.authority | ||||
| 	if parsed.host then | ||||
| 		authority = parsed.host | ||||
| 		if parsed.port then authority = authority .. ":" .. parsed.port end | ||||
| 		local userinfo = parsed.userinfo | ||||
| 		if parsed.user then | ||||
| 			userinfo = parsed.user | ||||
| 			if parsed.password then | ||||
| 				userinfo = userinfo .. ":" .. parsed.password | ||||
| 			end | ||||
| 		end | ||||
| 		if userinfo then authority = userinfo .. "@" .. authority end | ||||
| 	end | ||||
|     if authority then url = "//" .. authority .. url end | ||||
|     if parsed.scheme then url = parsed.scheme .. ":" .. url end | ||||
|     if parsed.fragment then url = url .. "#" .. parsed.fragment end | ||||
|     -- url = string.gsub(url, "%s", "") | ||||
|     return url | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Builds a absolute URL from a base and a relative URL according to RFC 2396 | ||||
| -- Input | ||||
| --   base_url | ||||
| --   relative_url | ||||
| -- Returns | ||||
| --   corresponding absolute url | ||||
| ----------------------------------------------------------------------------- | ||||
| function absolute(base_url, relative_url) | ||||
|     if base.type(base_url) == "table" then | ||||
|         base_parsed = base_url | ||||
|         base_url = build(base_parsed) | ||||
|     else | ||||
|         base_parsed = parse(base_url) | ||||
|     end | ||||
|     local relative_parsed = parse(relative_url) | ||||
|     if not base_parsed then return relative_url | ||||
|     elseif not relative_parsed then return base_url | ||||
|     elseif relative_parsed.scheme then return relative_url | ||||
|     else | ||||
|         relative_parsed.scheme = base_parsed.scheme | ||||
|         if not relative_parsed.authority then | ||||
|             relative_parsed.authority = base_parsed.authority | ||||
|             if not relative_parsed.path then | ||||
|                 relative_parsed.path = base_parsed.path | ||||
|                 if not relative_parsed.params then | ||||
|                     relative_parsed.params = base_parsed.params | ||||
|                     if not relative_parsed.query then | ||||
|                         relative_parsed.query = base_parsed.query | ||||
|                     end | ||||
|                 end | ||||
|             else     | ||||
|                 relative_parsed.path = absolute_path(base_parsed.path or "", | ||||
|                     relative_parsed.path) | ||||
|             end | ||||
|         end | ||||
|         return build(relative_parsed) | ||||
|     end | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Breaks a path into its segments, unescaping the segments | ||||
| -- Input | ||||
| --   path | ||||
| -- Returns | ||||
| --   segment: a table with one entry per segment | ||||
| ----------------------------------------------------------------------------- | ||||
| function parse_path(path) | ||||
| 	local parsed = {} | ||||
| 	path = path or "" | ||||
| 	--path = string.gsub(path, "%s", "") | ||||
| 	string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) | ||||
| 	for i = 1, table.getn(parsed) do | ||||
| 		parsed[i] = unescape(parsed[i]) | ||||
| 	end | ||||
| 	if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end | ||||
| 	if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end | ||||
| 	return parsed | ||||
| end | ||||
|  | ||||
| ----------------------------------------------------------------------------- | ||||
| -- Builds a path component from its segments, escaping protected characters. | ||||
| -- Input | ||||
| --   parsed: path segments | ||||
| --   unsafe: if true, segments are not protected before path is built | ||||
| -- Returns | ||||
| --   path: corresponding path stringing | ||||
| ----------------------------------------------------------------------------- | ||||
| function build_path(parsed, unsafe) | ||||
| 	local path = "" | ||||
| 	local n = table.getn(parsed) | ||||
| 	if unsafe then | ||||
| 		for i = 1, n-1 do | ||||
| 			path = path .. parsed[i] | ||||
| 			path = path .. "/" | ||||
| 		end | ||||
| 		if n > 0 then | ||||
| 			path = path .. parsed[n] | ||||
| 			if parsed.is_directory then path = path .. "/" end | ||||
| 		end | ||||
| 	else | ||||
| 		for i = 1, n-1 do | ||||
| 			path = path .. protect_segment(parsed[i]) | ||||
| 			path = path .. "/" | ||||
| 		end | ||||
| 		if n > 0 then | ||||
| 			path = path .. protect_segment(parsed[n]) | ||||
| 			if parsed.is_directory then path = path .. "/" end | ||||
| 		end | ||||
| 	end | ||||
| 	if parsed.is_absolute then path = "/" .. path end | ||||
| 	return path | ||||
| end | ||||
		Reference in New Issue
	
	Block a user
	 Redo
					Redo