You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
4.0 KiB
Lua
181 lines
4.0 KiB
Lua
1 year ago
|
-- Copyright (c) 2009, 2010, 2011, 2012 by Moritz Wilhelmy
|
||
|
-- Copyright (c) 2009 by Kristofer Karlsson
|
||
|
-- Public domain lua-module for handling bittorrent-bencoded data.
|
||
|
-- This module includes both a recursive decoder and a recursive encoder.
|
||
|
|
||
|
local sort, concat, insert = table.sort, table.concat, table.insert
|
||
|
local pairs, ipairs, type, tonumber = pairs, ipairs, type, tonumber
|
||
|
local sub, find = string.sub, string.find
|
||
|
|
||
|
local M = {}
|
||
|
|
||
|
-- helpers
|
||
|
|
||
|
local function islist(t)
|
||
|
local n = #t
|
||
|
for k, v in pairs(t) do
|
||
|
if type(k) ~= "number"
|
||
|
or k % 1 ~= 0 -- integer?
|
||
|
or k < 1
|
||
|
or k > n
|
||
|
then
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
for i = 1, n do
|
||
|
if t[i] == nil then
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- encoder functions
|
||
|
|
||
|
local encode_rec -- encode_list/dict and encode_rec are mutually recursive...
|
||
|
|
||
|
local function encode_list(t, x)
|
||
|
|
||
|
insert(t, "l")
|
||
|
|
||
|
for _,v in ipairs(x) do
|
||
|
local err,ev = encode_rec(t, v); if err then return err,ev end
|
||
|
end
|
||
|
|
||
|
insert(t, "e")
|
||
|
end
|
||
|
|
||
|
local function encode_dict(t, x)
|
||
|
insert(t, "d")
|
||
|
-- bittorrent requires the keys to be sorted.
|
||
|
local sortedkeys = {}
|
||
|
for k, v in pairs(x) do
|
||
|
if type(k) ~= "string" then
|
||
|
return "bencoding requires dictionary keys to be strings", k
|
||
|
end
|
||
|
insert(sortedkeys, k)
|
||
|
end
|
||
|
sort(sortedkeys)
|
||
|
|
||
|
for k, v in ipairs(sortedkeys) do
|
||
|
local err,ev = encode_rec(t, v); if err then return err,ev end
|
||
|
err,ev = encode_rec(t, x[v]); if err then return err,ev end
|
||
|
end
|
||
|
insert(t, "e")
|
||
|
end
|
||
|
|
||
|
local function encode_int(t, x)
|
||
|
|
||
|
if x % 1 ~= 0 then return "number is not an integer", x end
|
||
|
insert(t, "i" )
|
||
|
insert(t, x )
|
||
|
insert(t, "e" )
|
||
|
end
|
||
|
|
||
|
local function encode_str(t, x)
|
||
|
|
||
|
insert(t, #x )
|
||
|
insert(t, ":" )
|
||
|
insert(t, x )
|
||
|
end
|
||
|
|
||
|
encode_rec = function(t, x)
|
||
|
|
||
|
local typx = type(x)
|
||
|
if typx == "string" then return encode_str (t, x)
|
||
|
elseif typx == "number" then return encode_int (t, x)
|
||
|
elseif typx == "table" then
|
||
|
|
||
|
if islist(x) then return encode_list (t, x)
|
||
|
else return encode_dict (t, x)
|
||
|
end
|
||
|
else
|
||
|
return "type cannot be converted to an acceptable type for bencoding", typx
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- call recursive bencoder function with empty table, stringify that table.
|
||
|
-- this is the only encode* function visible to module users.
|
||
|
M.encode = function (x)
|
||
|
|
||
|
local t = {}
|
||
|
local err, val = encode_rec(t,x)
|
||
|
if not err then
|
||
|
return concat(t)
|
||
|
else
|
||
|
return nil, err, val
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- decoder functions
|
||
|
|
||
|
local function decode_integer(s, index)
|
||
|
local a, b, int = find(s, "^(%-?%d+)e", index)
|
||
|
if not int then return nil, "not a number", nil end
|
||
|
int = tonumber(int)
|
||
|
if not int then return nil, "not a number", int end
|
||
|
return int, b + 1
|
||
|
end
|
||
|
|
||
|
local function decode_list(s, index)
|
||
|
local t = {}
|
||
|
while sub(s, index, index) ~= "e" do
|
||
|
local obj, ev
|
||
|
obj, index, ev = M.decode(s, index)
|
||
|
if not obj then return obj, index, ev end
|
||
|
insert(t, obj)
|
||
|
end
|
||
|
index = index + 1
|
||
|
return t, index
|
||
|
end
|
||
|
|
||
|
local function decode_dictionary(s, index)
|
||
|
local t = {}
|
||
|
while sub(s, index, index) ~= "e" do
|
||
|
local obj1, obj2, ev
|
||
|
|
||
|
obj1, index, ev = M.decode(s, index)
|
||
|
if not obj1 then return obj1, index, ev end
|
||
|
|
||
|
obj2, index, ev = M.decode(s, index)
|
||
|
if not obj2 then return obj2, index, ev end
|
||
|
|
||
|
t[obj1] = obj2
|
||
|
end
|
||
|
index = index + 1
|
||
|
return t, index
|
||
|
end
|
||
|
|
||
|
local function decode_string(s, index)
|
||
|
local a, b, len = find(s, "^([0-9]+):", index)
|
||
|
if not len then return nil, "not a length", len end
|
||
|
index = b + 1
|
||
|
|
||
|
local v = sub(s, index, index + len - 1)
|
||
|
if #v < tonumber(len) then return nil, "truncated string at end of input", v end
|
||
|
index = index + len
|
||
|
return v, index
|
||
|
end
|
||
|
|
||
|
|
||
|
M.decode = function (s, index)
|
||
|
if not s then return nil, "no data", nil end
|
||
|
index = index or 1
|
||
|
local t = sub(s, index, index)
|
||
|
if not t then return nil, "truncation error", nil end
|
||
|
|
||
|
if t == "i" then
|
||
|
return decode_integer(s, index + 1)
|
||
|
elseif t == "l" then
|
||
|
return decode_list(s, index + 1)
|
||
|
elseif t == "d" then
|
||
|
return decode_dictionary(s, index + 1)
|
||
|
elseif t >= '0' and t <= '9' then
|
||
|
return decode_string(s, index)
|
||
|
else
|
||
|
return nil, "invalid type", t
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return M
|