Commit 5507e9772c943e34ba4abac4e6afae978155824e

Authored by dg
1 parent 9ce0336b

import luasocket

remdebug (but it is probably too slow to use)
better stacktrace (elcugo)


git-svn-id: http://svn.net-core.org/repos/t-engine4@27 51575b47-30f0-44d4-a5cc-537603b46e54

Too many changes to show.

To preserve performance only 31 of 31+ files are displayed.

  1 +--[[ Allows for remote debugging, but it makes things *SLOW*
  2 +require"remdebug.engine"
  3 +remdebug.engine.start()
  4 +]]
  5 +
1 6 -- load some utility functions
2 7 dofile("/engine/utils.lua")
3 8
... ...
  1 +-----------------------------------------------------------------------------
  2 +-- LTN12 - Filters, sources, sinks and pumps.
  3 +-- LuaSocket toolkit.
  4 +-- Author: Diego Nehab
  5 +-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $
  6 +-----------------------------------------------------------------------------
  7 +
  8 +-----------------------------------------------------------------------------
  9 +-- Declare module
  10 +-----------------------------------------------------------------------------
  11 +local string = require("string")
  12 +local table = require("table")
  13 +local base = _G
  14 +module("ltn12")
  15 +
  16 +filter = {}
  17 +source = {}
  18 +sink = {}
  19 +pump = {}
  20 +
  21 +-- 2048 seems to be better in windows...
  22 +BLOCKSIZE = 2048
  23 +_VERSION = "LTN12 1.0.1"
  24 +
  25 +-----------------------------------------------------------------------------
  26 +-- Filter stuff
  27 +-----------------------------------------------------------------------------
  28 +-- returns a high level filter that cycles a low-level filter
  29 +function filter.cycle(low, ctx, extra)
  30 + base.assert(low)
  31 + return function(chunk)
  32 + local ret
  33 + ret, ctx = low(ctx, chunk, extra)
  34 + return ret
  35 + end
  36 +end
  37 +
  38 +-- chains a bunch of filters together
  39 +-- (thanks to Wim Couwenberg)
  40 +function filter.chain(...)
  41 + local n = table.getn(arg)
  42 + local top, index = 1, 1
  43 + local retry = ""
  44 + return function(chunk)
  45 + retry = chunk and retry
  46 + while true do
  47 + if index == top then
  48 + chunk = arg[index](chunk)
  49 + if chunk == "" or top == n then return chunk
  50 + elseif chunk then index = index + 1
  51 + else
  52 + top = top+1
  53 + index = top
  54 + end
  55 + else
  56 + chunk = arg[index](chunk or "")
  57 + if chunk == "" then
  58 + index = index - 1
  59 + chunk = retry
  60 + elseif chunk then
  61 + if index == n then return chunk
  62 + else index = index + 1 end
  63 + else base.error("filter returned inappropriate nil") end
  64 + end
  65 + end
  66 + end
  67 +end
  68 +
  69 +-----------------------------------------------------------------------------
  70 +-- Source stuff
  71 +-----------------------------------------------------------------------------
  72 +-- create an empty source
  73 +local function empty()
  74 + return nil
  75 +end
  76 +
  77 +function source.empty()
  78 + return empty
  79 +end
  80 +
  81 +-- returns a source that just outputs an error
  82 +function source.error(err)
  83 + return function()
  84 + return nil, err
  85 + end
  86 +end
  87 +
  88 +-- creates a file source
  89 +function source.file(handle, io_err)
  90 + if handle then
  91 + return function()
  92 + local chunk = handle:read(BLOCKSIZE)
  93 + if not chunk then handle:close() end
  94 + return chunk
  95 + end
  96 + else return source.error(io_err or "unable to open file") end
  97 +end
  98 +
  99 +-- turns a fancy source into a simple source
  100 +function source.simplify(src)
  101 + base.assert(src)
  102 + return function()
  103 + local chunk, err_or_new = src()
  104 + src = err_or_new or src
  105 + if not chunk then return nil, err_or_new
  106 + else return chunk end
  107 + end
  108 +end
  109 +
  110 +-- creates string source
  111 +function source.string(s)
  112 + if s then
  113 + local i = 1
  114 + return function()
  115 + local chunk = string.sub(s, i, i+BLOCKSIZE-1)
  116 + i = i + BLOCKSIZE
  117 + if chunk ~= "" then return chunk
  118 + else return nil end
  119 + end
  120 + else return source.empty() end
  121 +end
  122 +
  123 +-- creates rewindable source
  124 +function source.rewind(src)
  125 + base.assert(src)
  126 + local t = {}
  127 + return function(chunk)
  128 + if not chunk then
  129 + chunk = table.remove(t)
  130 + if not chunk then return src()
  131 + else return chunk end
  132 + else
  133 + table.insert(t, chunk)
  134 + end
  135 + end
  136 +end
  137 +
  138 +function source.chain(src, f)
  139 + base.assert(src and f)
  140 + local last_in, last_out = "", ""
  141 + local state = "feeding"
  142 + local err
  143 + return function()
  144 + if not last_out then
  145 + base.error('source is empty!', 2)
  146 + end
  147 + while true do
  148 + if state == "feeding" then
  149 + last_in, err = src()
  150 + if err then return nil, err end
  151 + last_out = f(last_in)
  152 + if not last_out then
  153 + if last_in then
  154 + base.error('filter returned inappropriate nil')
  155 + else
  156 + return nil
  157 + end
  158 + elseif last_out ~= "" then
  159 + state = "eating"
  160 + if last_in then last_in = "" end
  161 + return last_out
  162 + end
  163 + else
  164 + last_out = f(last_in)
  165 + if last_out == "" then
  166 + if last_in == "" then
  167 + state = "feeding"
  168 + else
  169 + base.error('filter returned ""')
  170 + end
  171 + elseif not last_out then
  172 + if last_in then
  173 + base.error('filter returned inappropriate nil')
  174 + else
  175 + return nil
  176 + end
  177 + else
  178 + return last_out
  179 + end
  180 + end
  181 + end
  182 + end
  183 +end
  184 +
  185 +-- creates a source that produces contents of several sources, one after the
  186 +-- other, as if they were concatenated
  187 +-- (thanks to Wim Couwenberg)
  188 +function source.cat(...)
  189 + local src = table.remove(arg, 1)
  190 + return function()
  191 + while src do
  192 + local chunk, err = src()
  193 + if chunk then return chunk end
  194 + if err then return nil, err end
  195 + src = table.remove(arg, 1)
  196 + end
  197 + end
  198 +end
  199 +
  200 +-----------------------------------------------------------------------------
  201 +-- Sink stuff
  202 +-----------------------------------------------------------------------------
  203 +-- creates a sink that stores into a table
  204 +function sink.table(t)
  205 + t = t or {}
  206 + local f = function(chunk, err)
  207 + if chunk then table.insert(t, chunk) end
  208 + return 1
  209 + end
  210 + return f, t
  211 +end
  212 +
  213 +-- turns a fancy sink into a simple sink
  214 +function sink.simplify(snk)
  215 + base.assert(snk)
  216 + return function(chunk, err)
  217 + local ret, err_or_new = snk(chunk, err)
  218 + if not ret then return nil, err_or_new end
  219 + snk = err_or_new or snk
  220 + return 1
  221 + end
  222 +end
  223 +
  224 +-- creates a file sink
  225 +function sink.file(handle, io_err)
  226 + if handle then
  227 + return function(chunk, err)
  228 + if not chunk then
  229 + handle:close()
  230 + return 1
  231 + else return handle:write(chunk) end
  232 + end
  233 + else return sink.error(io_err or "unable to open file") end
  234 +end
  235 +
  236 +-- creates a sink that discards data
  237 +local function null()
  238 + return 1
  239 +end
  240 +
  241 +function sink.null()
  242 + return null
  243 +end
  244 +
  245 +-- creates a sink that just returns an error
  246 +function sink.error(err)
  247 + return function()
  248 + return nil, err
  249 + end
  250 +end
  251 +
  252 +-- chains a sink with a filter
  253 +function sink.chain(f, snk)
  254 + base.assert(f and snk)
  255 + return function(chunk, err)
  256 + if chunk ~= "" then
  257 + local filtered = f(chunk)
  258 + local done = chunk and ""
  259 + while true do
  260 + local ret, snkerr = snk(filtered, err)
  261 + if not ret then return nil, snkerr end
  262 + if filtered == done then return 1 end
  263 + filtered = f(done)
  264 + end
  265 + else return 1 end
  266 + end
  267 +end
  268 +
  269 +-----------------------------------------------------------------------------
  270 +-- Pump stuff
  271 +-----------------------------------------------------------------------------
  272 +-- pumps one chunk from the source to the sink
  273 +function pump.step(src, snk)
  274 + local chunk, src_err = src()
  275 + local ret, snk_err = snk(chunk, src_err)
  276 + if chunk and ret then return 1
  277 + else return nil, src_err or snk_err end
  278 +end
  279 +
  280 +-- pumps all data from a source to a sink, using a step function
  281 +function pump.all(src, snk, step)
  282 + base.assert(src and snk)
  283 + step = step or pump.step
  284 + while true do
  285 + local ret, err = step(src, snk)
  286 + if not ret then
  287 + if err then return nil, err
  288 + else return 1 end
  289 + end
  290 + end
  291 +end
  292 +
