Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -----------------------------------------------------------------------------
- -- URI parsing, composition and relative URL resolution
- -- LuaSocket toolkit.
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- Declare module
- -----------------------------------------------------------------------------
- local string = require("string")
- local base = _G
- local table = require("table")
- local socket = require("socket")
- socket.url = {}
- local _M = socket.url
- -----------------------------------------------------------------------------
- -- Module version
- -----------------------------------------------------------------------------
- _M._VERSION = "URL 1.0.3"
- -----------------------------------------------------------------------------
- -- Encodes a string into its escaped hexadecimal representation
- -- Input
- -- s: binary string to be encoded
- -- Returns
- -- escaped representation of string binary
- -----------------------------------------------------------------------------
- function _M.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 within 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
- -----------------------------------------------------------------------------
- -- Unencodes a escaped hexadecimal string into its binary representation
- -- Input
- -- s: escaped hexadecimal string to be unencoded
- -- Returns
- -- unescaped binary representation of escaped hexadecimal binary
- -----------------------------------------------------------------------------
- function _M.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 _M.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 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 fragment
- url = string.gsub(url, "#(.*)$", function(f)
- parsed.fragment = f
- return ""
- end)
- -- get query string
- 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
- -- IPv6?
- parsed.host = string.match(authority, "^%[(.+)%]$") or 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 _M.build(parsed)
- --local ppath = _M.parse_path(parsed.path or "")
- --local url = _M.build_path(ppath)
- local url = parsed.path or ""
- 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 string.find(authority, ":") then -- IPv6?
- authority = "[" .. authority .. "]"
- end
- if parsed.port then authority = authority .. ":" .. base.tostring(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 _M.absolute(base_url, relative_url)
- local base_parsed
- if base.type(base_url) == "table" then
- base_parsed = base_url
- base_url = _M.build(base_parsed)
- else
- base_parsed = _M.parse(base_url)
- end
- local relative_parsed = _M.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 _M.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 _M.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, #parsed do
- parsed[i] = _M.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 _M.build_path(parsed, unsafe)
- local path = ""
- local n = #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
- return _M
- -----------------------------------------------------------------------------
- -- Unified SMTP/FTP subsystem
- -- LuaSocket toolkit.
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- Declare module and import dependencies
- -----------------------------------------------------------------------------
- local base = _G
- local string = require("string")
- local socket = require("socket")
- local ltn12 = require("ltn12")
- socket.tp = {}
- local _M = socket.tp
- -----------------------------------------------------------------------------
- -- Program constants
- -----------------------------------------------------------------------------
- _M.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:getpeername()
- return self.c:getpeername()
- end
- function metat.__index:getsockname()
- return self.c:getpeername()
- end
- 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)
- cmd = string.upper(cmd)
- 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 = self.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 _M.connect(host, port, timeout, create)
- local c, e = (create or socket.tcp)()
- if not c then return nil, e end
- c:settimeout(timeout or _M.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
- return _M
- -----------------------------------------------------------------------------
- -- LuaSocket helper module
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- Declare module and import dependencies
- -----------------------------------------------------------------------------
- local base = _G
- local string = require("string")
- local math = require("math")
- local socket = require("socket.core")
- local _M = socket
- -----------------------------------------------------------------------------
- -- Exported auxiliar functions
- -----------------------------------------------------------------------------
- function _M.connect4(address, port, laddress, lport)
- return socket.connect(address, port, laddress, lport, "inet")
- end
- function _M.connect6(address, port, laddress, lport)
- return socket.connect(address, port, laddress, lport, "inet6")
- end
- function _M.bind(host, port, backlog)
- if host == "*" then host = "0.0.0.0" end
- local addrinfo, err = socket.dns.getaddrinfo(host);
- if not addrinfo then return nil, err end
- local sock, res
- err = "no info on address"
- for i, alt in base.ipairs(addrinfo) do
- if alt.family == "inet" then
- sock, err = socket.tcp4()
- else
- sock, err = socket.tcp6()
- end
- if not sock then return nil, err end
- sock:setoption("reuseaddr", true)
- res, err = sock:bind(alt.addr, port)
- if not res then
- sock:close()
- else
- res, err = sock:listen(backlog)
- if not res then
- sock:close()
- else
- return sock
- end
- end
- end
- return nil, err
- end
- _M.try = _M.newtry()
- function _M.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
- local sourcet, sinkt = {}, {}
- _M.sourcet = sourcet
- _M.sinkt = sinkt
- _M.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"]
- _M.sink = _M.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"]
- _M.source = _M.choose(sourcet)
- return _M
- -----------------------------------------------------------------------------
- -- MIME support for the Lua language.
- -- Author: Diego Nehab
- -- Conforming to RFCs 2045-2049
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- 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")
- local _M = mime
- -- encode, decode and wrap algorithm tables
- local encodet, decodet, wrapt = {},{},{}
- _M.encodet = encodet
- _M.decodet = decodet
- _M.wrapt = 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(_M.b64, "")
- end
- encodet['quoted-printable'] = function(mode)
- return ltn12.filter.cycle(_M.qp, "",
- (mode == "binary") and "=0D=0A" or "\r\n")
- end
- -- define the decoding filters
- decodet['base64'] = function()
- return ltn12.filter.cycle(_M.unb64, "")
- end
- decodet['quoted-printable'] = function()
- return ltn12.filter.cycle(_M.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(_M.wrp, length, length)
- end
- wrapt['base64'] = wrapt['text']
- wrapt['default'] = wrapt['text']
- wrapt['quoted-printable'] = function()
- return ltn12.filter.cycle(_M.qpwrp, 76, 76)
- end
- -- function that choose the encoding, decoding or wrap algorithm
- _M.encode = choose(encodet)
- _M.decode = choose(decodet)
- _M.wrap = choose(wrapt)
- -- define the end-of-line normalization filter
- function _M.normalize(marker)
- return ltn12.filter.cycle(_M.eol, 0, marker)
- end
- -- high level stuffing filter
- function _M.stuff()
- return ltn12.filter.cycle(_M.dot, 2)
- end
- return _M local _M = {}
- if module then
- mbox = _M
- end
- function _M.split_message(message_s)
- local message = {}
- message_s = string.gsub(message_s, "\r\n", "\n")
- string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end)
- string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end)
- if not message.body then
- string.gsub(message_s, "^\n(.*)", function (b) message.body = b end)
- end
- if not message.headers and not message.body then
- message.headers = message_s
- end
- return message.headers or "", message.body or ""
- end
- function _M.split_headers(headers_s)
- local headers = {}
- headers_s = string.gsub(headers_s, "\r\n", "\n")
- headers_s = string.gsub(headers_s, "\n[ ]+", " ")
- string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end)
- return headers
- end
- function _M.parse_header(header_s)
- header_s = string.gsub(header_s, "\n[ ]+", " ")
- header_s = string.gsub(header_s, "\n+", "")
- local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)")
- return name, value
- end
- function _M.parse_headers(headers_s)
- local headers_t = _M.split_headers(headers_s)
- local headers = {}
- for i = 1, #headers_t do
- local name, value = _M.parse_header(headers_t[i])
- if name then
- name = string.lower(name)
- if headers[name] then
- headers[name] = headers[name] .. ", " .. value
- else headers[name] = value end
- end
- end
- return headers
- end
- function _M.parse_from(from)
- local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>")
- if not address then
- _, __, address = string.find(from, "%s*(.+)%s*")
- end
- name = name or ""
- address = address or ""
- if name == "" then name = address end
- name = string.gsub(name, '"', "")
- return name, address
- end
- function _M.split_mbox(mbox_s)
- local mbox = {}
- mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n"
- local nj, i, j = 1, 1, 1
- while 1 do
- i, nj = string.find(mbox_s, "\n\nFrom .-\n", j)
- if not i then break end
- local message = string.sub(mbox_s, j, i-1)
- table.insert(mbox, message)
- j = nj+1
- end
- return mbox
- end
- function _M.parse(mbox_s)
- local mbox = _M.split_mbox(mbox_s)
- for i = 1, #mbox do
- mbox[i] = _M.parse_message(mbox[i])
- end
- return mbox
- end
- function _M.parse_message(message_s)
- local message = {}
- message.headers, message.body = _M.split_message(message_s)
- message.headers = _M.parse_headers(message.headers)
- return message
- end
- return _M
- -----------------------------------------------------------------------------
- -- LTN12 - Filters, sources, sinks and pumps.
- -- LuaSocket toolkit.
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- Declare module
- -----------------------------------------------------------------------------
- local string = require("string")
- local table = require("table")
- local unpack = unpack or table.unpack
- local base = _G
- local _M = {}
- if module then -- heuristic for exporting a global package table
- ltn12 = _M
- end
- local filter,source,sink,pump = {},{},{},{}
- _M.filter = filter
- _M.source = source
- _M.sink = sink
- _M.pump = pump
- local unpack = unpack or table.unpack
- local select = base.select
- -- 2048 seems to be better in windows...
- _M.BLOCKSIZE = 2048
- _M._VERSION = "LTN12 1.0.3"
- -----------------------------------------------------------------------------
- -- 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 arg = {...}
- local n = base.select('#',...)
- 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(_M.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+_M.BLOCKSIZE-1)
- i = i + _M.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
- -- chains a source with one or several filter(s)
- function source.chain(src, f, ...)
- if ... then f=filter.chain(f, ...) end
- 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 arg = {...}
- 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 one or several filter(s)
- function sink.chain(f, snk, ...)
- if ... then
- local args = { f, snk, ... }
- snk = table.remove(args, #args)
- f = filter.chain(unpack(args))
- end
- 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
- return _M
- -----------------------------------------------------------------------------
- -- HTTP/1.1 client support for the Lua language.
- -- LuaSocket toolkit.
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- -- 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 headers = require("socket.headers")
- local base = _G
- local table = require("table")
- socket.http = {}
- local _M = socket.http
- -----------------------------------------------------------------------------
- -- Program constants
- -----------------------------------------------------------------------------
- -- connection timeout in seconds
- _M.TIMEOUT = 60
- -- user agent field sent in request
- _M.USERAGENT = socket._VERSION
- -- supported schemes
- local SCHEMES = { ["http"] = true }
- -- default port for document retrieval
- local PORT = 80
- -----------------------------------------------------------------------------
- -- 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 _M.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(_M.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(tosend)
- local canonic = headers.canonic
- local h = "\r\n"
- for f, v in base.pairs(tosend) do
- h = (canonic[f] or f) .. ": " .. 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 _M.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 _M.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 host = string.gsub(reqt.authority, "^.-@", "")
- local lower = {
- ["user-agent"] = _M.USERAGENT,
- ["host"] = 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 .. ":" ..
- url.unescape(reqt.password)))
- end
- -- if we have proxy authentication information, pass it along
- local proxy = reqt.proxy or _M.PROXY
- if proxy then
- proxy = url.parse(proxy)
- if proxy.user and proxy.password then
- lower["proxy-authorization"] =
- "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password))
- end
- 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 = PORT end
- if not (nreqt.host and nreqt.host ~= "") then
- socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'")
- end
- -- compute uri if user hasn't overriden
- nreqt.uri = reqt.uri or adjusturi(nreqt)
- -- adjust headers in request
- nreqt.headers = adjustheaders(nreqt)
- -- ajust host and port if there is a proxy
- nreqt.host, nreqt.port = adjustproxy(nreqt)
- return nreqt
- end
- local function shouldredirect(reqt, code, headers)
- local location = headers.location
- if not location then return false end
- location = string.gsub(location, "%s", "")
- if location == "" then return false end
- local scheme = string.match(location, "^([%w][%w%+%-%.]*)%:")
- if scheme and not SCHEMES[scheme] then return false end
- return (reqt.redirect ~= false) and
- (code == 301 or code == 302 or code == 303 or code == 307) 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
- --[[local]] 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
- --[[local]] 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 = _M.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
- -- turns an url and a body into a generic request
- local function genericform(u, b)
- local t = {}
- local reqt = {
- url = u,
- sink = ltn12.sink.table(t),
- target = 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
- return reqt
- end
- _M.genericform = genericform
- local function srequest(u, b)
- local reqt = genericform(u, b)
- local _, code, headers, status = trequest(reqt)
- return table.concat(reqt.target), code, headers, status
- end
- _M.request = socket.protect(function(reqt, body)
- if base.type(reqt) == "string" then return srequest(reqt, body)
- else return trequest(reqt) end
- end)
- return _M
- -----------------------------------------------------------------------------
- -- Canonic header field capitalization
- -- LuaSocket toolkit.
- -- Author: Diego Nehab
- -----------------------------------------------------------------------------
- local socket = require("socket")
- socket.headers = {}
- local _M = socket.headers
- _M.canonic = {
- ["accept"] = "Accept",
- ["accept-charset"] = "Accept-Charset",
- ["accept-encoding"] = "Accept-Encoding",
- ["accept-language"] = "Accept-Language",
- ["accept-ranges"] = "Accept-Ranges",
- ["action"] = "Action",
- ["alternate-recipient"] = "Alternate-Recipient",
- ["age"] = "Age",
- ["allow"] = "Allow",
- ["arrival-date"] = "Arrival-Date",
- ["authorization"] = "Authorization",
- ["bcc"] = "Bcc",
- ["cache-control"] = "Cache-Control",
- ["cc"] = "Cc",
- ["comments"] = "Comments",
- ["connection"] = "Connection",
- ["content-description"] = "Content-Description",
- ["content-disposition"] = "Content-Disposition",
- ["content-encoding"] = "Content-Encoding",
- ["content-id"] = "Content-ID",
- ["content-language"] = "Content-Language",
- ["content-length"] = "Content-Length",
- ["content-location"] = "Content-Location",
- ["content-md5"] = "Content-MD5",
- ["content-range"] = "Content-Range",
- ["content-transfer-encoding"] = "Content-Transfer-Encoding",
- ["content-type"] = "Content-Type",
- ["cookie"] = "Cookie",
- ["date"] = "Date",
- ["diagnostic-code"] = "Diagnostic-Code",
- ["dsn-gateway"] = "DSN-Gateway",
- ["etag"] = "ETag",
- ["expect"] = "Expect",
- ["expires"] = "Expires",
- ["final-log-id"] = "Final-Log-ID",
- ["final-recipient"] = "Final-Recipient",
- ["from"] = "From",
- ["host"] = "Host",
- ["if-match"] = "If-Match",
- ["if-modified-since"] = "If-Modified-Since",
- ["if-none-match"] = "If-None-Match",
- ["if-range"] = "If-Range",
- ["if-unmodified-since"] = "If-Unmodified-Since",
- ["in-reply-to"] = "In-Reply-To",
- ["keywords"] = "Keywords",
- ["last-attempt-date"] = "Last-Attempt-Date",
- ["last-modified"] = "Last-Modified",
- ["location"] = "Location",
- ["max-forwards"] = "Max-Forwards",
- ["message-id"] = "Message-ID",
- ["mime-version"] = "MIME-Version",
- ["original-envelope-id"] = "Original-Envelope-ID",
- ["original-recipient"] = "Original-Recipient",
- ["pragma"] = "Pragma",
- ["proxy-authenticate"] = "Proxy-Authenticate",
- ["proxy-authorization"] = "Proxy-Authorization",
- ["range"] = "Range",
- ["received"] = "Received",
- ["received-from-mta"] = "Received-From-MTA",
- ["references"] = "References",
- ["referer"] = "Referer",
- ["remote-mta"] = "Remote-MTA",
- ["reply-to"] = "Reply-To",
- ["reporting-mta"] = "Reporting-MTA",
- ["resent-bcc"] = "Resent-Bcc",
- ["resent-cc"] = "Resent-Cc",
- ["resent-date"] = "Resent-Date",
- ["resent-from"] = "Resent-From",
- ["resent-message-id"] = "Resent-Message-ID",
- ["resent-reply-to"] = "Resent-Reply-To",
- ["resent-sender"] = "Resent-Sender",
- ["resent-to"] = "Resent-To",
- ["retry-after"] = "Retry-After",
- ["return-path"] = "Return-Path",
- ["sender"] = "Sender",
- ["server"] = "Server",
- ["smtp-remote-recipient"] = "SMTP-Remote-Recipient",
- ["status"] = "Status",
- ["subject"] = "Subject",
- ["te"] = "TE",
- ["to"] = "To",
- ["trailer"] = "Trailer",
- ["transfer-encoding"] = "Transfer-Encoding",
- ["upgrade"] = "Upgrade",
- ["user-agent"] = "User-Agent",
- ["vary"] = "Vary",
- ["via"] = "Via",
- ["warning"] = "Warning",
- ["will-retry-until"] = "Will-Retry-Until",
- ["www-authenticate"] = "WWW-Authenticate",
- ["x-mailer"] = "X-Mailer",
- }
- return
Add Comment
Please, Sign In to add comment