initial
This commit is contained in:
		
							
								
								
									
										
											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