... ...
  1 +-----------------------------------------------------------------------------
  2 +-- MIME support for the Lua language.
  3 +-- Author: Diego Nehab
  4 +-- Conforming to RFCs 2045-2049
  5 +-- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $
  6 +-----------------------------------------------------------------------------
  7 +
  8 +-----------------------------------------------------------------------------
  9 +-- Declare module and import dependencies
  10 +-----------------------------------------------------------------------------
  11 +local base = _G
  12 +local ltn12 = require("ltn12")
  13 +local mime = require("mime.core")
  14 +local io = require("io")
  15 +local string = require("string")
  16 +module("mime")
  17 +
  18 +-- encode, decode and wrap algorithm tables
  19 +encodet = {}
  20 +decodet = {}
  21 +wrapt = {}
  22 +
  23 +-- creates a function that chooses a filter by name from a given table
  24 +local function choose(table)
  25 + return function(name, opt1, opt2)
  26 + if base.type(name) ~= "string" then
  27 + name, opt1, opt2 = "default", name, opt1
  28 + end
  29 + local f = table[name or "nil"]
  30 + if not f then
  31 + base.error("unknown key (" .. base.tostring(name) .. ")", 3)
  32 + else return f(opt1, opt2) end
  33 + end
  34 +end
  35 +
  36 +-- define the encoding filters
  37 +encodet['base64'] = function()
  38 + return ltn12.filter.cycle(b64, "")
  39 +end
  40 +
  41 +encodet['quoted-printable'] = function(mode)
  42 + return ltn12.filter.cycle(qp, "",
  43 + (mode == "binary") and "=0D=0A" or "\r\n")
  44 +end
  45 +
  46 +-- define the decoding filters
  47 +decodet['base64'] = function()
  48 + return ltn12.filter.cycle(unb64, "")
  49 +end
  50 +
  51 +decodet['quoted-printable'] = function()
  52 + return ltn12.filter.cycle(unqp, "")
  53 +end
  54 +
  55 +local function format(chunk)
  56 + if chunk then
  57 + if chunk == "" then return "''"
  58 + else return string.len(chunk) end
  59 + else return "nil" end
  60 +end
  61 +
  62 +-- define the line-wrap filters
  63 +wrapt['text'] = function(length)
  64 + length = length or 76
  65 + return ltn12.filter.cycle(wrp, length, length)
  66 +end
  67 +wrapt['base64'] = wrapt['text']
  68 +wrapt['default'] = wrapt['text']
  69 +
  70 +wrapt['quoted-printable'] = function()
  71 + return ltn12.filter.cycle(qpwrp, 76, 76)
  72 +end
  73 +
  74 +-- function that choose the encoding, decoding or wrap algorithm
  75 +encode = choose(encodet)
  76 +decode = choose(decodet)
  77 +wrap = choose(wrapt)
  78 +
  79 +-- define the end-of-line normalization filter
  80 +function normalize(marker)
  81 + return ltn12.filter.cycle(eol, 0, marker)
  82 +end
  83 +
  84 +-- high level stuffing filter
  85 +function stuff()
  86 + return ltn12.filter.cycle(dot, 2)
  87 +end
