cleanup; move ./etc into ./samples and mark 'unsupported'
This commit is contained in:
parent
7187be8b76
commit
86de838eb5
18 changed files with 98 additions and 112 deletions
|
@ -1,11 +1,95 @@
|
|||
This directory contains some sample programs using
|
||||
LuaSocket. This code is not supported.
|
||||
|
||||
tftp.lua -- Trivial FTP client
|
||||
|
||||
This module implements file retrieval by the TFTP protocol.
|
||||
Its main use was to test the UDP code, but since someone
|
||||
found it usefull, I turned it into a module that is almost
|
||||
official (no uploads, yet).
|
||||
|
||||
dict.lua -- Dict client
|
||||
|
||||
The dict.lua module started with a cool simple client
|
||||
for the DICT protocol, written by Luiz Henrique Figueiredo.
|
||||
This new version has been converted into a library, similar
|
||||
to the HTTP and FTP libraries, that can be used from within
|
||||
any luasocket application. Take a look on the source code
|
||||
and you will be able to figure out how to use it.
|
||||
|
||||
lp.lua -- LPD client library
|
||||
|
||||
The lp.lua module implements the client part of the Line
|
||||
Printer Daemon protocol, used to print files on Unix
|
||||
machines. It is courtesy of David Burgess! See the source
|
||||
code and the lpr.lua in the examples directory.
|
||||
|
||||
b64.lua
|
||||
qp.lua
|
||||
eol.lua
|
||||
|
||||
These are tiny programs that perform Base64,
|
||||
Quoted-Printable and end-of-line marker conversions.
|
||||
|
||||
get.lua -- file retriever
|
||||
|
||||
This little program is a client that uses the FTP and
|
||||
HTTP code to implement a command line file graber. Just
|
||||
run
|
||||
|
||||
lua get.lua <remote-file> [<local-file>]
|
||||
|
||||
to download a remote file (either ftp:// or http://) to
|
||||
the specified local file. The program also prints the
|
||||
download throughput, elapsed time, bytes already downloaded
|
||||
etc during download.
|
||||
|
||||
check-memory.lua -- checks memory consumption
|
||||
|
||||
This is just to see how much memory each module uses.
|
||||
|
||||
dispatch.lua -- coroutine based dispatcher
|
||||
|
||||
This is a first try at a coroutine based non-blocking
|
||||
dispatcher for LuaSocket. Take a look at 'check-links.lua'
|
||||
and at 'forward.lua' to see how to use it.
|
||||
|
||||
check-links.lua -- HTML link checker program
|
||||
|
||||
This little program scans a HTML file and checks for broken
|
||||
links. It is similar to check-links.pl by Jamie Zawinski,
|
||||
but uses all facilities of the LuaSocket library and the Lua
|
||||
language. It has not been thoroughly tested, but it should
|
||||
work. Just run
|
||||
|
||||
lua check-links.lua [-n] {<url>} > output
|
||||
|
||||
and open the result to see a list of broken links. Make sure
|
||||
you check the '-n' switch. It runs in non-blocking mode,
|
||||
using coroutines, and is MUCH faster!
|
||||
|
||||
forward.lua -- coroutine based forward server
|
||||
|
||||
This is a forward server that can accept several connections
|
||||
and transfers simultaneously using non-blocking I/O and the
|
||||
coroutine-based dispatcher. You can run, for example
|
||||
|
||||
lua forward.lua 8080:proxy.com:3128
|
||||
|
||||
to redirect all local conections to port 8080 to the host
|
||||
'proxy.com' at port 3128.
|
||||
|
||||
unix.c and unix.h
|
||||
|
||||
This is an implementation of Unix local domain sockets and
|
||||
demonstrates how to extend LuaSocket with a new type of
|
||||
transport. It has been tested on Linux and on Mac OS X.
|
||||
|
||||
listener.lua -- socket to stdout
|
||||
talker.lua -- stdin to socket
|
||||
|
||||
listener.lua and talker.lua are about the simplest
|
||||
applications you can write using LuaSocket. Run
|
||||
applications you can write using LuaSocket. Run
|
||||
|
||||
'lua listener.lua' and 'lua talker.lua'
|
||||
|
||||
|
@ -17,13 +101,13 @@ be printed by listen.lua.
|
|||
This is a cool program written by David Burgess to print
|
||||
files using the Line Printer Daemon protocol, widely used in
|
||||
Unix machines. It uses the lp.lua implementation, in the
|
||||
etc directory. Just run 'lua lpr.lua <filename>
|
||||
samples directory. Just run 'lua lpr.lua <filename>
|
||||
queue=<printername>' and the file will print!
|
||||
|
||||
cddb.lua -- CDDB client
|
||||
|
||||
This is the first try on a simple CDDB client. Not really
|
||||
useful, but one day it might become a module.
|
||||
useful, but one day it might become a module.
|
||||
|
||||
daytimeclnt.lua -- day time client
|
||||
|
||||
|
|
19
samples/b64.lua
Normal file
19
samples/b64.lua
Normal file
|
@ -0,0 +1,19 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program to convert to and from Base64
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local ltn12 = require("ltn12")
|
||||
local mime = require("mime")
|
||||
local source = ltn12.source.file(io.stdin)
|
||||
local sink = ltn12.sink.file(io.stdout)
|
||||
local convert
|
||||
if arg and arg[1] == '-d' then
|
||||
convert = mime.decode("base64")
|
||||
else
|
||||
local base64 = mime.encode("base64")
|
||||
local wrap = mime.wrap()
|
||||
convert = ltn12.filter.chain(base64, wrap)
|
||||
end
|
||||
sink = ltn12.sink.chain(convert, sink)
|
||||
ltn12.pump.all(source, sink)
|
111
samples/check-links.lua
Normal file
111
samples/check-links.lua
Normal file
|
@ -0,0 +1,111 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program that checks links in HTML files, using coroutines and
|
||||
-- non-blocking I/O via the dispatcher module.
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local url = require("socket.url")
|
||||
local dispatch = require("dispatch")
|
||||
local http = require("socket.http")
|
||||
dispatch.TIMEOUT = 10
|
||||
|
||||
-- make sure the user knows how to invoke us
|
||||
arg = arg or {}
|
||||
if #arg < 1 then
|
||||
print("Usage:\n luasocket check-links.lua [-n] {<url>}")
|
||||
exit()
|
||||
end
|
||||
|
||||
-- '-n' means we are running in non-blocking mode
|
||||
if arg[1] == "-n" then
|
||||
-- if non-blocking I/O was requested, use real dispatcher interface
|
||||
table.remove(arg, 1)
|
||||
handler = dispatch.newhandler("coroutine")
|
||||
else
|
||||
-- if using blocking I/O, use fake dispatcher interface
|
||||
handler = dispatch.newhandler("sequential")
|
||||
end
|
||||
|
||||
local nthreads = 0
|
||||
|
||||
-- get the status of a URL using the dispatcher
|
||||
function getstatus(link)
|
||||
local parsed = url.parse(link, {scheme = "file"})
|
||||
if parsed.scheme == "http" then
|
||||
nthreads = nthreads + 1
|
||||
handler:start(function()
|
||||
local r, c, h, s = http.request{
|
||||
method = "HEAD",
|
||||
url = link,
|
||||
create = handler.tcp
|
||||
}
|
||||
if r and c == 200 then io.write('\t', link, '\n')
|
||||
else io.write('\t', link, ': ', tostring(c), '\n') end
|
||||
nthreads = nthreads - 1
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function readfile(path)
|
||||
path = url.unescape(path)
|
||||
local file, error = io.open(path, "r")
|
||||
if file then
|
||||
local body = file:read("*a")
|
||||
file:close()
|
||||
return body
|
||||
else return nil, error end
|
||||
end
|
||||
|
||||
function load(u)
|
||||
local parsed = url.parse(u, { scheme = "file" })
|
||||
local body, headers, code, error
|
||||
local base = u
|
||||
if parsed.scheme == "http" then
|
||||
body, code, headers = http.request(u)
|
||||
if code == 200 then
|
||||
-- if there was a redirect, update base to reflect it
|
||||
base = headers.location or base
|
||||
end
|
||||
if not body then
|
||||
error = code
|
||||
end
|
||||
elseif parsed.scheme == "file" then
|
||||
body, error = readfile(parsed.path)
|
||||
else error = string.format("unhandled scheme '%s'", parsed.scheme) end
|
||||
return base, body, error
|
||||
end
|
||||
|
||||
function getlinks(body, base)
|
||||
-- get rid of comments
|
||||
body = string.gsub(body, "%<%!%-%-.-%-%-%>", "")
|
||||
local links = {}
|
||||
-- extract links
|
||||
body = string.gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href)
|
||||
table.insert(links, url.absolute(base, href))
|
||||
end)
|
||||
body = string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href)
|
||||
table.insert(links, url.absolute(base, href))
|
||||
end)
|
||||
string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(.-)>", function(href)
|
||||
table.insert(links, url.absolute(base, href))
|
||||
end)
|
||||
return links
|
||||
end
|
||||
|
||||
function checklinks(address)
|
||||
local base, body, error = load(address)
|
||||
if not body then print(error) return end
|
||||
print("Checking ", base)
|
||||
local links = getlinks(body, base)
|
||||
for _, link in ipairs(links) do
|
||||
getstatus(link)
|
||||
end
|
||||
end
|
||||
|
||||
for _, address in ipairs(arg) do
|
||||
checklinks(url.absolute("file:", address))
|
||||
end
|
||||
|
||||
while nthreads > 0 do
|
||||
handler:step()
|
||||
end
|
17
samples/check-memory.lua
Normal file
17
samples/check-memory.lua
Normal file
|
@ -0,0 +1,17 @@
|
|||
function load(s)
|
||||
collectgarbage()
|
||||
local a = gcinfo()
|
||||
_G[s] = require(s)
|
||||
collectgarbage()
|
||||
local b = gcinfo()
|
||||
print(s .. ":\t " .. (b-a) .. "k")
|
||||
end
|
||||
|
||||
load("socket.url")
|
||||
load("ltn12")
|
||||
load("socket")
|
||||
load("mime")
|
||||
load("socket.tp")
|
||||
load("socket.smtp")
|
||||
load("socket.http")
|
||||
load("socket.ftp")
|
88
samples/cookie.lua
Normal file
88
samples/cookie.lua
Normal file
|
@ -0,0 +1,88 @@
|
|||
local socket = require"socket"
|
||||
local http = require"socket.http"
|
||||
local url = require"socket.url"
|
||||
local ltn12 = require"ltn12"
|
||||
|
||||
local token_class = '[^%c%s%(%)%<%>%@%,%;%:%\\%"%/%[%]%?%=%{%}]'
|
||||
|
||||
local function unquote(t, quoted)
|
||||
local n = string.match(t, "%$(%d+)$")
|
||||
if n then n = tonumber(n) end
|
||||
if quoted[n] then return quoted[n]
|
||||
else return t end
|
||||
end
|
||||
|
||||
local function parse_set_cookie(c, quoted, cookie_table)
|
||||
c = c .. ";$last=last;"
|
||||
local _, _, n, v, i = string.find(c, "(" .. token_class ..
|
||||
"+)%s*=%s*(.-)%s*;%s*()")
|
||||
local cookie = {
|
||||
name = n,
|
||||
value = unquote(v, quoted),
|
||||
attributes = {}
|
||||
}
|
||||
while 1 do
|
||||
_, _, n, v, i = string.find(c, "(" .. token_class ..
|
||||
"+)%s*=?%s*(.-)%s*;%s*()", i)
|
||||
if not n or n == "$last" then break end
|
||||
cookie.attributes[#cookie.attributes+1] = {
|
||||
name = n,
|
||||
value = unquote(v, quoted)
|
||||
}
|
||||
end
|
||||
cookie_table[#cookie_table+1] = cookie
|
||||
end
|
||||
|
||||
local function split_set_cookie(s, cookie_table)
|
||||
cookie_table = cookie_table or {}
|
||||
-- remove quoted strings from cookie list
|
||||
local quoted = {}
|
||||
s = string.gsub(s, '"(.-)"', function(q)
|
||||
quoted[#quoted+1] = q
|
||||
return "$" .. #quoted
|
||||
end)
|
||||
-- add sentinel
|
||||
s = s .. ",$last="
|
||||
-- split into individual cookies
|
||||
i = 1
|
||||
while 1 do
|
||||
local _, _, cookie, next_token
|
||||
_, _, cookie, i, next_token = string.find(s, "(.-)%s*%,%s*()(" ..
|
||||
token_class .. "+)%s*=", i)
|
||||
if not next_token then break end
|
||||
parse_set_cookie(cookie, quoted, cookie_table)
|
||||
if next_token == "$last" then break end
|
||||
end
|
||||
return cookie_table
|
||||
end
|
||||
|
||||
local function quote(s)
|
||||
if string.find(s, "[ %,%;]") then return '"' .. s .. '"'
|
||||
else return s end
|
||||
end
|
||||
|
||||
local _empty = {}
|
||||
local function build_cookies(cookies)
|
||||
s = ""
|
||||
for i,v in ipairs(cookies or _empty) do
|
||||
if v.name then
|
||||
s = s .. v.name
|
||||
if v.value and v.value ~= "" then
|
||||
s = s .. '=' .. quote(v.value)
|
||||
end
|
||||
end
|
||||
if v.name and #(v.attributes or _empty) > 0 then s = s .. "; " end
|
||||
for j,u in ipairs(v.attributes or _empty) do
|
||||
if u.name then
|
||||
s = s .. u.name
|
||||
if u.value and u.value ~= "" then
|
||||
s = s .. '=' .. quote(u.value)
|
||||
end
|
||||
end
|
||||
if j < #v.attributes then s = s .. "; " end
|
||||
end
|
||||
if i < #cookies then s = s .. ", " end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
151
samples/dict.lua
Normal file
151
samples/dict.lua
Normal file
|
@ -0,0 +1,151 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program to download DICT word definitions
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Load required modules
|
||||
-----------------------------------------------------------------------------
|
||||
local base = _G
|
||||
local string = require("string")
|
||||
local table = require("table")
|
||||
local socket = require("socket")
|
||||
local url = require("socket.url")
|
||||
local tp = require("socket.tp")
|
||||
module("socket.dict")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Globals
|
||||
-----------------------------------------------------------------------------
|
||||
HOST = "dict.org"
|
||||
PORT = 2628
|
||||
TIMEOUT = 10
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Low-level dict API
|
||||
-----------------------------------------------------------------------------
|
||||
local metat = { __index = {} }
|
||||
|
||||
function open(host, port)
|
||||
local tp = socket.try(tp.connect(host or HOST, port or PORT, TIMEOUT))
|
||||
return base.setmetatable({tp = tp}, metat)
|
||||
end
|
||||
|
||||
function metat.__index:greet()
|
||||
return socket.try(self.tp:check(220))
|
||||
end
|
||||
|
||||
function metat.__index:check(ok)
|
||||
local code, status = socket.try(self.tp:check(ok))
|
||||
return code,
|
||||
base.tonumber(socket.skip(2, string.find(status, "^%d%d%d (%d*)")))
|
||||
end
|
||||
|
||||
function metat.__index:getdef()
|
||||
local line = socket.try(self.tp:receive())
|
||||
local def = {}
|
||||
while line ~= "." do
|
||||
table.insert(def, line)
|
||||
line = socket.try(self.tp:receive())
|
||||
end
|
||||
return table.concat(def, "\n")
|
||||
end
|
||||
|
||||
function metat.__index:define(database, word)
|
||||
database = database or "!"
|
||||
socket.try(self.tp:command("DEFINE", database .. " " .. word))
|
||||
local code, count = self:check(150)
|
||||
local defs = {}
|
||||
for i = 1, count do
|
||||
self:check(151)
|
||||
table.insert(defs, self:getdef())
|
||||
end
|
||||
self:check(250)
|
||||
return defs
|
||||
end
|
||||
|
||||
function metat.__index:match(database, strat, word)
|
||||
database = database or "!"
|
||||
strat = strat or "."
|
||||
socket.try(self.tp:command("MATCH", database .." ".. strat .." ".. word))
|
||||
self:check(152)
|
||||
local mat = {}
|
||||
local line = socket.try(self.tp:receive())
|
||||
while line ~= '.' do
|
||||
database, word = socket.skip(2, string.find(line, "(%S+) (.*)"))
|
||||
if not mat[database] then mat[database] = {} end
|
||||
table.insert(mat[database], word)
|
||||
line = socket.try(self.tp:receive())
|
||||
end
|
||||
self:check(250)
|
||||
return mat
|
||||
end
|
||||
|
||||
function metat.__index:quit()
|
||||
self.tp:command("QUIT")
|
||||
return self:check(221)
|
||||
end
|
||||
|
||||
function metat.__index:close()
|
||||
return self.tp:close()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- High-level dict API
|
||||
-----------------------------------------------------------------------------
|
||||
local default = {
|
||||
scheme = "dict",
|
||||
host = "dict.org"
|
||||
}
|
||||
|
||||
local function there(f)
|
||||
if f == "" then return nil
|
||||
else return f end
|
||||
end
|
||||
|
||||
local function parse(u)
|
||||
local t = socket.try(url.parse(u, default))
|
||||
socket.try(t.scheme == "dict", "invalid scheme '" .. t.scheme .. "'")
|
||||
socket.try(t.path, "invalid path in url")
|
||||
local cmd, arg = socket.skip(2, string.find(t.path, "^/(.)(.*)$"))
|
||||
socket.try(cmd == "d" or cmd == "m", "<command> should be 'm' or 'd'")
|
||||
socket.try(arg and arg ~= "", "need at least <word> in URL")
|
||||
t.command, t.argument = cmd, arg
|
||||
arg = string.gsub(arg, "^:([^:]+)", function(f) t.word = f end)
|
||||
socket.try(t.word, "need at least <word> in URL")
|
||||
arg = string.gsub(arg, "^:([^:]*)", function(f) t.database = there(f) end)
|
||||
if cmd == "m" then
|
||||
arg = string.gsub(arg, "^:([^:]*)", function(f) t.strat = there(f) end)
|
||||
end
|
||||
string.gsub(arg, ":([^:]*)$", function(f) t.n = base.tonumber(f) end)
|
||||
return t
|
||||
end
|
||||
|
||||
local function tget(gett)
|
||||
local con = open(gett.host, gett.port)
|
||||
con:greet()
|
||||
if gett.command == "d" then
|
||||
local def = con:define(gett.database, gett.word)
|
||||
con:quit()
|
||||
con:close()
|
||||
if gett.n then return def[gett.n]
|
||||
else return def end
|
||||
elseif gett.command == "m" then
|
||||
local mat = con:match(gett.database, gett.strat, gett.word)
|
||||
con:quit()
|
||||
con:close()
|
||||
return mat
|
||||
else return nil, "invalid command" end
|
||||
end
|
||||
|
||||
local function sget(u)
|
||||
local gett = parse(u)
|
||||
return tget(gett)
|
||||
end
|
||||
|
||||
get = socket.protect(function(gett)
|
||||
if base.type(gett) == "string" then return sget(gett)
|
||||
else return tget(gett) end
|
||||
end)
|
||||
|
307
samples/dispatch.lua
Normal file
307
samples/dispatch.lua
Normal file
|
@ -0,0 +1,307 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- A hacked dispatcher module
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local base = _G
|
||||
local table = require("table")
|
||||
local string = require("string")
|
||||
local socket = require("socket")
|
||||
local coroutine = require("coroutine")
|
||||
module("dispatch")
|
||||
|
||||
-- if too much time goes by without any activity in one of our sockets, we
|
||||
-- just kill it
|
||||
TIMEOUT = 60
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- We implement 3 types of dispatchers:
|
||||
-- sequential
|
||||
-- coroutine
|
||||
-- threaded
|
||||
-- The user can choose whatever one is needed
|
||||
-----------------------------------------------------------------------------
|
||||
local handlert = {}
|
||||
|
||||
-- default handler is coroutine
|
||||
function newhandler(mode)
|
||||
mode = mode or "coroutine"
|
||||
return handlert[mode]()
|
||||
end
|
||||
|
||||
local function seqstart(self, func)
|
||||
return func()
|
||||
end
|
||||
|
||||
-- sequential handler simply calls the functions and doesn't wrap I/O
|
||||
function handlert.sequential()
|
||||
return {
|
||||
tcp = socket.tcp,
|
||||
start = seqstart
|
||||
}
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Mega hack. Don't try to do this at home.
|
||||
-----------------------------------------------------------------------------
|
||||
-- we can't yield across calls to protect on Lua 5.1, so we rewrite it with
|
||||
-- coroutines
|
||||
-- make sure you don't require any module that uses socket.protect before
|
||||
-- loading our hack
|
||||
if string.sub(base._VERSION, -3) == "5.1" then
|
||||
local function _protect(co, status, ...)
|
||||
if not status then
|
||||
local msg = ...
|
||||
if base.type(msg) == 'table' then
|
||||
return nil, msg[1]
|
||||
else
|
||||
base.error(msg, 0)
|
||||
end
|
||||
end
|
||||
if coroutine.status(co) == "suspended" then
|
||||
return _protect(co, coroutine.resume(co, coroutine.yield(...)))
|
||||
else
|
||||
return ...
|
||||
end
|
||||
end
|
||||
|
||||
function socket.protect(f)
|
||||
return function(...)
|
||||
local co = coroutine.create(f)
|
||||
return _protect(co, coroutine.resume(co, ...))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Simple set data structure. O(1) everything.
|
||||
-----------------------------------------------------------------------------
|
||||
local function newset()
|
||||
local reverse = {}
|
||||
local set = {}
|
||||
return base.setmetatable(set, {__index = {
|
||||
insert = function(set, value)
|
||||
if not reverse[value] then
|
||||
table.insert(set, value)
|
||||
reverse[value] = #set
|
||||
end
|
||||
end,
|
||||
remove = function(set, value)
|
||||
local index = reverse[value]
|
||||
if index then
|
||||
reverse[value] = nil
|
||||
local top = table.remove(set)
|
||||
if top ~= value then
|
||||
reverse[top] = index
|
||||
set[index] = top
|
||||
end
|
||||
end
|
||||
end
|
||||
}})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- socket.tcp() wrapper for the coroutine dispatcher
|
||||
-----------------------------------------------------------------------------
|
||||
local function cowrap(dispatcher, tcp, error)
|
||||
if not tcp then return nil, error end
|
||||
-- put it in non-blocking mode right away
|
||||
tcp:settimeout(0)
|
||||
-- metatable for wrap produces new methods on demand for those that we
|
||||
-- don't override explicitly.
|
||||
local metat = { __index = function(table, key)
|
||||
table[key] = function(...)
|
||||
return tcp[key](tcp,select(2,...))
|
||||
end
|
||||
return table[key]
|
||||
end}
|
||||
-- does our user want to do his own non-blocking I/O?
|
||||
local zero = false
|
||||
-- create a wrap object that will behave just like a real socket object
|
||||
local wrap = { }
|
||||
-- we ignore settimeout to preserve our 0 timeout, but record whether
|
||||
-- the user wants to do his own non-blocking I/O
|
||||
function wrap:settimeout(value, mode)
|
||||
if value == 0 then zero = true
|
||||
else zero = false end
|
||||
return 1
|
||||
end
|
||||
-- send in non-blocking mode and yield on timeout
|
||||
function wrap:send(data, first, last)
|
||||
first = (first or 1) - 1
|
||||
local result, error
|
||||
while true do
|
||||
-- return control to dispatcher and tell it we want to send
|
||||
-- if upon return the dispatcher tells us we timed out,
|
||||
-- return an error to whoever called us
|
||||
if coroutine.yield(dispatcher.sending, tcp) == "timeout" then
|
||||
return nil, "timeout"
|
||||
end
|
||||
-- try sending
|
||||
result, error, first = tcp:send(data, first+1, last)
|
||||
-- if we are done, or there was an unexpected error,
|
||||
-- break away from loop
|
||||
if error ~= "timeout" then return result, error, first end
|
||||
end
|
||||
end
|
||||
-- receive in non-blocking mode and yield on timeout
|
||||
-- or simply return partial read, if user requested timeout = 0
|
||||
function wrap:receive(pattern, partial)
|
||||
local error = "timeout"
|
||||
local value
|
||||
while true do
|
||||
-- return control to dispatcher and tell it we want to receive
|
||||
-- if upon return the dispatcher tells us we timed out,
|
||||
-- return an error to whoever called us
|
||||
if coroutine.yield(dispatcher.receiving, tcp) == "timeout" then
|
||||
return nil, "timeout"
|
||||
end
|
||||
-- try receiving
|
||||
value, error, partial = tcp:receive(pattern, partial)
|
||||
-- if we are done, or there was an unexpected error,
|
||||
-- break away from loop. also, if the user requested
|
||||
-- zero timeout, return all we got
|
||||
if (error ~= "timeout") or zero then
|
||||
return value, error, partial
|
||||
end
|
||||
end
|
||||
end
|
||||
-- connect in non-blocking mode and yield on timeout
|
||||
function wrap:connect(host, port)
|
||||
local result, error = tcp:connect(host, port)
|
||||
if error == "timeout" then
|
||||
-- return control to dispatcher. we will be writable when
|
||||
-- connection succeeds.
|
||||
-- if upon return the dispatcher tells us we have a
|
||||
-- timeout, just abort
|
||||
if coroutine.yield(dispatcher.sending, tcp) == "timeout" then
|
||||
return nil, "timeout"
|
||||
end
|
||||
-- when we come back, check if connection was successful
|
||||
result, error = tcp:connect(host, port)
|
||||
if result or error == "already connected" then return 1
|
||||
else return nil, "non-blocking connect failed" end
|
||||
else return result, error end
|
||||
end
|
||||
-- accept in non-blocking mode and yield on timeout
|
||||
function wrap:accept()
|
||||
while 1 do
|
||||
-- return control to dispatcher. we will be readable when a
|
||||
-- connection arrives.
|
||||
-- if upon return the dispatcher tells us we have a
|
||||
-- timeout, just abort
|
||||
if coroutine.yield(dispatcher.receiving, tcp) == "timeout" then
|
||||
return nil, "timeout"
|
||||
end
|
||||
local client, error = tcp:accept()
|
||||
if error ~= "timeout" then
|
||||
return cowrap(dispatcher, client, error)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- remove cortn from context
|
||||
function wrap:close()
|
||||
dispatcher.stamp[tcp] = nil
|
||||
dispatcher.sending.set:remove(tcp)
|
||||
dispatcher.sending.cortn[tcp] = nil
|
||||
dispatcher.receiving.set:remove(tcp)
|
||||
dispatcher.receiving.cortn[tcp] = nil
|
||||
return tcp:close()
|
||||
end
|
||||
return base.setmetatable(wrap, metat)
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Our coroutine dispatcher
|
||||
-----------------------------------------------------------------------------
|
||||
local cometat = { __index = {} }
|
||||
|
||||
function schedule(cortn, status, operation, tcp)
|
||||
if status then
|
||||
if cortn and operation then
|
||||
operation.set:insert(tcp)
|
||||
operation.cortn[tcp] = cortn
|
||||
operation.stamp[tcp] = socket.gettime()
|
||||
end
|
||||
else base.error(operation) end
|
||||
end
|
||||
|
||||
function kick(operation, tcp)
|
||||
operation.cortn[tcp] = nil
|
||||
operation.set:remove(tcp)
|
||||
end
|
||||
|
||||
function wakeup(operation, tcp)
|
||||
local cortn = operation.cortn[tcp]
|
||||
-- if cortn is still valid, wake it up
|
||||
if cortn then
|
||||
kick(operation, tcp)
|
||||
return cortn, coroutine.resume(cortn)
|
||||
-- othrewise, just get scheduler not to do anything
|
||||
else
|
||||
return nil, true
|
||||
end
|
||||
end
|
||||
|
||||
function abort(operation, tcp)
|
||||
local cortn = operation.cortn[tcp]
|
||||
if cortn then
|
||||
kick(operation, tcp)
|
||||
coroutine.resume(cortn, "timeout")
|
||||
end
|
||||
end
|
||||
|
||||
-- step through all active cortns
|
||||
function cometat.__index:step()
|
||||
-- check which sockets are interesting and act on them
|
||||
local readable, writable = socket.select(self.receiving.set,
|
||||
self.sending.set, 1)
|
||||
-- for all readable connections, resume their cortns and reschedule
|
||||
-- when they yield back to us
|
||||
for _, tcp in base.ipairs(readable) do
|
||||
schedule(wakeup(self.receiving, tcp))
|
||||
end
|
||||
-- for all writable connections, do the same
|
||||
for _, tcp in base.ipairs(writable) do
|
||||
schedule(wakeup(self.sending, tcp))
|
||||
end
|
||||
-- politely ask replacement I/O functions in idle cortns to
|
||||
-- return reporting a timeout
|
||||
local now = socket.gettime()
|
||||
for tcp, stamp in base.pairs(self.stamp) do
|
||||
if tcp.class == "tcp{client}" and now - stamp > TIMEOUT then
|
||||
abort(self.sending, tcp)
|
||||
abort(self.receiving, tcp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cometat.__index:start(func)
|
||||
local cortn = coroutine.create(func)
|
||||
schedule(cortn, coroutine.resume(cortn))
|
||||
end
|
||||
|
||||
function handlert.coroutine()
|
||||
local stamp = {}
|
||||
local dispatcher = {
|
||||
stamp = stamp,
|
||||
sending = {
|
||||
name = "sending",
|
||||
set = newset(),
|
||||
cortn = {},
|
||||
stamp = stamp
|
||||
},
|
||||
receiving = {
|
||||
name = "receiving",
|
||||
set = newset(),
|
||||
cortn = {},
|
||||
stamp = stamp
|
||||
},
|
||||
}
|
||||
function dispatcher.tcp()
|
||||
return cowrap(dispatcher, socket.tcp())
|
||||
end
|
||||
return base.setmetatable(dispatcher, cometat)
|
||||
end
|
||||
|
13
samples/eol.lua
Normal file
13
samples/eol.lua
Normal file
|
@ -0,0 +1,13 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program to adjust end of line markers.
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local mime = require("mime")
|
||||
local ltn12 = require("ltn12")
|
||||
local marker = '\n'
|
||||
if arg and arg[1] == '-d' then marker = '\r\n' end
|
||||
local filter = mime.normalize(marker)
|
||||
local source = ltn12.source.chain(ltn12.source.file(io.stdin), filter)
|
||||
local sink = ltn12.sink.file(io.stdout)
|
||||
ltn12.pump.all(source, sink)
|
65
samples/forward.lua
Normal file
65
samples/forward.lua
Normal file
|
@ -0,0 +1,65 @@
|
|||
-- load our favourite library
|
||||
local dispatch = require("dispatch")
|
||||
local handler = dispatch.newhandler()
|
||||
|
||||
-- make sure the user knows how to invoke us
|
||||
if #arg < 1 then
|
||||
print("Usage")
|
||||
print(" lua forward.lua <iport:ohost:oport> ...")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
-- function to move data from one socket to the other
|
||||
local function move(foo, bar)
|
||||
local live
|
||||
while 1 do
|
||||
local data, error, partial = foo:receive(2048)
|
||||
live = data or error == "timeout"
|
||||
data = data or partial
|
||||
local result, error = bar:send(data)
|
||||
if not live or not result then
|
||||
foo:close()
|
||||
bar:close()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- for each tunnel, start a new server
|
||||
for i, v in ipairs(arg) do
|
||||
-- capture forwarding parameters
|
||||
local _, _, iport, ohost, oport = string.find(v, "([^:]+):([^:]+):([^:]+)")
|
||||
assert(iport, "invalid arguments")
|
||||
-- create our server socket
|
||||
local server = assert(handler.tcp())
|
||||
assert(server:setoption("reuseaddr", true))
|
||||
assert(server:bind("*", iport))
|
||||
assert(server:listen(32))
|
||||
-- handler for the server object loops accepting new connections
|
||||
handler:start(function()
|
||||
while 1 do
|
||||
local client = assert(server:accept())
|
||||
assert(client:settimeout(0))
|
||||
-- for each new connection, start a new client handler
|
||||
handler:start(function()
|
||||
-- handler tries to connect to peer
|
||||
local peer = assert(handler.tcp())
|
||||
assert(peer:settimeout(0))
|
||||
assert(peer:connect(ohost, oport))
|
||||
-- if sucessful, starts a new handler to send data from
|
||||
-- client to peer
|
||||
handler:start(function()
|
||||
move(client, peer)
|
||||
end)
|
||||
-- afte starting new handler, enter in loop sending data from
|
||||
-- peer to client
|
||||
move(peer, client)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- simply loop stepping the server
|
||||
while 1 do
|
||||
handler:step()
|
||||
end
|
141
samples/get.lua
Normal file
141
samples/get.lua
Normal file
|
@ -0,0 +1,141 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program to download files from URLs
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local socket = require("socket")
|
||||
local http = require("socket.http")
|
||||
local ftp = require("socket.ftp")
|
||||
local url = require("socket.url")
|
||||
local ltn12 = require("ltn12")
|
||||
|
||||
-- formats a number of seconds into human readable form
|
||||
function nicetime(s)
|
||||
local l = "s"
|
||||
if s > 60 then
|
||||
s = s / 60
|
||||
l = "m"
|
||||
if s > 60 then
|
||||
s = s / 60
|
||||
l = "h"
|
||||
if s > 24 then
|
||||
s = s / 24
|
||||
l = "d" -- hmmm
|
||||
end
|
||||
end
|
||||
end
|
||||
if l == "s" then return string.format("%5.0f%s", s, l)
|
||||
else return string.format("%5.2f%s", s, l) end
|
||||
end
|
||||
|
||||
-- formats a number of bytes into human readable form
|
||||
function nicesize(b)
|
||||
local l = "B"
|
||||
if b > 1024 then
|
||||
b = b / 1024
|
||||
l = "KB"
|
||||
if b > 1024 then
|
||||
b = b / 1024
|
||||
l = "MB"
|
||||
if b > 1024 then
|
||||
b = b / 1024
|
||||
l = "GB" -- hmmm
|
||||
end
|
||||
end
|
||||
end
|
||||
return string.format("%7.2f%2s", b, l)
|
||||
end
|
||||
|
||||
-- returns a string with the current state of the download
|
||||
local remaining_s = "%s received, %s/s throughput, %2.0f%% done, %s remaining"
|
||||
local elapsed_s = "%s received, %s/s throughput, %s elapsed "
|
||||
function gauge(got, delta, size)
|
||||
local rate = got / delta
|
||||
if size and size >= 1 then
|
||||
return string.format(remaining_s, nicesize(got), nicesize(rate),
|
||||
100*got/size, nicetime((size-got)/rate))
|
||||
else
|
||||
return string.format(elapsed_s, nicesize(got),
|
||||
nicesize(rate), nicetime(delta))
|
||||
end
|
||||
end
|
||||
|
||||
-- creates a new instance of a receive_cb that saves to disk
|
||||
-- kind of copied from luasocket's manual callback examples
|
||||
function stats(size)
|
||||
local start = socket.gettime()
|
||||
local last = start
|
||||
local got = 0
|
||||
return function(chunk)
|
||||
-- elapsed time since start
|
||||
local current = socket.gettime()
|
||||
if chunk then
|
||||
-- total bytes received
|
||||
got = got + string.len(chunk)
|
||||
-- not enough time for estimate
|
||||
if current - last > 1 then
|
||||
io.stderr:write("\r", gauge(got, current - start, size))
|
||||
io.stderr:flush()
|
||||
last = current
|
||||
end
|
||||
else
|
||||
-- close up
|
||||
io.stderr:write("\r", gauge(got, current - start), "\n")
|
||||
end
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
|
||||
-- determines the size of a http file
|
||||
function gethttpsize(u)
|
||||
local r, c, h = http.request {method = "HEAD", url = u}
|
||||
if c == 200 then
|
||||
return tonumber(h["content-length"])
|
||||
end
|
||||
end
|
||||
|
||||
-- downloads a file using the http protocol
|
||||
function getbyhttp(u, file)
|
||||
local save = ltn12.sink.file(file or io.stdout)
|
||||
-- only print feedback if output is not stdout
|
||||
if file then save = ltn12.sink.chain(stats(gethttpsize(u)), save) end
|
||||
local r, c, h, s = http.request {url = u, sink = save }
|
||||
if c ~= 200 then io.stderr:write(s or c, "\n") end
|
||||
end
|
||||
|
||||
-- downloads a file using the ftp protocol
|
||||
function getbyftp(u, file)
|
||||
local save = ltn12.sink.file(file or io.stdout)
|
||||
-- only print feedback if output is not stdout
|
||||
-- and we don't know how big the file is
|
||||
if file then save = ltn12.sink.chain(stats(), save) end
|
||||
local gett = url.parse(u)
|
||||
gett.sink = save
|
||||
gett.type = "i"
|
||||
local ret, err = ftp.get(gett)
|
||||
if err then print(err) end
|
||||
end
|
||||
|
||||
-- determines the scheme
|
||||
function getscheme(u)
|
||||
-- this is an heuristic to solve a common invalid url poblem
|
||||
if not string.find(u, "//") then u = "//" .. u end
|
||||
local parsed = url.parse(u, {scheme = "http"})
|
||||
return parsed.scheme
|
||||
end
|
||||
|
||||
-- gets a file either by http or ftp, saving as <name>
|
||||
function get(u, name)
|
||||
local fout = name and io.open(name, "wb")
|
||||
local scheme = getscheme(u)
|
||||
if scheme == "ftp" then getbyftp(u, fout)
|
||||
elseif scheme == "http" then getbyhttp(u, fout)
|
||||
else print("unknown scheme" .. scheme) end
|
||||
end
|
||||
|
||||
-- main program
|
||||
arg = arg or {}
|
||||
if #arg < 1 then
|
||||
io.write("Usage:\n lua get.lua <remote-url> [<local-file>]\n")
|
||||
os.exit(1)
|
||||
else get(arg[1], arg[2]) end
|
17
samples/links
Normal file
17
samples/links
Normal file
|
@ -0,0 +1,17 @@
|
|||
<a href="http://www.cs.princeton.edu"> bla </a>
|
||||
<a href="http://www.princeton.edu"> bla </a>
|
||||
<a href="http://www.tecgraf.puc-rio.br"> bla </a>
|
||||
<a href="http://www.inf.puc-rio.br"> bla </a>
|
||||
<a href="http://www.puc-rio.br"> bla </a>
|
||||
<a href="http://www.impa.br"> bla </a>
|
||||
<a href="http://www.lua.org"> bla </a>
|
||||
<a href="http://www.lua-users.org"> bla </a>
|
||||
<a href="http://www.amazon.com"> bla </a>
|
||||
<a href="http://www.google.com"> bla </a>
|
||||
<a href="http://www.nytimes.com"> bla </a>
|
||||
<a href="http://www.bbc.co.uk"> bla </a>
|
||||
<a href="http://oglobo.globo.com"> bla </a>
|
||||
<a href="http://slate.msn.com"> bla </a>
|
||||
<a href="http://www.apple.com"> bla </a>
|
||||
<a href="http://www.microsoft.com"> bla </a>
|
||||
<a href="http://www.nasa.gov"> bla </a>
|
323
samples/lp.lua
Normal file
323
samples/lp.lua
Normal file
|
@ -0,0 +1,323 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- LPD support for the Lua language
|
||||
-- LuaSocket toolkit.
|
||||
-- Author: David Burgess
|
||||
-- Modified by Diego Nehab, but David is in charge
|
||||
-----------------------------------------------------------------------------
|
||||
--[[
|
||||
if you have any questions: RFC 1179
|
||||
]]
|
||||
-- make sure LuaSocket is loaded
|
||||
local io = require("io")
|
||||
local base = _G
|
||||
local os = require("os")
|
||||
local math = require("math")
|
||||
local string = require("string")
|
||||
local socket = require("socket")
|
||||
local ltn12 = require("ltn12")
|
||||
module("socket.lp")
|
||||
|
||||
-- default port
|
||||
PORT = 515
|
||||
SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
|
||||
PRINTER = os.getenv("PRINTER") or "printer"
|
||||
|
||||
local function connect(localhost, option)
|
||||
local host = option.host or SERVER
|
||||
local port = option.port or PORT
|
||||
local skt
|
||||
local try = socket.newtry(function() if skt then skt:close() end end)
|
||||
if option.localbind then
|
||||
-- bind to a local port (if we can)
|
||||
local localport = 721
|
||||
local done, err
|
||||
repeat
|
||||
skt = socket.try(socket.tcp())
|
||||
try(skt:settimeout(30))
|
||||
done, err = skt:bind(localhost, localport)
|
||||
if not done then
|
||||
localport = localport + 1
|
||||
skt:close()
|
||||
skt = nil
|
||||
else break end
|
||||
until localport > 731
|
||||
socket.try(skt, err)
|
||||
else skt = socket.try(socket.tcp()) end
|
||||
try(skt:connect(host, port))
|
||||
return { skt = skt, try = try }
|
||||
end
|
||||
|
||||
--[[
|
||||
RFC 1179
|
||||
5.3 03 - Send queue state (short)
|
||||
|
||||
+----+-------+----+------+----+
|
||||
| 03 | Queue | SP | List | LF |
|
||||
+----+-------+----+------+----+
|
||||
Command code - 3
|
||||
Operand 1 - Printer queue name
|
||||
Other operands - User names or job numbers
|
||||
|
||||
If the user names or job numbers or both are supplied then only those
|
||||
jobs for those users or with those numbers will be sent.
|
||||
|
||||
The response is an ASCII stream which describes the printer queue.
|
||||
The stream continues until the connection closes. Ends of lines are
|
||||
indicated with ASCII LF control characters. The lines may also
|
||||
contain ASCII HT control characters.
|
||||
|
||||
5.4 04 - Send queue state (long)
|
||||
|
||||
+----+-------+----+------+----+
|
||||
| 04 | Queue | SP | List | LF |
|
||||
+----+-------+----+------+----+
|
||||
Command code - 4
|
||||
Operand 1 - Printer queue name
|
||||
Other operands - User names or job numbers
|
||||
|
||||
If the user names or job numbers or both are supplied then only those
|
||||
jobs for those users or with those numbers will be sent.
|
||||
|
||||
The response is an ASCII stream which describes the printer queue.
|
||||
The stream continues until the connection closes. Ends of lines are
|
||||
indicated with ASCII LF control characters. The lines may also
|
||||
contain ASCII HT control characters.
|
||||
]]
|
||||
|
||||
-- gets server acknowledement
|
||||
local function recv_ack(con)
|
||||
local ack = con.skt:receive(1)
|
||||
con.try(string.char(0) == ack, "failed to receive server acknowledgement")
|
||||
end
|
||||
|
||||
-- sends client acknowledement
|
||||
local function send_ack(con)
|
||||
local sent = con.skt:send(string.char(0))
|
||||
con.try(sent == 1, "failed to send acknowledgement")
|
||||
end
|
||||
|
||||
-- sends queue request
|
||||
-- 5.2 02 - Receive a printer job
|
||||
--
|
||||
-- +----+-------+----+
|
||||
-- | 02 | Queue | LF |
|
||||
-- +----+-------+----+
|
||||
-- Command code - 2
|
||||
-- Operand - Printer queue name
|
||||
--
|
||||
-- Receiving a job is controlled by a second level of commands. The
|
||||
-- daemon is given commands by sending them over the same connection.
|
||||
-- The commands are described in the next section (6).
|
||||
--
|
||||
-- After this command is sent, the client must read an acknowledgement
|
||||
-- octet from the daemon. A positive acknowledgement is an octet of
|
||||
-- zero bits. A negative acknowledgement is an octet of any other
|
||||
-- pattern.
|
||||
local function send_queue(con, queue)
|
||||
queue = queue or PRINTER
|
||||
local str = string.format("\2%s\10", queue)
|
||||
local sent = con.skt:send(str)
|
||||
con.try(sent == string.len(str), "failed to send print request")
|
||||
recv_ack(con)
|
||||
end
|
||||
|
||||
-- sends control file
|
||||
-- 6.2 02 - Receive control file
|
||||
--
|
||||
-- +----+-------+----+------+----+
|
||||
-- | 02 | Count | SP | Name | LF |
|
||||
-- +----+-------+----+------+----+
|
||||
-- Command code - 2
|
||||
-- Operand 1 - Number of bytes in control file
|
||||
-- Operand 2 - Name of control file
|
||||
--
|
||||
-- The control file must be an ASCII stream with the ends of lines
|
||||
-- indicated by ASCII LF. The total number of bytes in the stream is
|
||||
-- sent as the first operand. The name of the control file is sent as
|
||||
-- the second. It should start with ASCII "cfA", followed by a three
|
||||
-- digit job number, followed by the host name which has constructed the
|
||||
-- control file. Acknowledgement processing must occur as usual after
|
||||
-- the command is sent.
|
||||
--
|
||||
-- The next "Operand 1" octets over the same TCP connection are the
|
||||
-- intended contents of the control file. Once all of the contents have
|
||||
-- been delivered, an octet of zero bits is sent as an indication that
|
||||
-- the file being sent is complete. A second level of acknowledgement
|
||||
-- processing must occur at this point.
|
||||
|
||||
-- sends data file
|
||||
-- 6.3 03 - Receive data file
|
||||
--
|
||||
-- +----+-------+----+------+----+
|
||||
-- | 03 | Count | SP | Name | LF |
|
||||
-- +----+-------+----+------+----+
|
||||
-- Command code - 3
|
||||
-- Operand 1 - Number of bytes in data file
|
||||
-- Operand 2 - Name of data file
|
||||
--
|
||||
-- The data file may contain any 8 bit values at all. The total number
|
||||
-- of bytes in the stream may be sent as the first operand, otherwise
|
||||
-- the field should be cleared to 0. The name of the data file should
|
||||
-- start with ASCII "dfA". This should be followed by a three digit job
|
||||
-- number. The job number should be followed by the host name which has
|
||||
-- constructed the data file. Interpretation of the contents of the
|
||||
-- data file is determined by the contents of the corresponding control
|
||||
-- file. If a data file length has been specified, the next "Operand 1"
|
||||
-- octets over the same TCP connection are the intended contents of the
|
||||
-- data file. In this case, once all of the contents have been
|
||||
-- delivered, an octet of zero bits is sent as an indication that the
|
||||
-- file being sent is complete. A second level of acknowledgement
|
||||
-- processing must occur at this point.
|
||||
|
||||
|
||||
local function send_hdr(con, control)
|
||||
local sent = con.skt:send(control)
|
||||
con.try(sent and sent >= 1 , "failed to send header file")
|
||||
recv_ack(con)
|
||||
end
|
||||
|
||||
local function send_control(con, control)
|
||||
local sent = con.skt:send(control)
|
||||
con.try(sent and sent >= 1, "failed to send control file")
|
||||
send_ack(con)
|
||||
end
|
||||
|
||||
local function send_data(con,fh,size)
|
||||
local buf
|
||||
while size > 0 do
|
||||
buf,message = fh:read(8192)
|
||||
if buf then
|
||||
st = con.try(con.skt:send(buf))
|
||||
size = size - st
|
||||
else
|
||||
con.try(size == 0, "file size mismatch")
|
||||
end
|
||||
end
|
||||
recv_ack(con) -- note the double acknowledgement
|
||||
send_ack(con)
|
||||
recv_ack(con)
|
||||
return size
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
local control_dflt = {
|
||||
"H"..string.sub(socket.hostname,1,31).."\10", -- host
|
||||
"C"..string.sub(socket.hostname,1,31).."\10", -- class
|
||||
"J"..string.sub(filename,1,99).."\10", -- jobname
|
||||
"L"..string.sub(user,1,31).."\10", -- print banner page
|
||||
"I"..tonumber(indent).."\10", -- indent column count ('f' only)
|
||||
"M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
|
||||
"N"..string.sub(filename,1,131).."\10", -- name of source file
|
||||
"P"..string.sub(user,1,31).."\10", -- user name
|
||||
"T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
|
||||
"W"..tonumber(width or 132).."\10", -- width of print f,l,p only
|
||||
|
||||
"f"..file.."\10", -- formatted print (remove control chars)
|
||||
"l"..file.."\10", -- print
|
||||
"o"..file.."\10", -- postscript
|
||||
"p"..file.."\10", -- pr format - requires T, L
|
||||
"r"..file.."\10", -- fortran format
|
||||
"U"..file.."\10", -- Unlink (data file only)
|
||||
}
|
||||
]]
|
||||
|
||||
-- generate a varying job number
|
||||
local seq = 0
|
||||
local function newjob(connection)
|
||||
seq = seq + 1
|
||||
return math.floor(socket.gettime() * 1000 + seq)%1000
|
||||
end
|
||||
|
||||
|
||||
local format_codes = {
|
||||
binary = 'l',
|
||||
text = 'f',
|
||||
ps = 'o',
|
||||
pr = 'p',
|
||||
fortran = 'r',
|
||||
l = 'l',
|
||||
r = 'r',
|
||||
o = 'o',
|
||||
p = 'p',
|
||||
f = 'f'
|
||||
}
|
||||
|
||||
-- lp.send{option}
|
||||
-- requires option.file
|
||||
|
||||
send = socket.protect(function(option)
|
||||
socket.try(option and base.type(option) == "table", "invalid options")
|
||||
local file = option.file
|
||||
socket.try(file, "invalid file name")
|
||||
local fh = socket.try(io.open(file,"rb"))
|
||||
local datafile_size = fh:seek("end") -- get total size
|
||||
fh:seek("set") -- go back to start of file
|
||||
local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
|
||||
or "localhost"
|
||||
local con = connect(localhost, option)
|
||||
-- format the control file
|
||||
local jobno = newjob()
|
||||
local localip = socket.dns.toip(localhost)
|
||||
localhost = string.sub(localhost,1,31)
|
||||
local user = string.sub(option.user or os.getenv("LPRUSER") or
|
||||
os.getenv("USERNAME") or os.getenv("USER") or "anonymous", 1,31)
|
||||
local lpfile = string.format("dfA%3.3d%-s", jobno, localhost);
|
||||
local fmt = format_codes[option.format] or 'l'
|
||||
local class = string.sub(option.class or localip or localhost,1,31)
|
||||
local _,_,ctlfn = string.find(file,".*[%/%\\](.*)")
|
||||
ctlfn = string.sub(ctlfn or file,1,131)
|
||||
local cfile =
|
||||
string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
|
||||
localhost,
|
||||
class,
|
||||
option.job or "LuaSocket",
|
||||
user,
|
||||
fmt, lpfile,
|
||||
lpfile,
|
||||
ctlfn); -- mandatory part of ctl file
|
||||
if (option.banner) then cfile = cfile .. 'L'..user..'\10' end
|
||||
if (option.indent) then cfile = cfile .. 'I'..base.tonumber(option.indent)..'\10' end
|
||||
if (option.mail) then cfile = cfile .. 'M'..string.sub((option.mail),1,128)..'\10' end
|
||||
if (fmt == 'p' and option.title) then cfile = cfile .. 'T'..string.sub((option.title),1,79)..'\10' end
|
||||
if ((fmt == 'p' or fmt == 'l' or fmt == 'f') and option.width) then
|
||||
cfile = cfile .. 'W'..base.tonumber(option,width)..'\10'
|
||||
end
|
||||
|
||||
con.skt:settimeout(option.timeout or 65)
|
||||
-- send the queue header
|
||||
send_queue(con, option.queue)
|
||||
-- send the control file header
|
||||
local cfilecmd = string.format("\2%d cfA%3.3d%-s\n",string.len(cfile), jobno, localhost);
|
||||
send_hdr(con,cfilecmd)
|
||||
|
||||
-- send the control file
|
||||
send_control(con,cfile)
|
||||
|
||||
-- send the data file header
|
||||
local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
|
||||
send_hdr(con,dfilecmd)
|
||||
|
||||
-- send the data file
|
||||
send_data(con,fh,datafile_size)
|
||||
fh:close()
|
||||
con.skt:close();
|
||||
return jobno, datafile_size
|
||||
end)
|
||||
|
||||
--
|
||||
-- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
|
||||
--
|
||||
query = socket.protect(function(p)
|
||||
p = p or {}
|
||||
local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
|
||||
or "localhost"
|
||||
local con = connect(localhost,p)
|
||||
local fmt
|
||||
if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
|
||||
con.try(con.skt:send(string.format("%c%s %s\n", fmt, p.queue or "*",
|
||||
p.list or "")))
|
||||
local data = con.try(con.skt:receive("*a"))
|
||||
con.skt:close()
|
||||
return data
|
||||
end)
|
23
samples/qp.lua
Normal file
23
samples/qp.lua
Normal file
|
@ -0,0 +1,23 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Little program to convert to and from Quoted-Printable
|
||||
-- LuaSocket sample files
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
local ltn12 = require("ltn12")
|
||||
local mime = require("mime")
|
||||
local convert
|
||||
arg = arg or {}
|
||||
local mode = arg and arg[1] or "-et"
|
||||
if mode == "-et" then
|
||||
local normalize = mime.normalize()
|
||||
local qp = mime.encode("quoted-printable")
|
||||
local wrap = mime.wrap("quoted-printable")
|
||||
convert = ltn12.filter.chain(normalize, qp, wrap)
|
||||
elseif mode == "-eb" then
|
||||
local qp = mime.encode("quoted-printable", "binary")
|
||||
local wrap = mime.wrap("quoted-printable")
|
||||
convert = ltn12.filter.chain(qp, wrap)
|
||||
else convert = mime.decode("quoted-printable") end
|
||||
local source = ltn12.source.chain(ltn12.source.file(io.stdin), convert)
|
||||
local sink = ltn12.sink.file(io.stdout)
|
||||
ltn12.pump.all(source, sink)
|
154
samples/tftp.lua
Normal file
154
samples/tftp.lua
Normal file
|
@ -0,0 +1,154 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- TFTP support for the Lua language
|
||||
-- LuaSocket toolkit.
|
||||
-- Author: Diego Nehab
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Load required files
|
||||
-----------------------------------------------------------------------------
|
||||
local base = _G
|
||||
local table = require("table")
|
||||
local math = require("math")
|
||||
local string = require("string")
|
||||
local socket = require("socket")
|
||||
local ltn12 = require("ltn12")
|
||||
local url = require("socket.url")
|
||||
module("socket.tftp")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Program constants
|
||||
-----------------------------------------------------------------------------
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
|
||||
PORT = 69
|
||||
local OP_RRQ = 1
|
||||
local OP_WRQ = 2
|
||||
local OP_DATA = 3
|
||||
local OP_ACK = 4
|
||||
local OP_ERROR = 5
|
||||
local OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Packet creation functions
|
||||
-----------------------------------------------------------------------------
|
||||
local function RRQ(source, mode)
|
||||
return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
|
||||
end
|
||||
|
||||
local function WRQ(source, mode)
|
||||
return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
|
||||
end
|
||||
|
||||
local function ACK(block)
|
||||
local low, high
|
||||
low = math.mod(block, 256)
|
||||
high = (block - low)/256
|
||||
return char(0, OP_ACK, high, low)
|
||||
end
|
||||
|
||||
local function get_OP(dgram)
|
||||
local op = byte(dgram, 1)*256 + byte(dgram, 2)
|
||||
return op
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Packet analysis functions
|
||||
-----------------------------------------------------------------------------
|
||||
local function split_DATA(dgram)
|
||||
local block = byte(dgram, 3)*256 + byte(dgram, 4)
|
||||
local data = string.sub(dgram, 5)
|
||||
return block, data
|
||||
end
|
||||
|
||||
local function get_ERROR(dgram)
|
||||
local code = byte(dgram, 3)*256 + byte(dgram, 4)
|
||||
local msg
|
||||
_,_, msg = string.find(dgram, "(.*)\000", 5)
|
||||
return string.format("error code %d: %s", code, msg)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- The real work
|
||||
-----------------------------------------------------------------------------
|
||||
local function tget(gett)
|
||||
local retries, dgram, sent, datahost, dataport, code
|
||||
local last = 0
|
||||
socket.try(gett.host, "missing host")
|
||||
local con = socket.try(socket.udp())
|
||||
local try = socket.newtry(function() con:close() end)
|
||||
-- convert from name to ip if needed
|
||||
gett.host = try(socket.dns.toip(gett.host))
|
||||
con:settimeout(1)
|
||||
-- first packet gives data host/port to be used for data transfers
|
||||
local path = string.gsub(gett.path or "", "^/", "")
|
||||
path = url.unescape(path)
|
||||
retries = 0
|
||||
repeat
|
||||
sent = try(con:sendto(RRQ(path, "octet"), gett.host, gett.port))
|
||||
dgram, datahost, dataport = con:receivefrom()
|
||||
retries = retries + 1
|
||||
until dgram or datahost ~= "timeout" or retries > 5
|
||||
try(dgram, datahost)
|
||||
-- associate socket with data host/port
|
||||
try(con:setpeername(datahost, dataport))
|
||||
-- default sink
|
||||
local sink = gett.sink or ltn12.sink.null()
|
||||
-- process all data packets
|
||||
while 1 do
|
||||
-- decode packet
|
||||
code = get_OP(dgram)
|
||||
try(code ~= OP_ERROR, get_ERROR(dgram))
|
||||
try(code == OP_DATA, "unhandled opcode " .. code)
|
||||
-- get data packet parts
|
||||
local block, data = split_DATA(dgram)
|
||||
-- if not repeated, write
|
||||
if block == last+1 then
|
||||
try(sink(data))
|
||||
last = block
|
||||
end
|
||||
-- last packet brings less than 512 bytes of data
|
||||
if string.len(data) < 512 then
|
||||
try(con:send(ACK(block)))
|
||||
try(con:close())
|
||||
try(sink(nil))
|
||||
return 1
|
||||
end
|
||||
-- get the next packet
|
||||
retries = 0
|
||||
repeat
|
||||
sent = try(con:send(ACK(last)))
|
||||
dgram, err = con:receive()
|
||||
retries = retries + 1
|
||||
until dgram or err ~= "timeout" or retries > 5
|
||||
try(dgram, err)
|
||||
end
|
||||
end
|
||||
|
||||
local default = {
|
||||
port = PORT,
|
||||
path ="/",
|
||||
scheme = "tftp"
|
||||
}
|
||||
|
||||
local function parse(u)
|
||||
local t = socket.try(url.parse(u, default))
|
||||
socket.try(t.scheme == "tftp", "invalid scheme '" .. t.scheme .. "'")
|
||||
socket.try(t.host, "invalid host")
|
||||
return t
|
||||
end
|
||||
|
||||
local function sget(u)
|
||||
local gett = parse(u)
|
||||
local t = {}
|
||||
gett.sink = ltn12.sink.table(t)
|
||||
tget(gett)
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
get = socket.protect(function(gett)
|
||||
if base.type(gett) == "string" then return sget(gett)
|
||||
else return tget(gett) end
|
||||
end)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue