aboutsummaryrefslogtreecommitdiff
path: root/etc
diff options
context:
space:
mode:
authorCaleb Maclennan <caleb@alerque.com>2023-11-10 09:12:04 +0300
committerCaleb Maclennan <caleb@alerque.com>2023-11-10 09:12:04 +0300
commit5c4fc93d5f4137bf4c22ddf1a048c907a4a26727 (patch)
treea9a68e1f6a9c3bfe2b64fa1c3a4098865b7d3b5d /etc
parentccef3bc4e2aa6ee5b997a80aabb58f4ff0b0e98f (diff)
parent43a97b7f0053313b43906371dbdc226271e6c8ab (diff)
downloadluasocket-hjelmeland-patch-1.tar.gz
luasocket-hjelmeland-patch-1.tar.bz2
luasocket-hjelmeland-patch-1.zip
Merge branch 'master' into hjelmeland-patch-1hjelmeland-patch-1
Diffstat (limited to 'etc')
-rw-r--r--etc/README89
-rw-r--r--etc/b64.lua19
-rw-r--r--etc/check-links.lua111
-rw-r--r--etc/check-memory.lua17
-rw-r--r--etc/cookie.lua88
-rw-r--r--etc/dict.lua151
-rw-r--r--etc/dispatch.lua307
-rw-r--r--etc/eol.lua13
-rw-r--r--etc/forward.lua65
-rw-r--r--etc/get.lua141
-rw-r--r--etc/links17
-rw-r--r--etc/lp.lua323
-rw-r--r--etc/qp.lua23
-rw-r--r--etc/tftp.lua154
14 files changed, 0 insertions, 1518 deletions
diff --git a/etc/README b/etc/README
deleted file mode 100644
index cfd3e37..0000000
--- a/etc/README
+++ /dev/null
@@ -1,89 +0,0 @@
1This directory contains code that is more useful than the
2samples. This code *is* supported.
3
4 tftp.lua -- Trivial FTP client
5
6This module implements file retrieval by the TFTP protocol.
7Its main use was to test the UDP code, but since someone
8found it usefull, I turned it into a module that is almost
9official (no uploads, yet).
10
11 dict.lua -- Dict client
12
13The dict.lua module started with a cool simple client
14for the DICT protocol, written by Luiz Henrique Figueiredo.
15This new version has been converted into a library, similar
16to the HTTP and FTP libraries, that can be used from within
17any luasocket application. Take a look on the source code
18and you will be able to figure out how to use it.
19
20 lp.lua -- LPD client library
21
22The lp.lua module implements the client part of the Line
23Printer Daemon protocol, used to print files on Unix
24machines. It is courtesy of David Burgess! See the source
25code and the lpr.lua in the examples directory.
26
27 b64.lua
28 qp.lua
29 eol.lua
30
31These are tiny programs that perform Base64,
32Quoted-Printable and end-of-line marker conversions.
33
34 get.lua -- file retriever
35
36This little program is a client that uses the FTP and
37HTTP code to implement a command line file graber. Just
38run
39
40 lua get.lua <remote-file> [<local-file>]
41
42to download a remote file (either ftp:// or http://) to
43the specified local file. The program also prints the
44download throughput, elapsed time, bytes already downloaded
45etc during download.
46
47 check-memory.lua -- checks memory consumption
48
49This is just to see how much memory each module uses.
50
51 dispatch.lua -- coroutine based dispatcher
52
53This is a first try at a coroutine based non-blocking
54dispatcher for LuaSocket. Take a look at 'check-links.lua'
55and at 'forward.lua' to see how to use it.
56
57 check-links.lua -- HTML link checker program
58
59This little program scans a HTML file and checks for broken
60links. It is similar to check-links.pl by Jamie Zawinski,
61but uses all facilities of the LuaSocket library and the Lua
62language. It has not been thoroughly tested, but it should
63work. Just run
64
65 lua check-links.lua [-n] {<url>} > output
66
67and open the result to see a list of broken links. Make sure
68you check the '-n' switch. It runs in non-blocking mode,
69using coroutines, and is MUCH faster!
70
71 forward.lua -- coroutine based forward server
72
73This is a forward server that can accept several connections
74and transfers simultaneously using non-blocking I/O and the
75coroutine-based dispatcher. You can run, for example
76
77 lua forward.lua 8080:proxy.com:3128
78
79to redirect all local conections to port 8080 to the host
80'proxy.com' at port 3128.
81
82 unix.c and unix.h
83
84This is an implementation of Unix local domain sockets and
85demonstrates how to extend LuaSocket with a new type of
86transport. It has been tested on Linux and on Mac OS X.
87
88Good luck,
89Diego.
diff --git a/etc/b64.lua b/etc/b64.lua
deleted file mode 100644
index 11eeb2d..0000000
--- a/etc/b64.lua
+++ /dev/null
@@ -1,19 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program to convert to and from Base64
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6local ltn12 = require("ltn12")
7local mime = require("mime")
8local source = ltn12.source.file(io.stdin)
9local sink = ltn12.sink.file(io.stdout)
10local convert
11if arg and arg[1] == '-d' then
12 convert = mime.decode("base64")
13else
14 local base64 = mime.encode("base64")
15 local wrap = mime.wrap()
16 convert = ltn12.filter.chain(base64, wrap)
17end
18sink = ltn12.sink.chain(convert, sink)
19ltn12.pump.all(source, sink)
diff --git a/etc/check-links.lua b/etc/check-links.lua
deleted file mode 100644
index 283f3ac..0000000
--- a/etc/check-links.lua
+++ /dev/null
@@ -1,111 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program that checks links in HTML files, using coroutines and
3-- non-blocking I/O via the dispatcher module.
4-- LuaSocket sample files
5-- Author: Diego Nehab
6-----------------------------------------------------------------------------
7local url = require("socket.url")
8local dispatch = require("dispatch")
9local http = require("socket.http")
10dispatch.TIMEOUT = 10
11
12-- make sure the user knows how to invoke us
13arg = arg or {}
14if #arg < 1 then
15 print("Usage:\n luasocket check-links.lua [-n] {<url>}")
16 exit()
17end
18
19-- '-n' means we are running in non-blocking mode
20if arg[1] == "-n" then
21 -- if non-blocking I/O was requested, use real dispatcher interface
22 table.remove(arg, 1)
23 handler = dispatch.newhandler("coroutine")
24else
25 -- if using blocking I/O, use fake dispatcher interface
26 handler = dispatch.newhandler("sequential")
27end
28
29local nthreads = 0
30
31-- get the status of a URL using the dispatcher
32function getstatus(link)
33 local parsed = url.parse(link, {scheme = "file"})
34 if parsed.scheme == "http" then
35 nthreads = nthreads + 1
36 handler:start(function()
37 local r, c, h, s = http.request{
38 method = "HEAD",
39 url = link,
40 create = handler.tcp
41 }
42 if r and c == 200 then io.write('\t', link, '\n')
43 else io.write('\t', link, ': ', tostring(c), '\n') end
44 nthreads = nthreads - 1
45 end)
46 end
47end
48
49function readfile(path)
50 path = url.unescape(path)
51 local file, error = io.open(path, "r")
52 if file then
53 local body = file:read("*a")
54 file:close()
55 return body
56 else return nil, error end
57end
58
59function load(u)
60 local parsed = url.parse(u, { scheme = "file" })
61 local body, headers, code, error
62 local base = u
63 if parsed.scheme == "http" then
64 body, code, headers = http.request(u)
65 if code == 200 then
66 -- if there was a redirect, update base to reflect it
67 base = headers.location or base
68 end
69 if not body then
70 error = code
71 end
72 elseif parsed.scheme == "file" then
73 body, error = readfile(parsed.path)
74 else error = string.format("unhandled scheme '%s'", parsed.scheme) end
75 return base, body, error
76end
77
78function getlinks(body, base)
79 -- get rid of comments
80 body = string.gsub(body, "%<%!%-%-.-%-%-%>", "")
81 local links = {}
82 -- extract links
83 body = string.gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href)
84 table.insert(links, url.absolute(base, href))
85 end)
86 body = string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href)
87 table.insert(links, url.absolute(base, href))
88 end)
89 string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(.-)>", function(href)
90 table.insert(links, url.absolute(base, href))
91 end)
92 return links
93end
94
95function checklinks(address)
96 local base, body, error = load(address)
97 if not body then print(error) return end
98 print("Checking ", base)
99 local links = getlinks(body, base)
100 for _, link in ipairs(links) do
101 getstatus(link)
102 end
103end
104
105for _, address in ipairs(arg) do
106 checklinks(url.absolute("file:", address))
107end
108
109while nthreads > 0 do
110 handler:step()
111end
diff --git a/etc/check-memory.lua b/etc/check-memory.lua
deleted file mode 100644
index 7bd984d..0000000
--- a/etc/check-memory.lua
+++ /dev/null
@@ -1,17 +0,0 @@
1function load(s)
2 collectgarbage()
3 local a = gcinfo()
4 _G[s] = require(s)
5 collectgarbage()
6 local b = gcinfo()
7 print(s .. ":\t " .. (b-a) .. "k")
8end
9
10load("socket.url")
11load("ltn12")
12load("socket")
13load("mime")
14load("socket.tp")
15load("socket.smtp")
16load("socket.http")
17load("socket.ftp")
diff --git a/etc/cookie.lua b/etc/cookie.lua
deleted file mode 100644
index 4adb403..0000000
--- a/etc/cookie.lua
+++ /dev/null
@@ -1,88 +0,0 @@
1local socket = require"socket"
2local http = require"socket.http"
3local url = require"socket.url"
4local ltn12 = require"ltn12"
5
6local token_class = '[^%c%s%(%)%<%>%@%,%;%:%\\%"%/%[%]%?%=%{%}]'
7
8local function unquote(t, quoted)
9 local n = string.match(t, "%$(%d+)$")
10 if n then n = tonumber(n) end
11 if quoted[n] then return quoted[n]
12 else return t end
13end
14
15local function parse_set_cookie(c, quoted, cookie_table)
16 c = c .. ";$last=last;"
17 local _, __, n, v, i = string.find(c, "(" .. token_class ..
18 "+)%s*=%s*(.-)%s*;%s*()")
19 local cookie = {
20 name = n,
21 value = unquote(v, quoted),
22 attributes = {}
23 }
24 while 1 do
25 _, __, n, v, i = string.find(c, "(" .. token_class ..
26 "+)%s*=?%s*(.-)%s*;%s*()", i)
27 if not n or n == "$last" then break end
28 cookie.attributes[#cookie.attributes+1] = {
29 name = n,
30 value = unquote(v, quoted)
31 }
32 end
33 cookie_table[#cookie_table+1] = cookie
34end
35
36local function split_set_cookie(s, cookie_table)
37 cookie_table = cookie_table or {}
38 -- remove quoted strings from cookie list
39 local quoted = {}
40 s = string.gsub(s, '"(.-)"', function(q)
41 quoted[#quoted+1] = q
42 return "$" .. #quoted
43 end)
44 -- add sentinel
45 s = s .. ",$last="
46 -- split into individual cookies
47 i = 1
48 while 1 do
49 local _, __, cookie, next_token
50 _, __, cookie, i, next_token = string.find(s, "(.-)%s*%,%s*()(" ..
51 token_class .. "+)%s*=", i)
52 if not next_token then break end
53 parse_set_cookie(cookie, quoted, cookie_table)
54 if next_token == "$last" then break end
55 end
56 return cookie_table
57end
58
59local function quote(s)
60 if string.find(s, "[ %,%;]") then return '"' .. s .. '"'
61 else return s end
62end
63
64local _empty = {}
65local function build_cookies(cookies)
66 s = ""
67 for i,v in ipairs(cookies or _empty) do
68 if v.name then
69 s = s .. v.name
70 if v.value and v.value ~= "" then
71 s = s .. '=' .. quote(v.value)
72 end
73 end
74 if v.name and #(v.attributes or _empty) > 0 then s = s .. "; " end
75 for j,u in ipairs(v.attributes or _empty) do
76 if u.name then
77 s = s .. u.name
78 if u.value and u.value ~= "" then
79 s = s .. '=' .. quote(u.value)
80 end
81 end
82 if j < #v.attributes then s = s .. "; " end
83 end
84 if i < #cookies then s = s .. ", " end
85 end
86 return s
87end
88
diff --git a/etc/dict.lua b/etc/dict.lua
deleted file mode 100644
index 8c5b711..0000000
--- a/etc/dict.lua
+++ /dev/null
@@ -1,151 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program to download DICT word definitions
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6
7-----------------------------------------------------------------------------
8-- Load required modules
9-----------------------------------------------------------------------------
10local base = _G
11local string = require("string")
12local table = require("table")
13local socket = require("socket")
14local url = require("socket.url")
15local tp = require("socket.tp")
16module("socket.dict")
17
18-----------------------------------------------------------------------------
19-- Globals
20-----------------------------------------------------------------------------
21HOST = "dict.org"
22PORT = 2628
23TIMEOUT = 10
24
25-----------------------------------------------------------------------------
26-- Low-level dict API
27-----------------------------------------------------------------------------
28local metat = { __index = {} }
29
30function open(host, port)
31 local tp = socket.try(tp.connect(host or HOST, port or PORT, TIMEOUT))
32 return base.setmetatable({tp = tp}, metat)
33end
34
35function metat.__index:greet()
36 return socket.try(self.tp:check(220))
37end
38
39function metat.__index:check(ok)
40 local code, status = socket.try(self.tp:check(ok))
41 return code,
42 base.tonumber(socket.skip(2, string.find(status, "^%d%d%d (%d*)")))
43end
44
45function metat.__index:getdef()
46 local line = socket.try(self.tp:receive())
47 local def = {}
48 while line ~= "." do
49 table.insert(def, line)
50 line = socket.try(self.tp:receive())
51 end
52 return table.concat(def, "\n")
53end
54
55function metat.__index:define(database, word)
56 database = database or "!"
57 socket.try(self.tp:command("DEFINE", database .. " " .. word))
58 local code, count = self:check(150)
59 local defs = {}
60 for i = 1, count do
61 self:check(151)
62 table.insert(defs, self:getdef())
63 end
64 self:check(250)
65 return defs
66end
67
68function metat.__index:match(database, strat, word)
69 database = database or "!"
70 strat = strat or "."
71 socket.try(self.tp:command("MATCH", database .." ".. strat .." ".. word))
72 self:check(152)
73 local mat = {}
74 local line = socket.try(self.tp:receive())
75 while line ~= '.' do
76 database, word = socket.skip(2, string.find(line, "(%S+) (.*)"))
77 if not mat[database] then mat[database] = {} end
78 table.insert(mat[database], word)
79 line = socket.try(self.tp:receive())
80 end
81 self:check(250)
82 return mat
83end
84
85function metat.__index:quit()
86 self.tp:command("QUIT")
87 return self:check(221)
88end
89
90function metat.__index:close()
91 return self.tp:close()
92end
93
94-----------------------------------------------------------------------------
95-- High-level dict API
96-----------------------------------------------------------------------------
97local default = {
98 scheme = "dict",
99 host = "dict.org"
100}
101
102local function there(f)
103 if f == "" then return nil
104 else return f end
105end
106
107local function parse(u)
108 local t = socket.try(url.parse(u, default))
109 socket.try(t.scheme == "dict", "invalid scheme '" .. t.scheme .. "'")
110 socket.try(t.path, "invalid path in url")
111 local cmd, arg = socket.skip(2, string.find(t.path, "^/(.)(.*)$"))
112 socket.try(cmd == "d" or cmd == "m", "<command> should be 'm' or 'd'")
113 socket.try(arg and arg ~= "", "need at least <word> in URL")
114 t.command, t.argument = cmd, arg
115 arg = string.gsub(arg, "^:([^:]+)", function(f) t.word = f end)
116 socket.try(t.word, "need at least <word> in URL")
117 arg = string.gsub(arg, "^:([^:]*)", function(f) t.database = there(f) end)
118 if cmd == "m" then
119 arg = string.gsub(arg, "^:([^:]*)", function(f) t.strat = there(f) end)
120 end
121 string.gsub(arg, ":([^:]*)$", function(f) t.n = base.tonumber(f) end)
122 return t
123end
124
125local function tget(gett)
126 local con = open(gett.host, gett.port)
127 con:greet()
128 if gett.command == "d" then
129 local def = con:define(gett.database, gett.word)
130 con:quit()
131 con:close()
132 if gett.n then return def[gett.n]
133 else return def end
134 elseif gett.command == "m" then
135 local mat = con:match(gett.database, gett.strat, gett.word)
136 con:quit()
137 con:close()
138 return mat
139 else return nil, "invalid command" end
140end
141
142local function sget(u)
143 local gett = parse(u)
144 return tget(gett)
145end
146
147get = socket.protect(function(gett)
148 if base.type(gett) == "string" then return sget(gett)
149 else return tget(gett) end
150end)
151
diff --git a/etc/dispatch.lua b/etc/dispatch.lua
deleted file mode 100644
index 2485415..0000000
--- a/etc/dispatch.lua
+++ /dev/null
@@ -1,307 +0,0 @@
1-----------------------------------------------------------------------------
2-- A hacked dispatcher module
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6local base = _G
7local table = require("table")
8local string = require("string")
9local socket = require("socket")
10local coroutine = require("coroutine")
11module("dispatch")
12
13-- if too much time goes by without any activity in one of our sockets, we
14-- just kill it
15TIMEOUT = 60
16
17-----------------------------------------------------------------------------
18-- We implement 3 types of dispatchers:
19-- sequential
20-- coroutine
21-- threaded
22-- The user can choose whatever one is needed
23-----------------------------------------------------------------------------
24local handlert = {}
25
26-- default handler is coroutine
27function newhandler(mode)
28 mode = mode or "coroutine"
29 return handlert[mode]()
30end
31
32local function seqstart(self, func)
33 return func()
34end
35
36-- sequential handler simply calls the functions and doesn't wrap I/O
37function handlert.sequential()
38 return {
39 tcp = socket.tcp,
40 start = seqstart
41 }
42end
43
44-----------------------------------------------------------------------------
45-- Mega hack. Don't try to do this at home.
46-----------------------------------------------------------------------------
47-- we can't yield across calls to protect on Lua 5.1, so we rewrite it with
48-- coroutines
49-- make sure you don't require any module that uses socket.protect before
50-- loading our hack
51if string.sub(base._VERSION, -3) == "5.1" then
52 local function _protect(co, status, ...)
53 if not status then
54 local msg = ...
55 if base.type(msg) == 'table' then
56 return nil, msg[1]
57 else
58 base.error(msg, 0)
59 end
60 end
61 if coroutine.status(co) == "suspended" then
62 return _protect(co, coroutine.resume(co, coroutine.yield(...)))
63 else
64 return ...
65 end
66 end
67
68 function socket.protect(f)
69 return function(...)
70 local co = coroutine.create(f)
71 return _protect(co, coroutine.resume(co, ...))
72 end
73 end
74end
75
76-----------------------------------------------------------------------------
77-- Simple set data structure. O(1) everything.
78-----------------------------------------------------------------------------
79local function newset()
80 local reverse = {}
81 local set = {}
82 return base.setmetatable(set, {__index = {
83 insert = function(set, value)
84 if not reverse[value] then
85 table.insert(set, value)
86 reverse[value] = #set
87 end
88 end,
89 remove = function(set, value)
90 local index = reverse[value]
91 if index then
92 reverse[value] = nil
93 local top = table.remove(set)
94 if top ~= value then
95 reverse[top] = index
96 set[index] = top
97 end
98 end
99 end
100 }})
101end
102
103-----------------------------------------------------------------------------
104-- socket.tcp() wrapper for the coroutine dispatcher
105-----------------------------------------------------------------------------
106local function cowrap(dispatcher, tcp, error)
107 if not tcp then return nil, error end
108 -- put it in non-blocking mode right away
109 tcp:settimeout(0)
110 -- metatable for wrap produces new methods on demand for those that we
111 -- don't override explicitly.
112 local metat = { __index = function(table, key)
113 table[key] = function(...)
114 return tcp[key](tcp,select(2,...))
115 end
116 return table[key]
117 end}
118 -- does our user want to do his own non-blocking I/O?
119 local zero = false
120 -- create a wrap object that will behave just like a real socket object
121 local wrap = { }
122 -- we ignore settimeout to preserve our 0 timeout, but record whether
123 -- the user wants to do his own non-blocking I/O
124 function wrap:settimeout(value, mode)
125 if value == 0 then zero = true
126 else zero = false end
127 return 1
128 end
129 -- send in non-blocking mode and yield on timeout
130 function wrap:send(data, first, last)
131 first = (first or 1) - 1
132 local result, error
133 while true do
134 -- return control to dispatcher and tell it we want to send
135 -- if upon return the dispatcher tells us we timed out,
136 -- return an error to whoever called us
137 if coroutine.yield(dispatcher.sending, tcp) == "timeout" then
138 return nil, "timeout"
139 end
140 -- try sending
141 result, error, first = tcp:send(data, first+1, last)
142 -- if we are done, or there was an unexpected error,
143 -- break away from loop
144 if error ~= "timeout" then return result, error, first end
145 end
146 end
147 -- receive in non-blocking mode and yield on timeout
148 -- or simply return partial read, if user requested timeout = 0
149 function wrap:receive(pattern, partial)
150 local error = "timeout"
151 local value
152 while true do
153 -- return control to dispatcher and tell it we want to receive
154 -- if upon return the dispatcher tells us we timed out,
155 -- return an error to whoever called us
156 if coroutine.yield(dispatcher.receiving, tcp) == "timeout" then
157 return nil, "timeout"
158 end
159 -- try receiving
160 value, error, partial = tcp:receive(pattern, partial)
161 -- if we are done, or there was an unexpected error,
162 -- break away from loop. also, if the user requested
163 -- zero timeout, return all we got
164 if (error ~= "timeout") or zero then
165 return value, error, partial
166 end
167 end
168 end
169 -- connect in non-blocking mode and yield on timeout
170 function wrap:connect(host, port)
171 local result, error = tcp:connect(host, port)
172 if error == "timeout" then
173 -- return control to dispatcher. we will be writable when
174 -- connection succeeds.
175 -- if upon return the dispatcher tells us we have a
176 -- timeout, just abort
177 if coroutine.yield(dispatcher.sending, tcp) == "timeout" then
178 return nil, "timeout"
179 end
180 -- when we come back, check if connection was successful
181 result, error = tcp:connect(host, port)
182 if result or error == "already connected" then return 1
183 else return nil, "non-blocking connect failed" end
184 else return result, error end
185 end
186 -- accept in non-blocking mode and yield on timeout
187 function wrap:accept()
188 while 1 do
189 -- return control to dispatcher. we will be readable when a
190 -- connection arrives.
191 -- if upon return the dispatcher tells us we have a
192 -- timeout, just abort
193 if coroutine.yield(dispatcher.receiving, tcp) == "timeout" then
194 return nil, "timeout"
195 end
196 local client, error = tcp:accept()
197 if error ~= "timeout" then
198 return cowrap(dispatcher, client, error)
199 end
200 end
201 end
202 -- remove cortn from context
203 function wrap:close()
204 dispatcher.stamp[tcp] = nil
205 dispatcher.sending.set:remove(tcp)
206 dispatcher.sending.cortn[tcp] = nil
207 dispatcher.receiving.set:remove(tcp)
208 dispatcher.receiving.cortn[tcp] = nil
209 return tcp:close()
210 end
211 return base.setmetatable(wrap, metat)
212end
213
214
215-----------------------------------------------------------------------------
216-- Our coroutine dispatcher
217-----------------------------------------------------------------------------
218local cometat = { __index = {} }
219
220function schedule(cortn, status, operation, tcp)
221 if status then
222 if cortn and operation then
223 operation.set:insert(tcp)
224 operation.cortn[tcp] = cortn
225 operation.stamp[tcp] = socket.gettime()
226 end
227 else base.error(operation) end
228end
229
230function kick(operation, tcp)
231 operation.cortn[tcp] = nil
232 operation.set:remove(tcp)
233end
234
235function wakeup(operation, tcp)
236 local cortn = operation.cortn[tcp]
237 -- if cortn is still valid, wake it up
238 if cortn then
239 kick(operation, tcp)
240 return cortn, coroutine.resume(cortn)
241 -- othrewise, just get scheduler not to do anything
242 else
243 return nil, true
244 end
245end
246
247function abort(operation, tcp)
248 local cortn = operation.cortn[tcp]
249 if cortn then
250 kick(operation, tcp)
251 coroutine.resume(cortn, "timeout")
252 end
253end
254
255-- step through all active cortns
256function cometat.__index:step()
257 -- check which sockets are interesting and act on them
258 local readable, writable = socket.select(self.receiving.set,
259 self.sending.set, 1)
260 -- for all readable connections, resume their cortns and reschedule
261 -- when they yield back to us
262 for _, tcp in base.ipairs(readable) do
263 schedule(wakeup(self.receiving, tcp))
264 end
265 -- for all writable connections, do the same
266 for _, tcp in base.ipairs(writable) do
267 schedule(wakeup(self.sending, tcp))
268 end
269 -- politely ask replacement I/O functions in idle cortns to
270 -- return reporting a timeout
271 local now = socket.gettime()
272 for tcp, stamp in base.pairs(self.stamp) do
273 if tcp.class == "tcp{client}" and now - stamp > TIMEOUT then
274 abort(self.sending, tcp)
275 abort(self.receiving, tcp)
276 end
277 end
278end
279
280function cometat.__index:start(func)
281 local cortn = coroutine.create(func)
282 schedule(cortn, coroutine.resume(cortn))
283end
284
285function handlert.coroutine()
286 local stamp = {}
287 local dispatcher = {
288 stamp = stamp,
289 sending = {
290 name = "sending",
291 set = newset(),
292 cortn = {},
293 stamp = stamp
294 },
295 receiving = {
296 name = "receiving",
297 set = newset(),
298 cortn = {},
299 stamp = stamp
300 },
301 }
302 function dispatcher.tcp()
303 return cowrap(dispatcher, socket.tcp())
304 end
305 return base.setmetatable(dispatcher, cometat)
306end
307
diff --git a/etc/eol.lua b/etc/eol.lua
deleted file mode 100644
index eeaf0ce..0000000
--- a/etc/eol.lua
+++ /dev/null
@@ -1,13 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program to adjust end of line markers.
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6local mime = require("mime")
7local ltn12 = require("ltn12")
8local marker = '\n'
9if arg and arg[1] == '-d' then marker = '\r\n' end
10local filter = mime.normalize(marker)
11local source = ltn12.source.chain(ltn12.source.file(io.stdin), filter)
12local sink = ltn12.sink.file(io.stdout)
13ltn12.pump.all(source, sink)
diff --git a/etc/forward.lua b/etc/forward.lua
deleted file mode 100644
index 05ced1a..0000000
--- a/etc/forward.lua
+++ /dev/null
@@ -1,65 +0,0 @@
1-- load our favourite library
2local dispatch = require("dispatch")
3local handler = dispatch.newhandler()
4
5-- make sure the user knows how to invoke us
6if #arg < 1 then
7 print("Usage")
8 print(" lua forward.lua <iport:ohost:oport> ...")
9 os.exit(1)
10end
11
12-- function to move data from one socket to the other
13local function move(foo, bar)
14 local live
15 while 1 do
16 local data, error, partial = foo:receive(2048)
17 live = data or error == "timeout"
18 data = data or partial
19 local result, error = bar:send(data)
20 if not live or not result then
21 foo:close()
22 bar:close()
23 break
24 end
25 end
26end
27
28-- for each tunnel, start a new server
29for i, v in ipairs(arg) do
30 -- capture forwarding parameters
31 local _, _, iport, ohost, oport = string.find(v, "([^:]+):([^:]+):([^:]+)")
32 assert(iport, "invalid arguments")
33 -- create our server socket
34 local server = assert(handler.tcp())
35 assert(server:setoption("reuseaddr", true))
36 assert(server:bind("*", iport))
37 assert(server:listen(32))
38 -- handler for the server object loops accepting new connections
39 handler:start(function()
40 while 1 do
41 local client = assert(server:accept())
42 assert(client:settimeout(0))
43 -- for each new connection, start a new client handler
44 handler:start(function()
45 -- handler tries to connect to peer
46 local peer = assert(handler.tcp())
47 assert(peer:settimeout(0))
48 assert(peer:connect(ohost, oport))
49 -- if sucessful, starts a new handler to send data from
50 -- client to peer
51 handler:start(function()
52 move(client, peer)
53 end)
54 -- afte starting new handler, enter in loop sending data from
55 -- peer to client
56 move(peer, client)
57 end)
58 end
59 end)
60end
61
62-- simply loop stepping the server
63while 1 do
64 handler:step()
65end
diff --git a/etc/get.lua b/etc/get.lua
deleted file mode 100644
index 9edc235..0000000
--- a/etc/get.lua
+++ /dev/null
@@ -1,141 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program to download files from URLs
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6local socket = require("socket")
7local http = require("socket.http")
8local ftp = require("socket.ftp")
9local url = require("socket.url")
10local ltn12 = require("ltn12")
11
12-- formats a number of seconds into human readable form
13function nicetime(s)
14 local l = "s"
15 if s > 60 then
16 s = s / 60
17 l = "m"
18 if s > 60 then
19 s = s / 60
20 l = "h"
21 if s > 24 then
22 s = s / 24
23 l = "d" -- hmmm
24 end
25 end
26 end
27 if l == "s" then return string.format("%5.0f%s", s, l)
28 else return string.format("%5.2f%s", s, l) end
29end
30
31-- formats a number of bytes into human readable form
32function nicesize(b)
33 local l = "B"
34 if b > 1024 then
35 b = b / 1024
36 l = "KB"
37 if b > 1024 then
38 b = b / 1024
39 l = "MB"
40 if b > 1024 then
41 b = b / 1024
42 l = "GB" -- hmmm
43 end
44 end
45 end
46 return string.format("%7.2f%2s", b, l)
47end
48
49-- returns a string with the current state of the download
50local remaining_s = "%s received, %s/s throughput, %2.0f%% done, %s remaining"
51local elapsed_s = "%s received, %s/s throughput, %s elapsed "
52function gauge(got, delta, size)
53 local rate = got / delta
54 if size and size >= 1 then
55 return string.format(remaining_s, nicesize(got), nicesize(rate),
56 100*got/size, nicetime((size-got)/rate))
57 else
58 return string.format(elapsed_s, nicesize(got),
59 nicesize(rate), nicetime(delta))
60 end
61end
62
63-- creates a new instance of a receive_cb that saves to disk
64-- kind of copied from luasocket's manual callback examples
65function stats(size)
66 local start = socket.gettime()
67 local last = start
68 local got = 0
69 return function(chunk)
70 -- elapsed time since start
71 local current = socket.gettime()
72 if chunk then
73 -- total bytes received
74 got = got + string.len(chunk)
75 -- not enough time for estimate
76 if current - last > 1 then
77 io.stderr:write("\r", gauge(got, current - start, size))
78 io.stderr:flush()
79 last = current
80 end
81 else
82 -- close up
83 io.stderr:write("\r", gauge(got, current - start), "\n")
84 end
85 return chunk
86 end
87end
88
89-- determines the size of a http file
90function gethttpsize(u)
91 local r, c, h = http.request {method = "HEAD", url = u}
92 if c == 200 then
93 return tonumber(h["content-length"])
94 end
95end
96
97-- downloads a file using the http protocol
98function getbyhttp(u, file)
99 local save = ltn12.sink.file(file or io.stdout)
100 -- only print feedback if output is not stdout
101 if file then save = ltn12.sink.chain(stats(gethttpsize(u)), save) end
102 local r, c, h, s = http.request {url = u, sink = save }
103 if c ~= 200 then io.stderr:write(s or c, "\n") end
104end
105
106-- downloads a file using the ftp protocol
107function getbyftp(u, file)
108 local save = ltn12.sink.file(file or io.stdout)
109 -- only print feedback if output is not stdout
110 -- and we don't know how big the file is
111 if file then save = ltn12.sink.chain(stats(), save) end
112 local gett = url.parse(u)
113 gett.sink = save
114 gett.type = "i"
115 local ret, err = ftp.get(gett)
116 if err then print(err) end
117end
118
119-- determines the scheme
120function getscheme(u)
121 -- this is an heuristic to solve a common invalid url poblem
122 if not string.find(u, "//") then u = "//" .. u end
123 local parsed = url.parse(u, {scheme = "http"})
124 return parsed.scheme
125end
126
127-- gets a file either by http or ftp, saving as <name>
128function get(u, name)
129 local fout = name and io.open(name, "wb")
130 local scheme = getscheme(u)
131 if scheme == "ftp" then getbyftp(u, fout)
132 elseif scheme == "http" then getbyhttp(u, fout)
133 else print("unknown scheme" .. scheme) end
134end
135
136-- main program
137arg = arg or {}
138if #arg < 1 then
139 io.write("Usage:\n lua get.lua <remote-url> [<local-file>]\n")
140 os.exit(1)
141else get(arg[1], arg[2]) end
diff --git a/etc/links b/etc/links
deleted file mode 100644
index 087f1c0..0000000
--- a/etc/links
+++ /dev/null
@@ -1,17 +0,0 @@
1<a href="http://www.cs.princeton.edu"> bla </a>
2<a href="http://www.princeton.edu"> bla </a>
3<a href="http://www.tecgraf.puc-rio.br"> bla </a>
4<a href="http://www.inf.puc-rio.br"> bla </a>
5<a href="http://www.puc-rio.br"> bla </a>
6<a href="http://www.impa.br"> bla </a>
7<a href="http://www.lua.org"> bla </a>
8<a href="http://www.lua-users.org"> bla </a>
9<a href="http://www.amazon.com"> bla </a>
10<a href="http://www.google.com"> bla </a>
11<a href="http://www.nytimes.com"> bla </a>
12<a href="http://www.bbc.co.uk"> bla </a>
13<a href="http://oglobo.globo.com"> bla </a>
14<a href="http://slate.msn.com"> bla </a>
15<a href="http://www.apple.com"> bla </a>
16<a href="http://www.microsoft.com"> bla </a>
17<a href="http://www.nasa.gov"> bla </a>
diff --git a/etc/lp.lua b/etc/lp.lua
deleted file mode 100644
index 25f0b95..0000000
--- a/etc/lp.lua
+++ /dev/null
@@ -1,323 +0,0 @@
1-----------------------------------------------------------------------------
2-- LPD support for the Lua language
3-- LuaSocket toolkit.
4-- Author: David Burgess
5-- Modified by Diego Nehab, but David is in charge
6-----------------------------------------------------------------------------
7--[[
8 if you have any questions: RFC 1179
9]]
10-- make sure LuaSocket is loaded
11local io = require("io")
12local base = _G
13local os = require("os")
14local math = require("math")
15local string = require("string")
16local socket = require("socket")
17local ltn12 = require("ltn12")
18module("socket.lp")
19
20-- default port
21PORT = 515
22SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
23PRINTER = os.getenv("PRINTER") or "printer"
24
25local function connect(localhost, option)
26 local host = option.host or SERVER
27 local port = option.port or PORT
28 local skt
29 local try = socket.newtry(function() if skt then skt:close() end end)
30 if option.localbind then
31 -- bind to a local port (if we can)
32 local localport = 721
33 local done, err
34 repeat
35 skt = socket.try(socket.tcp())
36 try(skt:settimeout(30))
37 done, err = skt:bind(localhost, localport)
38 if not done then
39 localport = localport + 1
40 skt:close()
41 skt = nil
42 else break end
43 until localport > 731
44 socket.try(skt, err)
45 else skt = socket.try(socket.tcp()) end
46 try(skt:connect(host, port))
47 return { skt = skt, try = try }
48end
49
50--[[
51RFC 1179
525.3 03 - Send queue state (short)
53
54 +----+-------+----+------+----+
55 | 03 | Queue | SP | List | LF |
56 +----+-------+----+------+----+
57 Command code - 3
58 Operand 1 - Printer queue name
59 Other operands - User names or job numbers
60
61 If the user names or job numbers or both are supplied then only those
62 jobs for those users or with those numbers will be sent.
63
64 The response is an ASCII stream which describes the printer queue.
65 The stream continues until the connection closes. Ends of lines are
66 indicated with ASCII LF control characters. The lines may also
67 contain ASCII HT control characters.
68
695.4 04 - Send queue state (long)
70
71 +----+-------+----+------+----+
72 | 04 | Queue | SP | List | LF |
73 +----+-------+----+------+----+
74 Command code - 4
75 Operand 1 - Printer queue name
76 Other operands - User names or job numbers
77
78 If the user names or job numbers or both are supplied then only those
79 jobs for those users or with those numbers will be sent.
80
81 The response is an ASCII stream which describes the printer queue.
82 The stream continues until the connection closes. Ends of lines are
83 indicated with ASCII LF control characters. The lines may also
84 contain ASCII HT control characters.
85]]
86
87-- gets server acknowledement
88local function recv_ack(con)
89 local ack = con.skt:receive(1)
90 con.try(string.char(0) == ack, "failed to receive server acknowledgement")
91end
92
93-- sends client acknowledement
94local function send_ack(con)
95 local sent = con.skt:send(string.char(0))
96 con.try(sent == 1, "failed to send acknowledgement")
97end
98
99-- sends queue request
100-- 5.2 02 - Receive a printer job
101--
102-- +----+-------+----+
103-- | 02 | Queue | LF |
104-- +----+-------+----+
105-- Command code - 2
106-- Operand - Printer queue name
107--
108-- Receiving a job is controlled by a second level of commands. The
109-- daemon is given commands by sending them over the same connection.
110-- The commands are described in the next section (6).
111--
112-- After this command is sent, the client must read an acknowledgement
113-- octet from the daemon. A positive acknowledgement is an octet of
114-- zero bits. A negative acknowledgement is an octet of any other
115-- pattern.
116local function send_queue(con, queue)
117 queue = queue or PRINTER
118 local str = string.format("\2%s\10", queue)
119 local sent = con.skt:send(str)
120 con.try(sent == string.len(str), "failed to send print request")
121 recv_ack(con)
122end
123
124-- sends control file
125-- 6.2 02 - Receive control file
126--
127-- +----+-------+----+------+----+
128-- | 02 | Count | SP | Name | LF |
129-- +----+-------+----+------+----+
130-- Command code - 2
131-- Operand 1 - Number of bytes in control file
132-- Operand 2 - Name of control file
133--
134-- The control file must be an ASCII stream with the ends of lines
135-- indicated by ASCII LF. The total number of bytes in the stream is
136-- sent as the first operand. The name of the control file is sent as
137-- the second. It should start with ASCII "cfA", followed by a three
138-- digit job number, followed by the host name which has constructed the
139-- control file. Acknowledgement processing must occur as usual after
140-- the command is sent.
141--
142-- The next "Operand 1" octets over the same TCP connection are the
143-- intended contents of the control file. Once all of the contents have
144-- been delivered, an octet of zero bits is sent as an indication that
145-- the file being sent is complete. A second level of acknowledgement
146-- processing must occur at this point.
147
148-- sends data file
149-- 6.3 03 - Receive data file
150--
151-- +----+-------+----+------+----+
152-- | 03 | Count | SP | Name | LF |
153-- +----+-------+----+------+----+
154-- Command code - 3
155-- Operand 1 - Number of bytes in data file
156-- Operand 2 - Name of data file
157--
158-- The data file may contain any 8 bit values at all. The total number
159-- of bytes in the stream may be sent as the first operand, otherwise
160-- the field should be cleared to 0. The name of the data file should
161-- start with ASCII "dfA". This should be followed by a three digit job
162-- number. The job number should be followed by the host name which has
163-- constructed the data file. Interpretation of the contents of the
164-- data file is determined by the contents of the corresponding control
165-- file. If a data file length has been specified, the next "Operand 1"
166-- octets over the same TCP connection are the intended contents of the
167-- data file. In this case, once all of the contents have been
168-- delivered, an octet of zero bits is sent as an indication that the
169-- file being sent is complete. A second level of acknowledgement
170-- processing must occur at this point.
171
172
173local function send_hdr(con, control)
174 local sent = con.skt:send(control)
175 con.try(sent and sent >= 1 , "failed to send header file")
176 recv_ack(con)
177end
178
179local function send_control(con, control)
180 local sent = con.skt:send(control)
181 con.try(sent and sent >= 1, "failed to send control file")
182 send_ack(con)
183end
184
185local function send_data(con,fh,size)
186 local buf
187 while size > 0 do
188 buf,message = fh:read(8192)
189 if buf then
190 st = con.try(con.skt:send(buf))
191 size = size - st
192 else
193 con.try(size == 0, "file size mismatch")
194 end
195 end
196 recv_ack(con) -- note the double acknowledgement
197 send_ack(con)
198 recv_ack(con)
199 return size
200end
201
202
203--[[
204local control_dflt = {
205 "H"..string.sub(socket.hostname,1,31).."\10", -- host
206 "C"..string.sub(socket.hostname,1,31).."\10", -- class
207 "J"..string.sub(filename,1,99).."\10", -- jobname
208 "L"..string.sub(user,1,31).."\10", -- print banner page
209 "I"..tonumber(indent).."\10", -- indent column count ('f' only)
210 "M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
211 "N"..string.sub(filename,1,131).."\10", -- name of source file
212 "P"..string.sub(user,1,31).."\10", -- user name
213 "T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
214 "W"..tonumber(width or 132).."\10", -- width of print f,l,p only
215
216 "f"..file.."\10", -- formatted print (remove control chars)
217 "l"..file.."\10", -- print
218 "o"..file.."\10", -- postscript
219 "p"..file.."\10", -- pr format - requires T, L
220 "r"..file.."\10", -- fortran format
221 "U"..file.."\10", -- Unlink (data file only)
222}
223]]
224
225-- generate a varying job number
226local seq = 0
227local function newjob(connection)
228 seq = seq + 1
229 return math.floor(socket.gettime() * 1000 + seq)%1000
230end
231
232
233local format_codes = {
234 binary = 'l',
235 text = 'f',
236 ps = 'o',
237 pr = 'p',
238 fortran = 'r',
239 l = 'l',
240 r = 'r',
241 o = 'o',
242 p = 'p',
243 f = 'f'
244}
245
246-- lp.send{option}
247-- requires option.file
248
249send = socket.protect(function(option)
250 socket.try(option and base.type(option) == "table", "invalid options")
251 local file = option.file
252 socket.try(file, "invalid file name")
253 local fh = socket.try(io.open(file,"rb"))
254 local datafile_size = fh:seek("end") -- get total size
255 fh:seek("set") -- go back to start of file
256 local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
257 or "localhost"
258 local con = connect(localhost, option)
259-- format the control file
260 local jobno = newjob()
261 local localip = socket.dns.toip(localhost)
262 localhost = string.sub(localhost,1,31)
263 local user = string.sub(option.user or os.getenv("LPRUSER") or
264 os.getenv("USERNAME") or os.getenv("USER") or "anonymous", 1,31)
265 local lpfile = string.format("dfA%3.3d%-s", jobno, localhost);
266 local fmt = format_codes[option.format] or 'l'
267 local class = string.sub(option.class or localip or localhost,1,31)
268 local _,_,ctlfn = string.find(file,".*[%/%\\](.*)")
269 ctlfn = string.sub(ctlfn or file,1,131)
270 local cfile =
271 string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
272 localhost,
273 class,
274 option.job or "LuaSocket",
275 user,
276 fmt, lpfile,
277 lpfile,
278 ctlfn); -- mandatory part of ctl file
279 if (option.banner) then cfile = cfile .. 'L'..user..'\10' end
280 if (option.indent) then cfile = cfile .. 'I'..base.tonumber(option.indent)..'\10' end
281 if (option.mail) then cfile = cfile .. 'M'..string.sub((option.mail),1,128)..'\10' end
282 if (fmt == 'p' and option.title) then cfile = cfile .. 'T'..string.sub((option.title),1,79)..'\10' end
283 if ((fmt == 'p' or fmt == 'l' or fmt == 'f') and option.width) then
284 cfile = cfile .. 'W'..base.tonumber(option,width)..'\10'
285 end
286
287 con.skt:settimeout(option.timeout or 65)
288-- send the queue header
289 send_queue(con, option.queue)
290-- send the control file header
291 local cfilecmd = string.format("\2%d cfA%3.3d%-s\n",string.len(cfile), jobno, localhost);
292 send_hdr(con,cfilecmd)
293
294-- send the control file
295 send_control(con,cfile)
296
297-- send the data file header
298 local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
299 send_hdr(con,dfilecmd)
300
301-- send the data file
302 send_data(con,fh,datafile_size)
303 fh:close()
304 con.skt:close();
305 return jobno, datafile_size
306end)
307
308--
309-- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
310--
311query = socket.protect(function(p)
312 p = p or {}
313 local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
314 or "localhost"
315 local con = connect(localhost,p)
316 local fmt
317 if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
318 con.try(con.skt:send(string.format("%c%s %s\n", fmt, p.queue or "*",
319 p.list or "")))
320 local data = con.try(con.skt:receive("*a"))
321 con.skt:close()
322 return data
323end)
diff --git a/etc/qp.lua b/etc/qp.lua
deleted file mode 100644
index 523238b..0000000
--- a/etc/qp.lua
+++ /dev/null
@@ -1,23 +0,0 @@
1-----------------------------------------------------------------------------
2-- Little program to convert to and from Quoted-Printable
3-- LuaSocket sample files
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6local ltn12 = require("ltn12")
7local mime = require("mime")
8local convert
9arg = arg or {}
10local mode = arg and arg[1] or "-et"
11if mode == "-et" then
12 local normalize = mime.normalize()
13 local qp = mime.encode("quoted-printable")
14 local wrap = mime.wrap("quoted-printable")
15 convert = ltn12.filter.chain(normalize, qp, wrap)
16elseif mode == "-eb" then
17 local qp = mime.encode("quoted-printable", "binary")
18 local wrap = mime.wrap("quoted-printable")
19 convert = ltn12.filter.chain(qp, wrap)
20else convert = mime.decode("quoted-printable") end
21local source = ltn12.source.chain(ltn12.source.file(io.stdin), convert)
22local sink = ltn12.sink.file(io.stdout)
23ltn12.pump.all(source, sink)
diff --git a/etc/tftp.lua b/etc/tftp.lua
deleted file mode 100644
index ed99cd1..0000000
--- a/etc/tftp.lua
+++ /dev/null
@@ -1,154 +0,0 @@
1-----------------------------------------------------------------------------
2-- TFTP support for the Lua language
3-- LuaSocket toolkit.
4-- Author: Diego Nehab
5-----------------------------------------------------------------------------
6
7-----------------------------------------------------------------------------
8-- Load required files
9-----------------------------------------------------------------------------
10local base = _G
11local table = require("table")
12local math = require("math")
13local string = require("string")
14local socket = require("socket")
15local ltn12 = require("ltn12")
16local url = require("socket.url")
17module("socket.tftp")
18
19-----------------------------------------------------------------------------
20-- Program constants
21-----------------------------------------------------------------------------
22local char = string.char
23local byte = string.byte
24
25PORT = 69
26local OP_RRQ = 1
27local OP_WRQ = 2
28local OP_DATA = 3
29local OP_ACK = 4
30local OP_ERROR = 5
31local OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"}
32
33-----------------------------------------------------------------------------
34-- Packet creation functions
35-----------------------------------------------------------------------------
36local function RRQ(source, mode)
37 return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
38end
39
40local function WRQ(source, mode)
41 return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
42end
43
44local function ACK(block)
45 local low, high
46 low = math.mod(block, 256)
47 high = (block - low)/256
48 return char(0, OP_ACK, high, low)
49end
50
51local function get_OP(dgram)
52 local op = byte(dgram, 1)*256 + byte(dgram, 2)
53 return op
54end
55
56-----------------------------------------------------------------------------
57-- Packet analysis functions
58-----------------------------------------------------------------------------
59local function split_DATA(dgram)
60 local block = byte(dgram, 3)*256 + byte(dgram, 4)
61 local data = string.sub(dgram, 5)
62 return block, data
63end
64
65local function get_ERROR(dgram)
66 local code = byte(dgram, 3)*256 + byte(dgram, 4)
67 local msg
68 _,_, msg = string.find(dgram, "(.*)\000", 5)
69 return string.format("error code %d: %s", code, msg)
70end
71
72-----------------------------------------------------------------------------
73-- The real work
74-----------------------------------------------------------------------------
75local function tget(gett)
76 local retries, dgram, sent, datahost, dataport, code
77 local last = 0
78 socket.try(gett.host, "missing host")
79 local con = socket.try(socket.udp())
80 local try = socket.newtry(function() con:close() end)
81 -- convert from name to ip if needed
82 gett.host = try(socket.dns.toip(gett.host))
83 con:settimeout(1)
84 -- first packet gives data host/port to be used for data transfers
85 local path = string.gsub(gett.path or "", "^/", "")
86 path = url.unescape(path)
87 retries = 0
88 repeat
89 sent = try(con:sendto(RRQ(path, "octet"), gett.host, gett.port))
90 dgram, datahost, dataport = con:receivefrom()
91 retries = retries + 1
92 until dgram or datahost ~= "timeout" or retries > 5
93 try(dgram, datahost)
94 -- associate socket with data host/port
95 try(con:setpeername(datahost, dataport))
96 -- default sink
97 local sink = gett.sink or ltn12.sink.null()
98 -- process all data packets
99 while 1 do
100 -- decode packet
101 code = get_OP(dgram)
102 try(code ~= OP_ERROR, get_ERROR(dgram))
103 try(code == OP_DATA, "unhandled opcode " .. code)
104 -- get data packet parts
105 local block, data = split_DATA(dgram)
106 -- if not repeated, write
107 if block == last+1 then
108 try(sink(data))
109 last = block
110 end
111 -- last packet brings less than 512 bytes of data
112 if string.len(data) < 512 then
113 try(con:send(ACK(block)))
114 try(con:close())
115 try(sink(nil))
116 return 1
117 end
118 -- get the next packet
119 retries = 0
120 repeat
121 sent = try(con:send(ACK(last)))
122 dgram, err = con:receive()
123 retries = retries + 1
124 until dgram or err ~= "timeout" or retries > 5
125 try(dgram, err)
126 end
127end
128
129local default = {
130 port = PORT,
131 path ="/",
132 scheme = "tftp"
133}
134
135local function parse(u)
136 local t = socket.try(url.parse(u, default))
137 socket.try(t.scheme == "tftp", "invalid scheme '" .. t.scheme .. "'")
138 socket.try(t.host, "invalid host")
139 return t
140end
141
142local function sget(u)
143 local gett = parse(u)
144 local t = {}
145 gett.sink = ltn12.sink.table(t)
146 tget(gett)
147 return table.concat(t)
148end
149
150get = socket.protect(function(gett)
151 if base.type(gett) == "string" then return sget(gett)
152 else return tget(gett) end
153end)
154