... ...
  1 +--
  2 +-- RemDebug 1.0 Beta
  3 +-- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug)
  4 +--
  5 +
  6 +local socket = require"socket"
  7 +--local lfs = require"lfs"
  8 +local debug = require"debug"
  9 +
  10 +module("remdebug.engine", package.seeall)
  11 +
  12 +_COPYRIGHT = "2006 - Kepler Project"
  13 +_DESCRIPTION = "Remote Debugger for the Lua programming language"
  14 +_VERSION = "1.0"
  15 +
  16 +local coro_debugger
  17 +local events = { BREAK = 1, WATCH = 2 }
  18 +local breakpoints = {}
  19 +local watches = {}
  20 +local step_into = false
  21 +local step_over = false
  22 +local step_level = 0
  23 +local stack_level = 0
  24 +
  25 +local controller_host = "localhost"
  26 +local controller_port = 8171
  27 +
  28 +local function set_breakpoint(file, line)
  29 + if not breakpoints[file] then
  30 + breakpoints[file] = {}
  31 + end
  32 + breakpoints[file][line] = true
  33 +end
  34 +
  35 +local function remove_breakpoint(file, line)
  36 + if breakpoints[file] then
  37 + breakpoints[file][line] = nil
  38 + end
  39 +end
  40 +
  41 +local function has_breakpoint(file, line)
  42 + return breakpoints[file] and breakpoints[file][line]
  43 +end
  44 +
  45 +local function restore_vars(vars)
  46 + if type(vars) ~= 'table' then return end
  47 + local func = debug.getinfo(3, "f").func
  48 + local i = 1
  49 + local written_vars = {}
  50 + while true do
  51 + local name = debug.getlocal(3, i)
  52 + if not name then break end
  53 + debug.setlocal(3, i, vars[name])
  54 + written_vars[name] = true
  55 + i = i + 1
  56 + end
  57 + i = 1
  58 + while true do
  59 + local name = debug.getupvalue(func, i)
  60 + if not name then break end
  61 + if not written_vars[name] then
  62 + debug.setupvalue(func, i, vars[name])
  63 + written_vars[name] = true
  64 + end
  65 + i = i + 1
  66 + end
  67 +end
  68 +
  69 +local function capture_vars()
  70 + local vars = {}
  71 + local func = debug.getinfo(3, "f").func
  72 + local i = 1
  73 + while true do
  74 + local name, value = debug.getupvalue(func, i)
  75 + if not name then break end
  76 + vars[name] = value
  77 + i = i + 1
  78 + end
  79 + i = 1
  80 + while true do
  81 + local name, value = debug.getlocal(3, i)
  82 + if not name then break end
  83 + vars[name] = value
  84 + i = i + 1
  85 + end
  86 + setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) })
  87 + return vars
  88 +end
  89 +
  90 +local function break_dir(path)
  91 + local paths = {}
  92 + path = string.gsub(path, "\\", "/")
  93 + for w in string.gfind(path, "[^\/]+") do
  94 + table.insert(paths, w)
  95 + end
  96 + return paths
  97 +end
  98 +
  99 +local function merge_paths(path1, path2)
  100 + local paths1 = break_dir(path1)
  101 + local paths2 = break_dir(path2)
  102 + for i, path in ipairs(paths2) do
  103 + if path == ".." then
  104 + table.remove(paths1, table.getn(paths1))
  105 + elseif path ~= "." then
  106 + table.insert(paths1, path)
  107 + end
  108 + end
  109 + return table.concat(paths1, "/")
  110 +end
  111 +
  112 +local function debug_hook(event, line)
  113 + if event == "call" then
  114 + stack_level = stack_level + 1
  115 + elseif event == "return" then
  116 + stack_level = stack_level - 1
  117 + else
  118 + local file = debug.getinfo(2, "S").source
  119 + if string.find(file, "@") == 1 then
  120 + file = string.sub(file, 2)
  121 + end
  122 + file = merge_paths("/", file)
  123 + local vars = capture_vars()
  124 + table.foreach(watches, function (index, value)
  125 + setfenv(value, vars)
  126 + local status, res = pcall(value)
  127 + if status and res then
  128 + coroutine.resume(coro_debugger, events.WATCH, vars, file, line, index)
  129 + end
  130 + end)
  131 + if step_into or (step_over and stack_level <= step_level) or has_breakpoint(file, line) then
  132 + step_into = false
  133 + step_over = false
  134 + coroutine.resume(coro_debugger, events.BREAK, vars, file, line)
  135 + restore_vars(vars)
  136 + end
  137 + end
  138 +end
  139 +
  140 +local function debugger_loop(server)
  141 + local command
  142 + local eval_env = {}
  143 +
  144 + while true do
  145 + local line, status = server:receive()
  146 + command = string.sub(line, string.find(line, "^[A-Z]+"))
  147 + if command == "SETB" then
  148 + local _, _, _, filename, line = string.find(line, "^([A-Z]+)%s+([%w%p]+)%s+(%d+)$")
  149 + if filename and line then
  150 + set_breakpoint(filename, tonumber(line))
  151 + server:send("200 OK\n")
  152 + else
  153 + server:send("400 Bad Request\n")
  154 + end
  155 + elseif command == "DELB" then
  156 + local _, _, _, filename, line = string.find(line, "^([A-Z]+)%s+([%w%p]+)%s+(%d+)$")
  157 + if filename and line then
  158 + remove_breakpoint(filename, tonumber(line))
  159 + server:send("200 OK\n")
  160 + else
  161 + server:send("400 Bad Request\n")
  162 + end
  163 + elseif command == "EXEC" then
  164 + local _, _, chunk = string.find(line, "^[A-Z]+%s+(.+)$")
  165 + if chunk then
  166 + local func = loadstring(chunk)
  167 + local status, res
  168 + if func then
  169 + setfenv(func, eval_env)
  170 + status, res = xpcall(func, debug.traceback)
  171 + end
  172 + res = tostring(res)
  173 + if status then
  174 + server:send("200 OK " .. string.len(res) .. "\n")
  175 + server:send(res)
  176 + else
  177 + server:send("401 Error in Expression " .. string.len(res) .. "\n")
  178 + server:send(res)
  179 + end
  180 + else
  181 + server:send("400 Bad Request\n")
  182 + end
  183 + elseif command == "SETW" then
  184 + local _, _, exp = string.find(line, "^[A-Z]+%s+(.+)$")
  185 + if exp then
  186 + local func = loadstring("return(" .. exp .. ")")
  187 + local newidx = table.getn(watches) + 1
  188 + watches[newidx] = func
  189 + table.setn(watches, newidx)
  190 + server:send("200 OK " .. newidx .. "\n")
  191 + else
  192 + server:send("400 Bad Request\n")
  193 + end
  194 + elseif command == "DELW" then
  195 + local _, _, index = string.find(line, "^[A-Z]+%s+(%d+)$")
  196 + index = tonumber(index)
  197 + if index then
  198 + watches[index] = nil
  199 + server:send("200 OK\n")
  200 + else
  201 + server:send("400 Bad Request\n")
  202 + end
  203 + elseif command == "RUN" then
  204 + server:send("200 OK\n")
  205 + local ev, vars, file, line, idx_watch = coroutine.yield()
  206 + eval_env = vars
  207 + if ev == events.BREAK then
  208 + server:send("202 Paused " .. file .. " " .. line .. "\n")
  209 + elseif ev == events.WATCH then
  210 + server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
  211 + else
  212 + server:send("401 Error in Execution " .. string.len(file) .. "\n")
  213 + server:send(file)
  214 + end
  215 + elseif command == "STEP" then
  216 + server:send("200 OK\n")
  217 + step_into = true
  218 + local ev, vars, file, line, idx_watch = coroutine.yield()
  219 + eval_env = vars
  220 + if ev == events.BREAK then
  221 + server:send("202 Paused " .. file .. " " .. line .. "\n")
  222 + elseif ev == events.WATCH then
  223 + server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
  224 + else
  225 + server:send("401 Error in Execution " .. string.len(file) .. "\n")
  226 + server:send(file)
  227 + end
  228 + elseif command == "OVER" then
  229 + server:send("200 OK\n")
  230 + step_over = true
  231 + step_level = stack_level
  232 + local ev, vars, file, line, idx_watch = coroutine.yield()
  233 + eval_env = vars
  234 + if ev == events.BREAK then
  235 + server:send("202 Paused " .. file .. " " .. line .. "\n")
  236 + elseif ev == events.WATCH then
  237 + server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
  238 + else
  239 + server:send("401 Error in Execution " .. string.len(file) .. "\n")
  240 + server:send(file)
  241 + end
  242 + else
  243 + server:send("400 Bad Request\n")
  244 + end
  245 + end
  246 +end
  247 +
  248 +coro_debugger = coroutine.create(debugger_loop)
  249 +
  250 +--
  251 +-- remdebug.engine.config(tab)
  252 +-- Configures the engine
  253 +--
  254 +function config(tab)
  255 + if tab.host then
  256 + controller_host = tab.host
  257 + end
  258 + if tab.port then
  259 + controller_port = tab.port
  260 + end
  261 +end
  262 +
  263 +--
  264 +-- remdebug.engine.start()
  265 +-- Tries to start the debug session by connecting with a controller
  266 +--
  267 +function start()
  268 + pcall(require, "remdebug.config")
  269 + local server = socket.tcp()
  270 + server:connect(controller_host, controller_port)
  271 + if server then
  272 + _TRACEBACK = function (message)
  273 + local err = debug.traceback(message)
  274 + server:send("401 Error in Execution " .. string.len(err) .. "\n")
  275 + server:send(err)
  276 + server:close()
  277 + return err
  278 + end
  279 + debug.sethook(debug_hook, "lcr")
  280 + return coroutine.resume(coro_debugger, server)
  281 + end
  282 +end
  283 +
