diff options
Diffstat (limited to 'samples')
| -rw-r--r-- | samples/README | 90 | ||||
| -rw-r--r-- | samples/b64.lua | 19 | ||||
| -rw-r--r-- | samples/cddb.lua | 2 | ||||
| -rw-r--r-- | samples/check-links.lua | 111 | ||||
| -rw-r--r-- | samples/check-memory.lua | 17 | ||||
| -rw-r--r-- | samples/cookie.lua | 88 | ||||
| -rw-r--r-- | samples/dict.lua | 151 | ||||
| -rw-r--r-- | samples/dispatch.lua | 307 | ||||
| -rw-r--r-- | samples/eol.lua | 13 | ||||
| -rw-r--r-- | samples/forward.lua | 65 | ||||
| -rw-r--r-- | samples/get.lua | 141 | ||||
| -rw-r--r-- | samples/links | 17 | ||||
| -rw-r--r-- | samples/lp.lua | 323 | ||||
| -rw-r--r-- | samples/qp.lua | 23 | ||||
| -rw-r--r-- | samples/tftp.lua | 154 |
15 files changed, 1517 insertions, 4 deletions
diff --git a/samples/README b/samples/README index e63a6f5..4ee06b6 100644 --- a/samples/README +++ b/samples/README | |||
| @@ -1,11 +1,95 @@ | |||
| 1 | This directory contains some sample programs using | 1 | This directory contains some sample programs using |
| 2 | LuaSocket. This code is not supported. | 2 | LuaSocket. This code is not supported. |
| 3 | 3 | ||
| 4 | tftp.lua -- Trivial FTP client | ||
| 5 | |||
| 6 | This module implements file retrieval by the TFTP protocol. | ||
| 7 | Its main use was to test the UDP code, but since someone | ||
| 8 | found it usefull, I turned it into a module that is almost | ||
| 9 | official (no uploads, yet). | ||
| 10 | |||
| 11 | dict.lua -- Dict client | ||
| 12 | |||
| 13 | The dict.lua module started with a cool simple client | ||
| 14 | for the DICT protocol, written by Luiz Henrique Figueiredo. | ||
| 15 | This new version has been converted into a library, similar | ||
| 16 | to the HTTP and FTP libraries, that can be used from within | ||
| 17 | any luasocket application. Take a look on the source code | ||
| 18 | and you will be able to figure out how to use it. | ||
| 19 | |||
| 20 | lp.lua -- LPD client library | ||
| 21 | |||
| 22 | The lp.lua module implements the client part of the Line | ||
| 23 | Printer Daemon protocol, used to print files on Unix | ||
| 24 | machines. It is courtesy of David Burgess! See the source | ||
| 25 | code and the lpr.lua in the examples directory. | ||
| 26 | |||
| 27 | b64.lua | ||
| 28 | qp.lua | ||
| 29 | eol.lua | ||
| 30 | |||
| 31 | These are tiny programs that perform Base64, | ||
| 32 | Quoted-Printable and end-of-line marker conversions. | ||
| 33 | |||
| 34 | get.lua -- file retriever | ||
| 35 | |||
| 36 | This little program is a client that uses the FTP and | ||
| 37 | HTTP code to implement a command line file graber. Just | ||
| 38 | run | ||
| 39 | |||
| 40 | lua get.lua <remote-file> [<local-file>] | ||
| 41 | |||
| 42 | to download a remote file (either ftp:// or http://) to | ||
| 43 | the specified local file. The program also prints the | ||
| 44 | download throughput, elapsed time, bytes already downloaded | ||
| 45 | etc during download. | ||
| 46 | |||
| 47 | check-memory.lua -- checks memory consumption | ||
| 48 | |||
| 49 | This is just to see how much memory each module uses. | ||
| 50 | |||
| 51 | dispatch.lua -- coroutine based dispatcher | ||
| 52 | |||
| 53 | This is a first try at a coroutine based non-blocking | ||
| 54 | dispatcher for LuaSocket. Take a look at 'check-links.lua' | ||
| 55 | and at 'forward.lua' to see how to use it. | ||
| 56 | |||
| 57 | check-links.lua -- HTML link checker program | ||
| 58 | |||
| 59 | This little program scans a HTML file and checks for broken | ||
| 60 | links. It is similar to check-links.pl by Jamie Zawinski, | ||
| 61 | but uses all facilities of the LuaSocket library and the Lua | ||
| 62 | language. It has not been thoroughly tested, but it should | ||
| 63 | work. Just run | ||
| 64 | |||
| 65 | lua check-links.lua [-n] {<url>} > output | ||
| 66 | |||
| 67 | and open the result to see a list of broken links. Make sure | ||
| 68 | you check the '-n' switch. It runs in non-blocking mode, | ||
| 69 | using coroutines, and is MUCH faster! | ||
| 70 | |||
| 71 | forward.lua -- coroutine based forward server | ||
| 72 | |||
| 73 | This is a forward server that can accept several connections | ||
| 74 | and transfers simultaneously using non-blocking I/O and the | ||
| 75 | coroutine-based dispatcher. You can run, for example | ||
| 76 | |||
| 77 | lua forward.lua 8080:proxy.com:3128 | ||
| 78 | |||
| 79 | to redirect all local conections to port 8080 to the host | ||
| 80 | 'proxy.com' at port 3128. | ||
| 81 | |||
| 82 | unix.c and unix.h | ||
| 83 | |||
| 84 | This is an implementation of Unix local domain sockets and | ||
| 85 | demonstrates how to extend LuaSocket with a new type of | ||
| 86 | transport. It has been tested on Linux and on Mac OS X. | ||
| 87 | |||
| 4 | listener.lua -- socket to stdout | 88 | listener.lua -- socket to stdout |
| 5 | talker.lua -- stdin to socket | 89 | talker.lua -- stdin to socket |
| 6 | 90 | ||
| 7 | listener.lua and talker.lua are about the simplest | 91 | listener.lua and talker.lua are about the simplest |
| 8 | applications you can write using LuaSocket. Run | 92 | applications you can write using LuaSocket. Run |
| 9 | 93 | ||
| 10 | 'lua listener.lua' and 'lua talker.lua' | 94 | 'lua listener.lua' and 'lua talker.lua' |
| 11 | 95 | ||
| @@ -17,13 +101,13 @@ be printed by listen.lua. | |||
| 17 | This is a cool program written by David Burgess to print | 101 | This is a cool program written by David Burgess to print |
| 18 | files using the Line Printer Daemon protocol, widely used in | 102 | files using the Line Printer Daemon protocol, widely used in |
| 19 | Unix machines. It uses the lp.lua implementation, in the | 103 | Unix machines. It uses the lp.lua implementation, in the |
| 20 | etc directory. Just run 'lua lpr.lua <filename> | 104 | samples directory. Just run 'lua lpr.lua <filename> |
| 21 | queue=<printername>' and the file will print! | 105 | queue=<printername>' and the file will print! |
| 22 | 106 | ||
| 23 | cddb.lua -- CDDB client | 107 | cddb.lua -- CDDB client |
| 24 | 108 | ||
| 25 | This is the first try on a simple CDDB client. Not really | 109 | This is the first try on a simple CDDB client. Not really |
| 26 | useful, but one day it might become a module. | 110 | useful, but one day it might become a module. |
| 27 | 111 | ||
| 28 | daytimeclnt.lua -- day time client | 112 | daytimeclnt.lua -- day time client |
| 29 | 113 | ||
diff --git a/samples/b64.lua b/samples/b64.lua new file mode 100644 index 0000000..11eeb2d --- /dev/null +++ b/samples/b64.lua | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Little program to convert to and from Base64 | ||
| 3 | -- LuaSocket sample files | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | local ltn12 = require("ltn12") | ||
| 7 | local mime = require("mime") | ||
| 8 | local source = ltn12.source.file(io.stdin) | ||
| 9 | local sink = ltn12.sink.file(io.stdout) | ||
| 10 | local convert | ||
| 11 | if arg and arg[1] == '-d' then | ||
| 12 | convert = mime.decode("base64") | ||
| 13 | else | ||
| 14 | local base64 = mime.encode("base64") | ||
| 15 | local wrap = mime.wrap() | ||
| 16 | convert = ltn12.filter.chain(base64, wrap) | ||
| 17 | end | ||
| 18 | sink = ltn12.sink.chain(convert, sink) | ||
| 19 | ltn12.pump.all(source, sink) | ||
diff --git a/samples/cddb.lua b/samples/cddb.lua index 49a1871..59d5a44 100644 --- a/samples/cddb.lua +++ b/samples/cddb.lua | |||
| @@ -26,7 +26,7 @@ function parse(body) | |||
| 26 | data[key] = value | 26 | data[key] = value |
| 27 | end | 27 | end |
| 28 | end | 28 | end |
| 29 | return data, code, message | 29 | return data, code, message |
| 30 | end | 30 | end |
| 31 | 31 | ||
| 32 | local host = socket.dns.gethostname() | 32 | local host = socket.dns.gethostname() |
diff --git a/samples/check-links.lua b/samples/check-links.lua new file mode 100644 index 0000000..283f3ac --- /dev/null +++ b/samples/check-links.lua | |||
| @@ -0,0 +1,111 @@ | |||
| 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 | ----------------------------------------------------------------------------- | ||
| 7 | local url = require("socket.url") | ||
| 8 | local dispatch = require("dispatch") | ||
| 9 | local http = require("socket.http") | ||
| 10 | dispatch.TIMEOUT = 10 | ||
| 11 | |||
| 12 | -- make sure the user knows how to invoke us | ||
| 13 | arg = arg or {} | ||
| 14 | if #arg < 1 then | ||
| 15 | print("Usage:\n luasocket check-links.lua [-n] {<url>}") | ||
| 16 | exit() | ||
| 17 | end | ||
| 18 | |||
| 19 | -- '-n' means we are running in non-blocking mode | ||
| 20 | if 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") | ||
| 24 | else | ||
| 25 | -- if using blocking I/O, use fake dispatcher interface | ||
| 26 | handler = dispatch.newhandler("sequential") | ||
| 27 | end | ||
| 28 | |||
| 29 | local nthreads = 0 | ||
| 30 | |||
| 31 | -- get the status of a URL using the dispatcher | ||
| 32 | function 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 | ||
| 47 | end | ||
| 48 | |||
| 49 | function 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 | ||
| 57 | end | ||
| 58 | |||
| 59 | function 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 | ||
| 76 | end | ||
| 77 | |||
| 78 | function 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 | ||
| 93 | end | ||
| 94 | |||
| 95 | function 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 | ||
| 103 | end | ||
| 104 | |||
| 105 | for _, address in ipairs(arg) do | ||
| 106 | checklinks(url.absolute("file:", address)) | ||
| 107 | end | ||
| 108 | |||
| 109 | while nthreads > 0 do | ||
| 110 | handler:step() | ||
| 111 | end | ||
diff --git a/samples/check-memory.lua b/samples/check-memory.lua new file mode 100644 index 0000000..7bd984d --- /dev/null +++ b/samples/check-memory.lua | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | function 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") | ||
| 8 | end | ||
| 9 | |||
| 10 | load("socket.url") | ||
| 11 | load("ltn12") | ||
| 12 | load("socket") | ||
| 13 | load("mime") | ||
| 14 | load("socket.tp") | ||
| 15 | load("socket.smtp") | ||
| 16 | load("socket.http") | ||
| 17 | load("socket.ftp") | ||
diff --git a/samples/cookie.lua b/samples/cookie.lua new file mode 100644 index 0000000..fec10a1 --- /dev/null +++ b/samples/cookie.lua | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | local socket = require"socket" | ||
| 2 | local http = require"socket.http" | ||
| 3 | local url = require"socket.url" | ||
| 4 | local ltn12 = require"ltn12" | ||
| 5 | |||
| 6 | local token_class = '[^%c%s%(%)%<%>%@%,%;%:%\\%"%/%[%]%?%=%{%}]' | ||
| 7 | |||
| 8 | local 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 | ||
| 13 | end | ||
| 14 | |||
| 15 | local 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 | ||
| 34 | end | ||
| 35 | |||
| 36 | local 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 | ||
| 57 | end | ||
| 58 | |||
| 59 | local function quote(s) | ||
| 60 | if string.find(s, "[ %,%;]") then return '"' .. s .. '"' | ||
| 61 | else return s end | ||
| 62 | end | ||
| 63 | |||
| 64 | local _empty = {} | ||
| 65 | local 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 | ||
| 87 | end | ||
| 88 | |||
diff --git a/samples/dict.lua b/samples/dict.lua new file mode 100644 index 0000000..8c5b711 --- /dev/null +++ b/samples/dict.lua | |||
| @@ -0,0 +1,151 @@ | |||
| 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 | ----------------------------------------------------------------------------- | ||
| 10 | local base = _G | ||
| 11 | local string = require("string") | ||
| 12 | local table = require("table") | ||
| 13 | local socket = require("socket") | ||
| 14 | local url = require("socket.url") | ||
| 15 | local tp = require("socket.tp") | ||
| 16 | module("socket.dict") | ||
| 17 | |||
| 18 | ----------------------------------------------------------------------------- | ||
| 19 | -- Globals | ||
| 20 | ----------------------------------------------------------------------------- | ||
| 21 | HOST = "dict.org" | ||
| 22 | PORT = 2628 | ||
| 23 | TIMEOUT = 10 | ||
| 24 | |||
| 25 | ----------------------------------------------------------------------------- | ||
| 26 | -- Low-level dict API | ||
| 27 | ----------------------------------------------------------------------------- | ||
| 28 | local metat = { __index = {} } | ||
| 29 | |||
| 30 | function open(host, port) | ||
| 31 | local tp = socket.try(tp.connect(host or HOST, port or PORT, TIMEOUT)) | ||
| 32 | return base.setmetatable({tp = tp}, metat) | ||
| 33 | end | ||
| 34 | |||
| 35 | function metat.__index:greet() | ||
| 36 | return socket.try(self.tp:check(220)) | ||
| 37 | end | ||
| 38 | |||
| 39 | function 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*)"))) | ||
| 43 | end | ||
| 44 | |||
| 45 | function 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") | ||
| 53 | end | ||
| 54 | |||
| 55 | function 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 | ||
| 66 | end | ||
| 67 | |||
| 68 | function 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 | ||
| 83 | end | ||
| 84 | |||
| 85 | function metat.__index:quit() | ||
| 86 | self.tp:command("QUIT") | ||
| 87 | return self:check(221) | ||
| 88 | end | ||
| 89 | |||
| 90 | function metat.__index:close() | ||
| 91 | return self.tp:close() | ||
| 92 | end | ||
| 93 | |||
| 94 | ----------------------------------------------------------------------------- | ||
| 95 | -- High-level dict API | ||
| 96 | ----------------------------------------------------------------------------- | ||
| 97 | local default = { | ||
| 98 | scheme = "dict", | ||
| 99 | host = "dict.org" | ||
| 100 | } | ||
| 101 | |||
| 102 | local function there(f) | ||
| 103 | if f == "" then return nil | ||
| 104 | else return f end | ||
| 105 | end | ||
| 106 | |||
| 107 | local 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 | ||
| 123 | end | ||
| 124 | |||
| 125 | local 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 | ||
| 140 | end | ||
| 141 | |||
| 142 | local function sget(u) | ||
| 143 | local gett = parse(u) | ||
| 144 | return tget(gett) | ||
| 145 | end | ||
| 146 | |||
| 147 | get = socket.protect(function(gett) | ||
| 148 | if base.type(gett) == "string" then return sget(gett) | ||
| 149 | else return tget(gett) end | ||
| 150 | end) | ||
| 151 | |||
diff --git a/samples/dispatch.lua b/samples/dispatch.lua new file mode 100644 index 0000000..2485415 --- /dev/null +++ b/samples/dispatch.lua | |||
| @@ -0,0 +1,307 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- A hacked dispatcher module | ||
| 3 | -- LuaSocket sample files | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | local base = _G | ||
| 7 | local table = require("table") | ||
| 8 | local string = require("string") | ||
| 9 | local socket = require("socket") | ||
| 10 | local coroutine = require("coroutine") | ||
| 11 | module("dispatch") | ||
| 12 | |||
| 13 | -- if too much time goes by without any activity in one of our sockets, we | ||
| 14 | -- just kill it | ||
| 15 | TIMEOUT = 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 | ----------------------------------------------------------------------------- | ||
| 24 | local handlert = {} | ||
| 25 | |||
| 26 | -- default handler is coroutine | ||
| 27 | function newhandler(mode) | ||
| 28 | mode = mode or "coroutine" | ||
| 29 | return handlert[mode]() | ||
| 30 | end | ||
| 31 | |||
| 32 | local function seqstart(self, func) | ||
| 33 | return func() | ||
| 34 | end | ||
| 35 | |||
| 36 | -- sequential handler simply calls the functions and doesn't wrap I/O | ||
| 37 | function handlert.sequential() | ||
| 38 | return { | ||
| 39 | tcp = socket.tcp, | ||
| 40 | start = seqstart | ||
| 41 | } | ||
| 42 | end | ||
| 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 | ||
| 51 | if 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 | ||
| 74 | end | ||
| 75 | |||
| 76 | ----------------------------------------------------------------------------- | ||
| 77 | -- Simple set data structure. O(1) everything. | ||
| 78 | ----------------------------------------------------------------------------- | ||
| 79 | local 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 | }}) | ||
| 101 | end | ||
| 102 | |||
| 103 | ----------------------------------------------------------------------------- | ||
| 104 | -- socket.tcp() wrapper for the coroutine dispatcher | ||
| 105 | ----------------------------------------------------------------------------- | ||
| 106 | local 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) | ||
| 212 | end | ||
| 213 | |||
| 214 | |||
| 215 | ----------------------------------------------------------------------------- | ||
| 216 | -- Our coroutine dispatcher | ||
| 217 | ----------------------------------------------------------------------------- | ||
| 218 | local cometat = { __index = {} } | ||
| 219 | |||
| 220 | function 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 | ||
| 228 | end | ||
| 229 | |||
| 230 | function kick(operation, tcp) | ||
| 231 | operation.cortn[tcp] = nil | ||
| 232 | operation.set:remove(tcp) | ||
| 233 | end | ||
| 234 | |||
| 235 | function 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 | ||
| 245 | end | ||
| 246 | |||
| 247 | function abort(operation, tcp) | ||
| 248 | local cortn = operation.cortn[tcp] | ||
| 249 | if cortn then | ||
| 250 | kick(operation, tcp) | ||
| 251 | coroutine.resume(cortn, "timeout") | ||
| 252 | end | ||
| 253 | end | ||
| 254 | |||
| 255 | -- step through all active cortns | ||
| 256 | function 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 | ||
| 278 | end | ||
| 279 | |||
| 280 | function cometat.__index:start(func) | ||
| 281 | local cortn = coroutine.create(func) | ||
| 282 | schedule(cortn, coroutine.resume(cortn)) | ||
| 283 | end | ||
| 284 | |||
| 285 | function 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) | ||
| 306 | end | ||
| 307 | |||
diff --git a/samples/eol.lua b/samples/eol.lua new file mode 100644 index 0000000..eeaf0ce --- /dev/null +++ b/samples/eol.lua | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Little program to adjust end of line markers. | ||
| 3 | -- LuaSocket sample files | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | local mime = require("mime") | ||
| 7 | local ltn12 = require("ltn12") | ||
| 8 | local marker = '\n' | ||
| 9 | if arg and arg[1] == '-d' then marker = '\r\n' end | ||
| 10 | local filter = mime.normalize(marker) | ||
| 11 | local source = ltn12.source.chain(ltn12.source.file(io.stdin), filter) | ||
| 12 | local sink = ltn12.sink.file(io.stdout) | ||
| 13 | ltn12.pump.all(source, sink) | ||
diff --git a/samples/forward.lua b/samples/forward.lua new file mode 100644 index 0000000..05ced1a --- /dev/null +++ b/samples/forward.lua | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | -- load our favourite library | ||
| 2 | local dispatch = require("dispatch") | ||
| 3 | local handler = dispatch.newhandler() | ||
| 4 | |||
| 5 | -- make sure the user knows how to invoke us | ||
| 6 | if #arg < 1 then | ||
| 7 | print("Usage") | ||
| 8 | print(" lua forward.lua <iport:ohost:oport> ...") | ||
| 9 | os.exit(1) | ||
| 10 | end | ||
| 11 | |||
| 12 | -- function to move data from one socket to the other | ||
| 13 | local 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 | ||
| 26 | end | ||
| 27 | |||
| 28 | -- for each tunnel, start a new server | ||
| 29 | for 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) | ||
| 60 | end | ||
| 61 | |||
| 62 | -- simply loop stepping the server | ||
| 63 | while 1 do | ||
| 64 | handler:step() | ||
| 65 | end | ||
diff --git a/samples/get.lua b/samples/get.lua new file mode 100644 index 0000000..d53c465 --- /dev/null +++ b/samples/get.lua | |||
| @@ -0,0 +1,141 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Little program to download files from URLs | ||
| 3 | -- LuaSocket sample files | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | local socket = require("socket") | ||
| 7 | local http = require("socket.http") | ||
| 8 | local ftp = require("socket.ftp") | ||
| 9 | local url = require("socket.url") | ||
| 10 | local ltn12 = require("ltn12") | ||
| 11 | |||
| 12 | -- formats a number of seconds into human readable form | ||
| 13 | function 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 | ||
| 29 | end | ||
| 30 | |||
| 31 | -- formats a number of bytes into human readable form | ||
| 32 | function 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) | ||
| 47 | end | ||
| 48 | |||
| 49 | -- returns a string with the current state of the download | ||
| 50 | local remaining_s = "%s received, %s/s throughput, %2.0f%% done, %s remaining" | ||
| 51 | local elapsed_s = "%s received, %s/s throughput, %s elapsed " | ||
| 52 | function 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 | ||
| 61 | end | ||
| 62 | |||
| 63 | -- creates a new instance of a receive_cb that saves to disk | ||
| 64 | -- kind of copied from luasocket's manual callback examples | ||
| 65 | function 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 | ||
| 87 | end | ||
| 88 | |||
| 89 | -- determines the size of a http file | ||
| 90 | function 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 | ||
| 95 | end | ||
| 96 | |||
| 97 | -- downloads a file using the http protocol | ||
| 98 | function 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 | ||
| 104 | end | ||
| 105 | |||
| 106 | -- downloads a file using the ftp protocol | ||
| 107 | function 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 | ||
| 117 | end | ||
| 118 | |||
| 119 | -- determines the scheme | ||
| 120 | function 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 | ||
| 125 | end | ||
| 126 | |||
| 127 | -- gets a file either by http or ftp, saving as <name> | ||
| 128 | function 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 | ||
| 134 | end | ||
| 135 | |||
| 136 | -- main program | ||
| 137 | arg = arg or {} | ||
| 138 | if #arg < 1 then | ||
| 139 | io.write("Usage:\n lua get.lua <remote-url> [<local-file>]\n") | ||
| 140 | os.exit(1) | ||
| 141 | else get(arg[1], arg[2]) end | ||
diff --git a/samples/links b/samples/links new file mode 100644 index 0000000..087f1c0 --- /dev/null +++ b/samples/links | |||
| @@ -0,0 +1,17 @@ | |||
| 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/samples/lp.lua b/samples/lp.lua new file mode 100644 index 0000000..25f0b95 --- /dev/null +++ b/samples/lp.lua | |||
| @@ -0,0 +1,323 @@ | |||
| 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 | ||
| 11 | local io = require("io") | ||
| 12 | local base = _G | ||
| 13 | local os = require("os") | ||
| 14 | local math = require("math") | ||
| 15 | local string = require("string") | ||
| 16 | local socket = require("socket") | ||
| 17 | local ltn12 = require("ltn12") | ||
| 18 | module("socket.lp") | ||
| 19 | |||
| 20 | -- default port | ||
| 21 | PORT = 515 | ||
| 22 | SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost" | ||
| 23 | PRINTER = os.getenv("PRINTER") or "printer" | ||
| 24 | |||
| 25 | local 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 } | ||
| 48 | end | ||
| 49 | |||
| 50 | --[[ | ||
| 51 | RFC 1179 | ||
| 52 | 5.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 | |||
| 69 | 5.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 | ||
| 88 | local function recv_ack(con) | ||
| 89 | local ack = con.skt:receive(1) | ||
| 90 | con.try(string.char(0) == ack, "failed to receive server acknowledgement") | ||
| 91 | end | ||
| 92 | |||
| 93 | -- sends client acknowledement | ||
| 94 | local function send_ack(con) | ||
| 95 | local sent = con.skt:send(string.char(0)) | ||
| 96 | con.try(sent == 1, "failed to send acknowledgement") | ||
| 97 | end | ||
| 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. | ||
| 116 | local 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) | ||
| 122 | end | ||
| 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 | |||
| 173 | local 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) | ||
| 177 | end | ||
| 178 | |||
| 179 | local 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) | ||
| 183 | end | ||
| 184 | |||
| 185 | local 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 | ||
| 200 | end | ||
| 201 | |||
| 202 | |||
| 203 | --[[ | ||
| 204 | local 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 | ||
| 226 | local seq = 0 | ||
| 227 | local function newjob(connection) | ||
| 228 | seq = seq + 1 | ||
| 229 | return math.floor(socket.gettime() * 1000 + seq)%1000 | ||
| 230 | end | ||
| 231 | |||
| 232 | |||
| 233 | local 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 | |||
| 249 | send = 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 | ||
| 306 | end) | ||
| 307 | |||
| 308 | -- | ||
| 309 | -- lp.query({host=,queue=printer|'*', format='l'|'s', list=}) | ||
| 310 | -- | ||
| 311 | query = 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 | ||
| 323 | end) | ||
diff --git a/samples/qp.lua b/samples/qp.lua new file mode 100644 index 0000000..523238b --- /dev/null +++ b/samples/qp.lua | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Little program to convert to and from Quoted-Printable | ||
| 3 | -- LuaSocket sample files | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | local ltn12 = require("ltn12") | ||
| 7 | local mime = require("mime") | ||
| 8 | local convert | ||
| 9 | arg = arg or {} | ||
| 10 | local mode = arg and arg[1] or "-et" | ||
| 11 | if 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) | ||
| 16 | elseif 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) | ||
| 20 | else convert = mime.decode("quoted-printable") end | ||
| 21 | local source = ltn12.source.chain(ltn12.source.file(io.stdin), convert) | ||
| 22 | local sink = ltn12.sink.file(io.stdout) | ||
| 23 | ltn12.pump.all(source, sink) | ||
diff --git a/samples/tftp.lua b/samples/tftp.lua new file mode 100644 index 0000000..ed99cd1 --- /dev/null +++ b/samples/tftp.lua | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- TFTP support for the Lua language | ||
| 3 | -- LuaSocket toolkit. | ||
| 4 | -- Author: Diego Nehab | ||
| 5 | ----------------------------------------------------------------------------- | ||
| 6 | |||
| 7 | ----------------------------------------------------------------------------- | ||
| 8 | -- Load required files | ||
| 9 | ----------------------------------------------------------------------------- | ||
| 10 | local base = _G | ||
| 11 | local table = require("table") | ||
| 12 | local math = require("math") | ||
| 13 | local string = require("string") | ||
| 14 | local socket = require("socket") | ||
| 15 | local ltn12 = require("ltn12") | ||
| 16 | local url = require("socket.url") | ||
| 17 | module("socket.tftp") | ||
| 18 | |||
| 19 | ----------------------------------------------------------------------------- | ||
| 20 | -- Program constants | ||
| 21 | ----------------------------------------------------------------------------- | ||
| 22 | local char = string.char | ||
| 23 | local byte = string.byte | ||
| 24 | |||
| 25 | PORT = 69 | ||
| 26 | local OP_RRQ = 1 | ||
| 27 | local OP_WRQ = 2 | ||
| 28 | local OP_DATA = 3 | ||
| 29 | local OP_ACK = 4 | ||
| 30 | local OP_ERROR = 5 | ||
| 31 | local OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"} | ||
| 32 | |||
| 33 | ----------------------------------------------------------------------------- | ||
| 34 | -- Packet creation functions | ||
| 35 | ----------------------------------------------------------------------------- | ||
| 36 | local function RRQ(source, mode) | ||
| 37 | return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0) | ||
| 38 | end | ||
| 39 | |||
| 40 | local function WRQ(source, mode) | ||
| 41 | return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0) | ||
| 42 | end | ||
| 43 | |||
| 44 | local 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) | ||
| 49 | end | ||
| 50 | |||
| 51 | local function get_OP(dgram) | ||
| 52 | local op = byte(dgram, 1)*256 + byte(dgram, 2) | ||
| 53 | return op | ||
| 54 | end | ||
| 55 | |||
| 56 | ----------------------------------------------------------------------------- | ||
| 57 | -- Packet analysis functions | ||
| 58 | ----------------------------------------------------------------------------- | ||
| 59 | local 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 | ||
| 63 | end | ||
| 64 | |||
| 65 | local 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) | ||
| 70 | end | ||
| 71 | |||
| 72 | ----------------------------------------------------------------------------- | ||
| 73 | -- The real work | ||
| 74 | ----------------------------------------------------------------------------- | ||
| 75 | local 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 | ||
| 127 | end | ||
| 128 | |||
| 129 | local default = { | ||
| 130 | port = PORT, | ||
| 131 | path ="/", | ||
| 132 | scheme = "tftp" | ||
| 133 | } | ||
| 134 | |||
| 135 | local 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 | ||
| 140 | end | ||
| 141 | |||
| 142 | local 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) | ||
| 148 | end | ||
| 149 | |||
| 150 | get = socket.protect(function(gett) | ||
| 151 | if base.type(gett) == "string" then return sget(gett) | ||
| 152 | else return tget(gett) end | ||
| 153 | end) | ||
| 154 | |||