... ...
  1 +-----------------------------------------------------------------------------
  2 +-- LuaSocket helper module
  3 +-- Author: Diego Nehab
  4 +-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
  5 +-----------------------------------------------------------------------------
  6 +
  7 +-----------------------------------------------------------------------------
  8 +-- Declare module and import dependencies
  9 +-----------------------------------------------------------------------------
  10 +local base = _G
  11 +local string = require("string")
  12 +local math = require("math")
  13 +local socket = require("socket.core")
  14 +module("socket")
  15 +
  16 +-----------------------------------------------------------------------------
  17 +-- Exported auxiliar functions
  18 +-----------------------------------------------------------------------------
  19 +function connect(address, port, laddress, lport)
  20 + local sock, err = socket.tcp()
  21 + if not sock then return nil, err end
  22 + if laddress then
  23 + local res, err = sock:bind(laddress, lport, -1)
  24 + if not res then return nil, err end
  25 + end
  26 + local res, err = sock:connect(address, port)
  27 + if not res then return nil, err end
  28 + return sock
  29 +end
  30 +
  31 +function bind(host, port, backlog)
  32 + local sock, err = socket.tcp()
  33 + if not sock then return nil, err end
  34 + sock:setoption("reuseaddr", true)
  35 + local res, err = sock:bind(host, port)
  36 + if not res then return nil, err end
  37 + res, err = sock:listen(backlog)
  38 + if not res then return nil, err end
  39 + return sock
  40 +end
  41 +
  42 +try = newtry()
  43 +
  44 +function choose(table)
  45 + return function(name, opt1, opt2)
  46 + if base.type(name) ~= "string" then
  47 + name, opt1, opt2 = "default", name, opt1
  48 + end
  49 + local f = table[name or "nil"]
  50 + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
  51 + else return f(opt1, opt2) end
  52 + end
  53 +end
  54 +
  55 +-----------------------------------------------------------------------------
  56 +-- Socket sources and sinks, conforming to LTN12
  57 +-----------------------------------------------------------------------------
  58 +-- create namespaces inside LuaSocket namespace
  59 +sourcet = {}
  60 +sinkt = {}
  61 +
  62 +BLOCKSIZE = 2048
  63 +
  64 +sinkt["close-when-done"] = function(sock)
  65 + return base.setmetatable({
  66 + getfd = function() return sock:getfd() end,
  67 + dirty = function() return sock:dirty() end
  68 + }, {
  69 + __call = function(self, chunk, err)
  70 + if not chunk then
  71 + sock:close()
  72 + return 1
  73 + else return sock:send(chunk) end
  74 + end
  75 + })
  76 +end
  77 +
  78 +sinkt["keep-open"] = function(sock)
  79 + return base.setmetatable({
  80 + getfd = function() return sock:getfd() end,
  81 + dirty = function() return sock:dirty() end
  82 + }, {
  83 + __call = function(self, chunk, err)
  84 + if chunk then return sock:send(chunk)
  85 + else return 1 end
  86 + end
  87 + })
  88 +end
  89 +
  90 +sinkt["default"] = sinkt["keep-open"]
  91 +
  92 +sink = choose(sinkt)
  93 +
  94 +sourcet["by-length"] = function(sock, length)
  95 + return base.setmetatable({
  96 + getfd = function() return sock:getfd() end,
  97 + dirty = function() return sock:dirty() end
  98 + }, {
  99 + __call = function()
  100 + if length <= 0 then return nil end
  101 + local size = math.min(socket.BLOCKSIZE, length)
  102 + local chunk, err = sock:receive(size)
  103 + if err then return nil, err end
  104 + length = length - string.len(chunk)
  105 + return chunk
  106 + end
  107 + })
  108 +end
  109 +
  110 +sourcet["until-closed"] = function(sock)
  111 + local done
  112 + return base.setmetatable({
  113 + getfd = function() return sock:getfd() end,
  114 + dirty = function() return sock:dirty() end
  115 + }, {
  116 + __call = function()
  117 + if done then return nil end
  118 + local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
  119 + if not err then return chunk
  120 + elseif err == "closed" then
  121 + sock:close()
  122 + done = 1
  123 + return partial
  124 + else return nil, err end
  125 + end
  126 + })
  127 +end
  128 +
  129 +
  130 +sourcet["default"] = sourcet["until-closed"]
  131 +
  132 +source = choose(sourcet)
  133 +
... ...
  1 +-----------------------------------------------------------------------------
  2 +-- FTP support for the Lua language
  3 +-- LuaSocket toolkit.
  4 +-- Author: Diego Nehab
  5 +-- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $
  6 +-----------------------------------------------------------------------------
  7 +
  8 +-----------------------------------------------------------------------------
  9 +-- Declare module and import dependencies
  10 +-----------------------------------------------------------------------------
  11 +local base = _G
  12 +local table = require("table")
  13 +local string = require("string")
  14 +local math = require("math")
  15 +local socket = require("socket")
  16 +local url = require("socket.url")
  17 +local tp = require("socket.tp")
  18 +local ltn12 = require("ltn12")
  19 +module("socket.ftp")
  20 +
  21 +-----------------------------------------------------------------------------
  22 +-- Program constants
  23 +-----------------------------------------------------------------------------
  24 +-- timeout in seconds before the program gives up on a connection
  25 +TIMEOUT = 60
  26 +-- default port for ftp service
  27 +PORT = 21
  28 +-- this is the default anonymous password. used when no password is
  29 +-- provided in url. should be changed to your e-mail.
  30 +USER = "ftp"
  31 +PASSWORD = "anonymous@anonymous.org"
  32 +
  33 +-----------------------------------------------------------------------------
  34 +-- Low level FTP API
  35 +-----------------------------------------------------------------------------
  36 +local metat = { __index = {} }
  37 +
  38 +function open(server, port, create)
  39 + local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create))
  40 + local f = base.setmetatable({ tp = tp }, metat)
  41 + -- make sure everything gets closed in an exception
  42 + f.try = socket.newtry(function() f:close() end)
  43 + return f
  44 +end
  45 +
  46 +function metat.__index:portconnect()
  47 + self.try(self.server:settimeout(TIMEOUT))
  48 + self.data = self.try(self.server:accept())
  49 + self.try(self.data:settimeout(TIMEOUT))
  50 +end
  51 +
  52 +function metat.__index:pasvconnect()
  53 + self.data = self.try(socket.tcp())
  54 + self.try(self.data:settimeout(TIMEOUT))
  55 + self.try(self.data:connect(self.pasvt.ip, self.pasvt.port))
  56 +end
  57 +
  58 +function metat.__index:login(user, password)
  59 + self.try(self.tp:command("user", user or USER))
  60 + local code, reply = self.try(self.tp:check{"2..", 331})
  61 + if code == 331 then
  62 + self.try(self.tp:command("pass", password or PASSWORD))
  63 + self.try(self.tp:check("2.."))
  64 + end
  65 + return 1
  66 +end
  67 +
  68 +function metat.__index:pasv()
  69 + self.try(self.tp:command("pasv"))
  70 + local code, reply = self.try(self.tp:check("2.."))
  71 + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)"
  72 + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern))
  73 + self.try(a and b and c and d and p1 and p2, reply)
  74 + self.pasvt = {
  75 + ip = string.format("%d.%d.%d.%d", a, b, c, d),
  76 + port = p1*256 + p2
  77 + }
  78 + if self.server then
  79 + self.server:close()
  80 + self.server = nil
  81 + end
  82 + return self.pasvt.ip, self.pasvt.port
  83 +end
  84 +
  85 +function metat.__index:port(ip, port)
  86 + self.pasvt = nil
  87 + if not ip then
  88 + ip, port = self.try(self.tp:getcontrol():getsockname())
  89 + self.server = self.try(socket.bind(ip, 0))
  90 + ip, port = self.try(self.server:getsockname())
  91 + self.try(self.server:settimeout(TIMEOUT))
  92 + end
  93 + local pl = math.mod(port, 256)
  94 + local ph = (port - pl)/256
  95 + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
  96 + self.try(self.tp:command("port", arg))
  97 + self.try(self.tp:check("2.."))
  98 + return 1
  99 +end
  100 +
  101 +function metat.__index:send(sendt)
  102 + self.try(self.pasvt or self.server, "need port or pasv first")
  103 + -- if there is a pasvt table, we already sent a PASV command
  104 + -- we just get the data connection into self.data
  105 + if self.pasvt then self:pasvconnect() end
  106 + -- get the transfer argument and command
  107 + local argument = sendt.argument or
  108 + url.unescape(string.gsub(sendt.path or "", "^[/\\]", ""))
  109 + if argument == "" then argument = nil end
  110 + local command = sendt.command or "stor"
  111 + -- send the transfer command and check the reply
  112 + self.try(self.tp:command(command, argument))
  113 + local code, reply = self.try(self.tp:check{"2..", "1.."})
  114 + -- if there is not a a pasvt table, then there is a server
  115 + -- and we already sent a PORT command
  116 + if not self.pasvt then self:portconnect() end
  117 + -- get the sink, source and step for the transfer
  118 + local step = sendt.step or ltn12.pump.step
  119 + local readt = {self.tp.c}
  120 + local checkstep = function(src, snk)
  121 + -- check status in control connection while downloading
  122 + local readyt = socket.select(readt, nil, 0)
  123 + if readyt[tp] then code = self.try(self.tp:check("2..")) end
  124 + return step(src, snk)
  125 + end
  126 + local sink = socket.sink("close-when-done", self.data)
  127 + -- transfer all data and check error
  128 + self.try(ltn12.pump.all(sendt.source, sink, checkstep))
  129 + if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  130 + -- done with data connection
  131 + self.data:close()
  132 + -- find out how many bytes were sent
  133 + local sent = socket.skip(1, self.data:getstats())
  134 + self.data = nil
  135 + return sent
  136 +end
  137 +
  138 +function metat.__index:receive(recvt)
  139 + self.try(self.pasvt or self.server, "need port or pasv first")
  140 + if self.pasvt then self:pasvconnect() end
  141 + local argument = recvt.argument or
  142 + url.unescape(string.gsub(recvt.path or "", "^[/\\]", ""))
  143 + if argument == "" then argument = nil end
  144 + local command = recvt.command or "retr"
  145 + self.try(self.tp:command(command, argument))
  146 + local code = self.try(self.tp:check{"1..", "2.."})
  147 + if not self.pasvt then self:portconnect() end
  148 + local source = socket.source("until-closed", self.data)
  149 + local step = recvt.step or ltn12.pump.step
  150 + self.try(ltn12.pump.all(source, recvt.sink, step))
  151 + if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  152 + self.data:close()
  153 + self.data = nil
  154 + return 1
  155 +end
  156 +
  157 +function metat.__index:cwd(dir)
  158 + self.try(self.tp:command("cwd", dir))
  159 + self.try(self.tp:check(250))
  160 + return 1
  161 +end
  162 +
  163 +function metat.__index:type(type)
  164 + self.try(self.tp:command("type", type))
  165 + self.try(self.tp:check(200))
  166 + return 1
  167 +end
  168 +
  169 +function metat.__index:greet()
  170 + local code = self.try(self.tp:check{"1..", "2.."})
  171 + if string.find(code, "1..") then self.try(self.tp:check("2..")) end
  172 + return 1
  173 +end
  174 +
  175 +function metat.__index:quit()
  176 + self.try(self.tp:command("quit"))
  177 + self.try(self.tp:check("2.."))
  178 + return 1
  179 +end
  180 +
  181 +function metat.__index:close()
  182 + if self.data then self.data:close() end
  183 + if self.server then self.server:close() end
  184 + return self.tp:close()
  185 +end
  186 +
  187 +-----------------------------------------------------------------------------
  188 +-- High level FTP API
  189 +-----------------------------------------------------------------------------
  190 +local function override(t)
  191 + if t.url then
  192 + local u = url.parse(t.url)
  193 + for i,v in base.pairs(t) do
  194 + u[i] = v
  195 + end
  196 + return u
  197 + else return t end
  198 +end
  199 +
  200 +local function tput(putt)
  201 + putt = override(putt)
  202 + socket.try(putt.host, "missing hostname")
  203 + local f = open(putt.host, putt.port, putt.create)
  204 + f:greet()
  205 + f:login(putt.user, putt.password)
  206 + if putt.type then f:type(putt.type) end
  207 + f:pasv()
  208 + local sent = f:send(putt)
  209 + f:quit()
  210 + f:close()
  211 + return sent
  212 +end
  213 +
  214 +local default = {
  215 + path = "/",
  216 + scheme = "ftp"
  217 +}
  218 +
  219 +local function parse(u)
  220 + local t = socket.try(url.parse(u, default))
  221 + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'")
  222 + socket.try(t.host, "missing hostname")
  223 + local pat = "^type=(.)$"
  224 + if t.params then
  225 + t.type = socket.skip(2, string.find(t.params, pat))
  226 + socket.try(t.type == "a" or t.type == "i",
  227 + "invalid type '" .. t.type .. "'")
  228 + end
  229 + return t
  230 +end
  231 +
  232 +local function sput(u, body)
  233 + local putt = parse(u)
  234 + putt.source = ltn12.source.string(body)
  235 + return tput(putt)
  236 +end
  237 +
  238 +put = socket.protect(function(putt, body)
  239 + if base.type(putt) == "string" then return sput(putt, body)
  240 + else return tput(putt) end
  241 +end)
  242 +
  243 +local function tget(gett)
  244 + gett = override(gett)
  245 + socket.try(gett.host, "missing hostname")
  246 + local f = open(gett.host, gett.port, gett.create)
  247 + f:greet()
  248 + f:login(gett.user, gett.password)
  249 + if gett.type then f:type(gett.type) end
  250 + f:pasv()
  251 + f:receive(gett)
  252 + f:quit()
  253 + return f:close()
  254 +end
  255 +
  256 +local function sget(u)
  257 + local gett = parse(u)
  258 + local t = {}
  259 + gett.sink = ltn12.sink.table(t)
  260 + tget(gett)
  261 + return table.concat(t)
  262 +end
  263 +
  264 +command = socket.protect(function(cmdt)
  265 + cmdt = override(cmdt)
  266 + socket.try(cmdt.host, "missing hostname")
  267 + socket.try(cmdt.command, "missing command")
  268 + local f = open(cmdt.host, cmdt.port, cmdt.create)
  269 + f:greet()
  270 + f:login(cmdt.user, cmdt.password)
  271 + f.try(f.tp:command(cmdt.command, cmdt.argument))
  272 + if cmdt.check then f.try(f.tp:check(cmdt.check)) end
  273 + f:quit()
  274 + return f:close()
  275 +end)
  276 +
  277 +get = socket.protect(function(gett)
  278 + if base.type(gett) == "string" then return sget(gett)
  279 + else return tget(gett) end
  280 +end)
  281 +
... ...
  1 +-----------------------------------------------------------------------------
  2 +-- HTTP/1.1 client support for the Lua language.
  3 +-- LuaSocket toolkit.
  4 +-- Author: Diego Nehab
  5 +-- RCS ID: $Id: http.lua,v 1.71 2007/10/13 23:55:20 diego Exp $
  6 +-----------------------------------------------------------------------------
  7 +
  8 +-----------------------------------------------------------------------------
  9 +-- Declare module and import dependencies
  10 +-------------------------------------------------------------------------------
  11 +local socket = require("socket")
  12 +local url = require("socket.url")
  13 +local ltn12 = require("ltn12")
  14 +local mime = require("mime")
  15 +local string = require("string")
  16 +local base = _G
  17 +local table = require("table")
  18 +module("socket.http")
  19 +
  20 +-----------------------------------------------------------------------------
  21 +-- Program constants
  22 +-----------------------------------------------------------------------------
  23 +-- connection timeout in seconds
  24 +TIMEOUT = 60
  25 +-- default port for document retrieval
  26 +PORT = 80
  27 +-- user agent field sent in request
  28 +USERAGENT = socket._VERSION
  29 +
  30 +-----------------------------------------------------------------------------
  31 +-- Reads MIME headers from a connection, unfolding where needed
  32 +-----------------------------------------------------------------------------
  33 +local function receiveheaders(sock, headers)
  34 + local line, name, value, err
  35 + headers = headers or {}
  36 + -- get first line
  37 + line, err = sock:receive()
  38 + if err then return nil, err end
  39 + -- headers go until a blank line is found
  40 + while line ~= "" do
  41 + -- get field-name and value
  42 + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
  43 + if not (name and value) then return nil, "malformed reponse headers" end
  44 + name = string.lower(name)
  45 + -- get next line (value might be folded)
  46 + line, err = sock:receive()
  47 + if err then return nil, err end
  48 + -- unfold any folded values
  49 + while string.find(line, "^%s") do
  50 + value = value .. line
  51 + line = sock:receive()
  52 + if err then return nil, err end
  53 + end
  54 + -- save pair in table
  55 + if headers[name] then headers[name] = headers[name] .. ", " .. value
  56 + else headers[name] = value end
  57 + end
  58 + return headers
  59 +end
  60 +
  61 +-----------------------------------------------------------------------------
  62 +-- Extra sources and sinks
  63 +-----------------------------------------------------------------------------
  64 +socket.sourcet["http-chunked"] = function(sock, headers)
  65 + return base.setmetatable({
  66 + getfd = function() return sock:getfd() end,
  67 + dirty = function() return sock:dirty() end
  68 + }, {
  69 + __call = function()
  70 + -- get chunk size, skip extention
  71 + local line, err = sock:receive()
  72 + if err then return nil, err end
  73 + local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
  74 + if not size then return nil, "invalid chunk size" end
  75 + -- was it the last chunk?
  76 + if size > 0 then
  77 + -- if not, get chunk and skip terminating CRLF
  78 + local chunk, err, part = sock:receive(size)
  79 + if chunk then sock:receive() end
  80 + return chunk, err
  81 + else
  82 + -- if it was, read trailers into headers table
  83 + headers, err = receiveheaders(sock, headers)
  84 + if not headers then return nil, err end
  85 + end
  86 + end
  87 + })
  88 +end
  89 +
  90 +socket.sinkt["http-chunked"] = function(sock)
  91 + return base.setmetatable({
  92 + getfd = function() return sock:getfd() end,
  93 + dirty = function() return sock:dirty() end
  94 + }, {
  95 + __call = function(self, chunk, err)
  96 + if not chunk then return sock:send("0\r\n\r\n") end
  97 + local size = string.format("%X\r\n", string.len(chunk))
  98 + return sock:send(size .. chunk .. "\r\n")
  99 + end
  100 + })
  101 +end
  102 +
  103 +-----------------------------------------------------------------------------
  104 +-- Low level HTTP API
  105 +-----------------------------------------------------------------------------
  106 +local metat = { __index = {} }
  107 +
  108 +function open(host, port, create)
  109 + -- create socket with user connect function, or with default
  110 + local c = socket.try((create or socket.tcp)())
  111 + local h = base.setmetatable({ c = c }, metat)
  112 + -- create finalized try
  113 + h.try = socket.newtry(function() h:close() end)
  114 + -- set timeout before connecting
  115 + h.try(c:settimeout(TIMEOUT))
  116 + h.try(c:connect(host, port or PORT))
  117 + -- here everything worked
  118 + return h
  119 +end
  120 +
  121 +function metat.__index:sendrequestline(method, uri)
  122 + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
  123 + return self.try(self.c:send(reqline))
  124 +end
  125 +
  126 +function metat.__index:sendheaders(headers)
  127 + local h = "\r\n"
  128 + for i, v in base.pairs(headers) do
  129 + h = i .. ": " .. v .. "\r\n" .. h
  130 + end
  131 + self.try(self.c:send(h))
  132 + return 1
  133 +end
  134 +
  135 +function metat.__index:sendbody(headers, source, step)
  136 + source = source or ltn12.source.empty()
  137 + step = step or ltn12.pump.step
  138 + -- if we don't know the size in advance, send chunked and hope for the best
  139 + local mode = "http-chunked"
  140 + if headers["content-length"] then mode = "keep-open" end
  141 + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
  142 +end
  143 +
  144 +function metat.__index:receivestatusline()
  145 + local status = self.try(self.c:receive(5))
  146 + -- identify HTTP/0.9 responses, which do not contain a status line
  147 + -- this is just a heuristic, but is what the RFC recommends
  148 + if status ~= "HTTP/" then return nil, status end
  149 + -- otherwise proceed reading a status line
  150 + status = self.try(self.c:receive("*l", status))
  151 + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
  152 + return self.try(base.tonumber(code), status)
  153 +end
  154 +
  155 +function metat.__index:receiveheaders()
  156 + return self.try(receiveheaders(self.c))
  157 +end
  158 +
  159 +function metat.__index:receivebody(headers, sink, step)
  160 + sink = sink or ltn12.sink.null()
  161 + step = step or ltn12.pump.step
  162 + local length = base.tonumber(headers["content-length"])
  163 + local t = headers["transfer-encoding"] -- shortcut
  164 + local mode = "default" -- connection close
  165 + if t and t ~= "identity" then mode = "http-chunked"
  166 + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
  167 + return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
  168 + sink, step))
  169 +end
  170 +
  171 +function metat.__index:receive09body(status, sink, step)
  172 + local source = ltn12.source.rewind(socket.source("until-closed", self.c))
  173 + source(status)
  174 + return self.try(ltn12.pump.all(source, sink, step))
  175 +end
  176 +
  177 +function metat.__index:close()
  178 + return self.c:close()
  179 +end
  180 +
  181 +-----------------------------------------------------------------------------
  182 +-- High level HTTP API
  183 +-----------------------------------------------------------------------------
  184 +local function adjusturi(reqt)
  185 + local u = reqt
  186 + -- if there is a proxy, we need the full url. otherwise, just a part.
  187 + if not reqt.proxy and not PROXY then
  188 + u = {
  189 + path = socket.try(reqt.path, "invalid path 'nil'"),
  190 + params = reqt.params,
  191 + query = reqt.query,
  192 + fragment = reqt.fragment
  193 + }
  194 + end
  195 + return url.build(u)
  196 +end
  197 +
  198 +local function adjustproxy(reqt)
  199 + local proxy = reqt.proxy or PROXY
  200 + if proxy then
  201 + proxy = url.parse(proxy)
  202 + return proxy.host, proxy.port or 3128
  203 + else
  204 + return reqt.host, reqt.port
  205 + end
  206 +end
  207 +
  208 +local function adjustheaders(reqt)
  209 + -- default headers
  210 + local lower = {
  211 + ["user-agent"] = USERAGENT,
  212 + ["host"] = reqt.host,
  213 + ["connection"] = "close, TE",
  214 + ["te"] = "trailers"
  215 + }
  216 + -- if we have authentication information, pass it along
  217 + if reqt.user and reqt.password then
  218 + lower["authorization"] =
  219