├── LuaService.exe ├── binstd.dll ├── info.url ├── init.lua ├── install.cmd ├── lua ├── compact_encoding.lua ├── dht.lua ├── encoding.lua ├── g2.lua ├── http.lua ├── ltn12.lua ├── magnet_to_uri_res.lua ├── mime.lua ├── nodes.lua ├── nodes2.lua ├── print_override.lua ├── serialize2.lua ├── serialize3.lua ├── socket.lua ├── socket │ ├── ftp.lua │ ├── http.lua │ ├── smtp.lua │ ├── tp.lua │ └── url.lua ├── string2.lua ├── torred.lua └── util.lua ├── lua5.1.dll ├── lua5.1.exe ├── magnet_to_uri_res.cmd ├── msgs.txt ├── nodes.tbl ├── rhash.exe ├── settings.lua ├── socket └── core.dll ├── start.cmd └── uninstall.cmd /LuaService.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/LuaService.exe -------------------------------------------------------------------------------- /binstd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/binstd.dll -------------------------------------------------------------------------------- /info.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=http://localhost:6543/ 3 | Modified=C028B49309F9CA01FC 4 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- init.lua for the LuaDHT service 2 | return { 3 | tracelevel = 0, -- Framework trace level 4 | name = "LuaDHT", -- Service name for SCM 5 | script = "lua/dht.lua", -- Script that runs the service 6 | } -------------------------------------------------------------------------------- /install.cmd: -------------------------------------------------------------------------------- 1 | %~d0 2 | cd %~p0 3 | copy nodes.tbl "%SYSTEMROOT%\system32\nodes.tbl" 4 | LuaService -i 5 | info.url 6 | pause -------------------------------------------------------------------------------- /lua/compact_encoding.lua: -------------------------------------------------------------------------------- 1 | function decode_ip(data, start_index) 2 | local start_index = start_index or 1 3 | return ( data:byte(start_index).."."..data:byte(start_index+1).."."..data:byte(start_index+2).."."..data:byte(start_index+3) ) 4 | end 5 | 6 | function decode_port(data, start_index) 7 | local start_index = start_index or 1 8 | return ( data:byte(start_index)*256 + data:byte(start_index+1) ) 9 | end 10 | 11 | function decode_peer(data, start_index) 12 | local start_index = start_index or 1 13 | 14 | return decode_ip(data, start_index), decode_port(data, start_index + 4) 15 | end 16 | 17 | function read_ip(ip) 18 | return string.match(ip, "([0-9]+).([0-9]+).([0-9]+).([0-9]+)") 19 | end 20 | 21 | function ipv4_array(address) 22 | local ip = {} 23 | ip[1], ip[2], ip[3], ip[4] = read_ip(address) 24 | if ip[1] then 25 | ip[1], ip[2], ip[3], ip[4] = tonumber(ip[1]), tonumber(ip[2]), tonumber(ip[3]), tonumber(ip[4]) 26 | return ip 27 | end 28 | end 29 | 30 | function encode_ipv4(ip, swap) 31 | local a1, a2, a3, a4 = read_ip(ip) 32 | if swap then 33 | return string.char(a4,a3,a2,a1) 34 | else 35 | return string.char(a1,a2,a3,a4) 36 | end 37 | end 38 | 39 | function encode_port(port, le) 40 | local p1 = math.mod(port, 256) 41 | local p2 = (port - p1) / 256 42 | if le then p1, p2 = p2, p1 end 43 | return string.char(p2, p1) 44 | end 45 | 46 | 47 | function encode_peer_le(address, port) 48 | return encode_ipv4(address)..encode_port(port, true) 49 | end 50 | 51 | function encode_peer(address, port, le) 52 | return encode_ipv4(address)..encode_port(port) 53 | end 54 | 55 | function decode_node(data, node_index) 56 | node_index = node_index or 1 57 | local s = (26 * node_index) - 25 58 | if #data < (s + 25) then return nil end 59 | return data:sub(s, s + 19), decode_peer(data, s+20) 60 | end 61 | 62 | function encode_node(node) 63 | return node.id..encode_peer(node.address, node.port) 64 | end -------------------------------------------------------------------------------- /lua/dht.lua: -------------------------------------------------------------------------------- 1 | require("settings") 2 | require("print_override") 3 | 4 | require("socket") 5 | require("encoding") 6 | require("torred") 7 | require("serialize3") 8 | require("compact_encoding") 9 | require("nodes") 10 | require("http") 11 | require("util") 12 | 13 | function statistic_distance(id1 , id2) 14 | return xor(id1:byte(1), id2:byte(1)) * (256^3) + xor(id1:byte(2), id2:byte(2)) * (256^2) + xor(id1:byte(3), id2:byte(3)) * 256 + xor(id1:byte(4), id2:byte(4)) 15 | end 16 | 17 | local help = { 18 | ["a.name"]="name of torrent", 19 | ["q"]=[=[q=[help, ping, find_node, get_peers, announce_peer] 20 | help - Micro help about new parameter of dht packet. 21 | a.path (query packet) - path to new parameter 22 | r.help (responce packet) - help about new parameter 23 | 24 | more: http://forum.bittorrent.org/viewtopic.php?pid=2134 25 | 26 | other see on http://bittorrent.org/beps/bep_0005.html]=], 27 | ["a.path"]=[[if (query packet) and (q="help") then it path to new parameter]], 28 | ["r.help"]=[[if (responce packet) and (q="help") then it help text about new parameter]], 29 | ["r.ip"]=[[your ip]] 30 | } 31 | 32 | function get_help(path) 33 | if type(path)== "table" then 34 | path = table.concat(path, ".") 35 | end 36 | return help[path] 37 | end 38 | 39 | math.randomseed(os.time()) 40 | 41 | my_ip = "" 42 | my_id = random_part(9).."<<< ^-^ >>>" 43 | 44 | 45 | --my_id_plus = {my_id, "\0"..my_id:sub(2,20), "\127"..my_id:sub(2,20), "\255"..my_id:sub(2,20)} 46 | 47 | peers = {btih={}} 48 | torrent_info = {} 49 | search_btih = {} 50 | announce_btih = {} 51 | 52 | in_packet = 0 53 | packet_id = 0 54 | 55 | 56 | ----------------------------------------------------------------------------- 57 | function main() 58 | local loader, err = loadfile(nodes_file) 59 | if not loader then welcome_msg = welcome_msg.."\n"..err end 60 | nodes = (loader and loader()) or nodes 61 | local timer = new_timer() 62 | local save_timer = new_timer() 63 | local check_timer = new_timer() 64 | local random_timer = new_timer() 65 | local udp_port = socket.udp() 66 | udp_port:setsockname("*", port_number) 67 | udp_port:settimeout(1) 68 | 69 | local tcp_port = socket.bind("127.0.0.1", port_number) 70 | tcp_port:settimeout(0.001) 71 | 72 | function on_error() 73 | if udp_port then udp_port:close() end 74 | if tcp_port then tcp_port:close() end 75 | end 76 | 77 | print(welcome_msg) 78 | local cycle = 0 79 | while not ( service and service.stopping() ) do 80 | cycle = cycle + 1 81 | status_print(cycle) 82 | next_packet(udp_port) 83 | next_client(tcp_port, udp_port) 84 | if not service and save_timer(5*60) then 85 | if mgs_on then mid_print("--- save nodes ---") end 86 | save_nodes() 87 | end 88 | if ( in_packet > 10 ) and not nodes_clear then 89 | if mgs_on then mid_print("--- nodes clear on ---") end 90 | nodes_clear = true 91 | end 92 | if random_timer(30) then 93 | if mgs_on then mid_print("--- random bootstrap ---") end 94 | bootstrap(udp_port, random_part(20), true) 95 | collectgarbage() 96 | end 97 | if timer(12) then 98 | if mgs_on then mid_print("--- bootstrap ---") end 99 | bootstrap(udp_port, my_id) 100 | search(udp_port) 101 | announce(udp_port) 102 | end 103 | end 104 | 105 | mid_print("--- save nodes ---") 106 | save_nodes() 107 | udp_port:close() 108 | tcp_port:close() 109 | end 110 | 111 | local cut_list = {128, 64, 32, 16, 8, 4, 2, 1} 112 | function number_cut(number, bits_len) 113 | for i, n in ipairs(cut_list) do 114 | if (8-i >= bits_len) and (number>=n) then 115 | number = number - n 116 | elseif (8-i < bits_len) then 117 | break 118 | end 119 | end 120 | return number 121 | end 122 | 123 | local r = math.random(0, 7) 124 | 125 | function calc_idv4(ip_raw, r) 126 | local ip_cut = {string.byte(ip_raw, 1, 4)} 127 | ip_cut[1] = "\\"..math.fmod(ip_cut[1], 2) 128 | ip_cut[2] = "\\"..number_cut(ip_cut[2], 3) 129 | ip_cut[3] = "\\"..number_cut(ip_cut[3], 5) 130 | ip_cut[4] = "\\"..number_cut(ip_cut[4], 7) 131 | ip_cut[5] = "\\"..r 132 | ip_cut = table.concat(ip_cut) 133 | local command = "lua5.1 -l binstd -e\"io.write('"..ip_cut.."')\"|rhash -p\"%@h\" -" 134 | local x = io.popen(command, "r") 135 | return x:read(4) 136 | end 137 | 138 | function change_id() 139 | if not service then 140 | my_id = calc_idv4(my_ip, r)..string.sub(my_id, 5, 19)..string.char(r) 141 | end 142 | end 143 | 144 | function by_last_seen(node1, node2) 145 | return (node1.last_seen or node1.added) 146 | < (node2.last_seen or node2.added) 147 | end 148 | 149 | 150 | function skip_value(tbl, key, value) 151 | --print(tostring(key)) 152 | return (key == "sended") and (type(value) == "table") or 153 | (key == "bs_send") and (type(value) == "number") or 154 | (key == "last_query") and (type(value) == "table") or 155 | (key == "conected_to") and (type(value) == "string") or 156 | (key == "peer") and (type(value) == "string") or 157 | (key == "confirmed") 158 | end 159 | 160 | function save_nodes() 161 | local fnodes = io.open(nodes_file, "wb") 162 | local writer = function(data) 163 | fnodes:write(data) 164 | end 165 | writer("return ") 166 | serialize(nodes, writer, skip_value) 167 | --fnodes:write(serialize(nodes, "return", nil, nil, skip_value)) 168 | fnodes:close() 169 | end 170 | 171 | function is_searched(check_time) 172 | if check_time then 173 | return (os.time() - check_time) < 3*60 174 | else 175 | return false 176 | end 177 | end 178 | 179 | function check_search_node(node) 180 | return not ( is_searched(node.gp_sended) or ( node.sended and node.sended.packet )) 181 | end 182 | 183 | function search(udp_port) 184 | for info_hash, count in pairs(search_btih) do 185 | if count > 0 then 186 | local selected_nodes = find_close_id(info_hash, 3, check_search_node) 187 | 188 | for index, node in ipairs(selected_nodes) do 189 | node.gp_sended = os.time() 190 | send_get_peers(udp_port, node, info_hash) 191 | end 192 | 193 | search_btih[info_hash] = count - 1 194 | else 195 | search_btih[info_hash] = nil 196 | end 197 | end 198 | end 199 | 200 | function check_announce_node(node, info_hash) 201 | return node.token[info_hash] and not ( node.sended and node.sended.packet ) 202 | end 203 | 204 | function announce(udp_port) 205 | for info_hash, info in pairs( announce_btih ) do 206 | for node, token in pairs( info.token ) do 207 | send_announce(udp_port, node, info_hash, info.token[node], info.port) 208 | info.token[node] = nil 209 | end 210 | end 211 | end 212 | 213 | function announce_peer(info_hash, port) 214 | if port and port > 0 then 215 | if not announce_btih[info_hash] then 216 | announce_btih[info_hash] = { token = {} } 217 | end 218 | announce_btih[info_hash].port = port 219 | else 220 | print("port nil") 221 | end 222 | end 223 | 224 | function search_peers(info_hash) 225 | search_btih[info_hash] = 15 226 | end 227 | 228 | function get_peers(info_hash, port) 229 | search_peers(info_hash) 230 | announce_peer(info_hash, port) 231 | return peers.btih[info_hash] 232 | end 233 | 234 | function local_packet(krpc) 235 | if krpc.y == "q" then 236 | if krpc.q == "get_peers" then 237 | search_peers(krpc.a.info_hash) 238 | return { t = krpc.t, y = "r", r = {values=get_values(krpc.a.info_hash, true)} } 239 | elseif krpc.q == "announce_peer" then 240 | announce_peer(krpc.a.info_hash, krpc.a.port) 241 | end 242 | end 243 | end 244 | 245 | function next_packet(udp_port) 246 | local data, address, port = udp_port:receivefrom() 247 | if (not data) then 248 | if (address ~= "timeout" and address ~= "closed") then 249 | print("receivefrom error: "..address) 250 | debug.debug() 251 | end 252 | return 253 | end 254 | 255 | packet_id = packet_id + 1 256 | if not is_dictionary(data) then return end 257 | if mgs_on then status_print("from_bencode") end 258 | local krpc = from_bencode(data, true) 259 | if not krpc then return end 260 | if mgs_on then status_print("decoded") end 261 | if address == "127.0.0.1" then 262 | local ret = local_packet(krpc) 263 | if ret then 264 | udp_port:sendto(to_bencode(ret), address, port) 265 | end 266 | return 267 | end 268 | 269 | if packet_analyze(krpc) then 270 | --udp_port:sendto(data, address, port) 271 | end 272 | 273 | if krpc.y == "e" then 274 | local node = nodes.ap[address..":"..port] 275 | local sended = node and node.sended and node.sended.packet 276 | if sended and (sended.t == krpc.t) then 277 | node.sended.packet = nil 278 | node.sended.time = nil 279 | node.error = krpc.e 280 | end 281 | if type(krpc.e) == "table" then 282 | print_msg("node error:", krpc.e[1] or '', krpc.e[2] or '') 283 | end 284 | return 285 | end 286 | 287 | if not krpc.t then return end 288 | 289 | local id = (krpc.a and krpc.a.id) or (krpc.r and krpc.r.id) 290 | if not id then return end 291 | 292 | local node = add_node(id, address, port, add_node_check, true) 293 | if not node then return end 294 | node.last_seen = os.time() 295 | 296 | in_packet = in_packet + 1 297 | 298 | if krpc.y == "q" then -- query 299 | return on_query(udp_port, krpc, node) 300 | end 301 | 302 | local packet = node.sended and node.sended.packet 303 | if packet and (krpc.y == "r") and (packet.t == krpc.t) then -- responce 304 | node.sended.packet = nil 305 | node.sended.time = nil 306 | node.error = nil 307 | node.checked = os.time() 308 | return on_responce(krpc, packet, node) 309 | end 310 | end 311 | 312 | function check_node_bootstrap(node, no_linked) 313 | return (node.id ~= my_id) 314 | and (not (node.sended and node.sended.packet)) 315 | and ((node.bs_send or 0) < (os.time() - 600)) 316 | and ((not no_linked) or not node.last_seen) 317 | end 318 | 319 | function bootstrap(udp_port, id, no_linked) 320 | local selected_nodes = find_close_id(id, 4, check_node_bootstrap, {no_linked}) 321 | 322 | for index, node in ipairs(selected_nodes) do 323 | if (not nodes.count) or (nodes.count < (500*index)) or (index == 1) then 324 | node.bs_send = os.time() 325 | send_find_node(udp_port, node, id) 326 | else 327 | send_ping(udp_port, node) 328 | end 329 | end 330 | end 331 | 332 | function is_checked(check_time) 333 | if check_time then 334 | return (os.time() - check_time) < 15*60 335 | else 336 | return false 337 | end 338 | end 339 | 340 | function get_node_token(node) 341 | return global_token.."("..encode_peer(node.address, node.port)..")" 342 | end 343 | 344 | function check_token(token, node) 345 | if mgs_on then 346 | print(string.format("token:\t%s\n client: %s", 347 | safe_string(token), 348 | (safe_string(node.client) or ""))) 349 | end 350 | return token == get_node_token(node) 351 | end 352 | 353 | function check_node_on_query(node, for_node_id) 354 | return (node.id ~= my_id) and (for_node_id ~= node.id) and is_checked( node.last_seen ) 355 | end 356 | 357 | packet_analyze = (function() 358 | -- local const 359 | local top_level_keys = {y = "string", e = "table", q = "string", t = "string", a = "table", r = "table", v = "string", ip = "string"} 360 | local tables_keys = { 361 | y = {q = true, r = true, e = true}, 362 | q = {ping = true, find_node = true, get_peers = true, announce_peer = true, vote = true}, 363 | a = {id = "string", target = "string", info_hash = "string", token = "string", port = "number", want = "table", scrape = "number", name = "string" --[[UTc`]], vote = "number" --[[UTct]], seed = "number" --[[Az]], noseed = "number" --[[LT\0\16]]}, 364 | r = {id = "string", nodes = "string", nodes2 = "table", values = "table", token = "string", ip = "string", p = "number"}, 365 | e = {[1] = "number", [2] = "string"} 366 | } 367 | 368 | return function(packet) 369 | if not packet_analyze_on then return end 370 | for tlk, v in pairs(packet) do 371 | if top_level_keys[tlk] == type(v) then 372 | local keys_types = tables_keys[tlk] 373 | if keys_types then 374 | if type(v) == "table" then 375 | for k, v in pairs(v) do 376 | if keys_types[k] ~= type(v) then 377 | print2('found key: '..safe_string(tlk)..'.'..safe_string(k)) 378 | serialize(packet, nb_out) 379 | flush() 380 | return true 381 | end 382 | end 383 | elseif not keys_types[v] then 384 | print2('found value: '..safe_string(tlk)..' = '..safe_string(v)) 385 | serialize(packet, nb_out) 386 | flush() 387 | return true 388 | end 389 | end 390 | else 391 | print2('found top level key: '..safe_string(tlk)..' = '..safe_string(v)) 392 | serialize(packet, nb_out) 393 | flush() 394 | return true 395 | end 396 | end 397 | end 398 | end)() 399 | 400 | function get_values(info_hash, not_annonced) 401 | local peers_list = peers.btih[info_hash] 402 | if peers_list and next(peers_list) then 403 | local values = {} 404 | local peers_count = 0 405 | for compact, peer in pairs(peers_list) do 406 | if (os.time() - peer.last_seen) > 15*60 then 407 | peers_list[compact] = nil 408 | elseif not_annonced or peer.announce_node then 409 | peers_count = peers_count + 1 410 | table.insert(values, compact) 411 | end 412 | end 413 | 414 | if not next(values) then 415 | peers.btih[info_hash] = nil 416 | else 417 | return values, peers_count 418 | end 419 | end 420 | end 421 | 422 | function on_query(udp_port, query, node) 423 | 424 | local id = query.a.target or query.a.info_hash or query.a.id 425 | if not id then return end 426 | 427 | local tid = query.t 428 | if not tid then return end 429 | 430 | if not node.last_query then node.last_query = {} end 431 | 432 | if query.q ~= "announce_peer" and (os.time() - (node.last_query.time or 0) < 10) then 433 | return 434 | end 435 | 436 | node.client = query.v or node.client 437 | if mgs_on then 438 | print(string.format("%s\t\t\t> query (%s): %s, %s, %s ", 439 | packet_id, 440 | query.q, 441 | statistic_distance(id, my_id), 442 | safe_string(node.client) or "", 443 | ( (node.last_query.time and (os.time() - node.last_query.time)) or "first" ))) 444 | end 445 | node.last_query.query = query.q 446 | node.last_query.id = id 447 | node.last_query.time = os.time() 448 | 449 | 450 | local krpc = { t = tid, y = "r", r = {} } 451 | 452 | if query.q == "ping" then 453 | if node.ping_responce and (os.time() - node.ping_responce < 60) then 454 | if mgs_on then 455 | print("last ping: "..(os.time() - node.ping_responce)) 456 | end 457 | return 458 | end 459 | node.ping_responce = os.time() 460 | return send_krpc( udp_port, node, krpc) 461 | elseif query.q == "help" then 462 | krpc.r.help = get_help(query.a.path) 463 | if not krpc.r.help then 464 | return send_error( udp_port, node, 203, tid, "help not found" ) 465 | end 466 | return send_krpc( udp_port, node, krpc ) 467 | elseif query.q == "announce_peer" then 468 | if mgs_on then print(string.rep("-", 79)) end 469 | if check_token(query.a.token, node) then 470 | node.checked = os.time() 471 | 472 | add_peer(query.a.info_hash, node.address, query.a.port, node) 473 | 474 | add_info(query.a, node) 475 | 476 | if mgs_on then print(string.rep("-", 79)) end 477 | return send_krpc( udp_port, node, krpc ) 478 | else 479 | if mgs_on then print(string.rep("-", 79)) end 480 | return send_error( udp_port, node, 203, tid, "wrong token" ) 481 | end 482 | end 483 | 484 | -- find_node or get_peers -- 485 | 486 | local selected_nodes = find_close_id(id, 8, check_node_on_query, {node.id}) 487 | 488 | local compact_nodes = {} 489 | for index, node in ipairs(selected_nodes) do 490 | table.insert(compact_nodes, encode_node(node)) 491 | end 492 | 493 | if next(compact_nodes) then 494 | krpc.r.nodes = table.concat(compact_nodes) 495 | else 496 | return send_error( udp_port, node, 201, tid, "no nodes" ) 497 | end 498 | 499 | if query.q == "get_peers" then 500 | krpc.r.token = get_node_token(node) 501 | krpc.r.values = get_values(query.a.info_hash) 502 | 503 | if krpc.r.values then 504 | if mgs_on then print_msg("found peers:", #(krpc.r.values), hexenc(id)) end 505 | return send_krpc( udp_port, node, krpc, "responce peers" ) 506 | end 507 | end 508 | 509 | return send_krpc( udp_port, node, krpc, "responce nodes" ) 510 | end 511 | 512 | function add_node_check(id, address, port, node, self) 513 | if self then 514 | node.confirmed = true 515 | node.need_to_check = nil 516 | if node.id ~= id then 517 | node.ref = nil 518 | node.change_id = (node.change_id or 0) + 1 519 | print_msg(string.format("change id (%s)\n from: %s (%s)\n to: %s (%s)\n client: %s", 520 | node.change_id, 521 | hexenc(node.id), 522 | statistic_distance(node.id, my_id), 523 | hexenc(id), 524 | statistic_distance(id, my_id), 525 | safe_string(node.client) or "")) 526 | remove_node_id_ip(node) 527 | node.id = id 528 | elseif node.port ~= port then 529 | node.ref = nil 530 | node.change_port = (node.change_port or 0) + 1 531 | print_msg(string.format("change port (%s) from %s to %s\n for %s (%s)\n client: %s", 532 | node.change_port, 533 | node.port, 534 | port, 535 | hexenc(node.id), 536 | statistic_distance(node.id, my_id), 537 | safe_string(node.client) or "")) 538 | 539 | remove_node_ip_port(node) 540 | node.port = port 541 | end 542 | elseif node.id ~= id then 543 | local msg = "wrong" 544 | if not node.confirmed then 545 | msg = "found other" 546 | node.need_to_check = true 547 | end 548 | 549 | print_msg(string.format("%s id: %s (%s)\n for %s (%s)", 550 | msg, 551 | hexenc(id), 552 | statistic_distance(id, my_id), 553 | hexenc(node.id), 554 | statistic_distance(node.id, my_id))) 555 | elseif node.port ~= port then 556 | local msg = "wrong" 557 | if not node.confirmed then 558 | local msg = "found other" 559 | node.need_to_check = true 560 | end 561 | print_msg(string.format("%s port: %s for %s (%s)", 562 | msg, 563 | port, 564 | hexenc(node.id), 565 | statistic_distance(node.id, my_id))) 566 | end 567 | 568 | if not self then 569 | node.ref = (node.ref or 0) + 1 570 | end 571 | end 572 | 573 | function on_responce(responce, query, node) 574 | local id = query.a.target or query.a.info_hash 575 | 576 | if responce.v then 577 | node.client = responce.v 578 | end 579 | 580 | if responce.ip then 581 | if my_ip ~= responce.ip:sub(1,4) then 582 | my_ip = responce.ip:sub(1,4) 583 | change_id() 584 | if mgs_on then 585 | print(string.format("my_ip = %s", decode_ip(my_ip))) 586 | end 587 | end 588 | end 589 | 590 | --[[ if responce.r.ip and (my_ip ~= responce.r.ip) then 591 | my_ip = responce.r.ip 592 | change_id() 593 | if mgs_on then 594 | print(string.format("my_ip = %s", decode_ip(my_ip))) 595 | end 596 | end]] 597 | 598 | if mgs_on then 599 | print(string.format("%s\t\t\t> responce (%s): %s, %s", 600 | packet_id, 601 | query.q, 602 | statistic_distance(id or my_id, node.id), 603 | safe_string(node.client) or "")) 604 | end 605 | 606 | if (not id) or query.q == "ping" then 607 | free_krpc(query) 608 | return 609 | elseif query.q == "announce_peer" then 610 | node.announced = os.time() 611 | free_krpc(query) 612 | return 613 | elseif query.q == "get_peers" then 614 | if responce.r.token and announce_btih[id] then 615 | announce_btih[id].token[node] = responce.r.token 616 | end 617 | if responce.r.values then 618 | for i, v in pairs(responce.r.values) do 619 | if #v == 6 then 620 | add_peer(query.a.info_hash, decode_peer(v)) 621 | end 622 | end 623 | end 624 | end 625 | 626 | if responce.r.nodes then 627 | count = #responce.r.nodes / 26 628 | for i = 1, count do 629 | local id, address, port = decode_node(responce.r.nodes, i) 630 | if id ~= my_id then 631 | local node1 = add_node(id, address, port, add_node_check) 632 | if node1 then 633 | node1.conected_to = node1.conected_to or node.id 634 | end 635 | end 636 | end 637 | end 638 | 639 | if responce.r.nodes2 then 640 | for i, compact in pairs(responce.r.nodes2) do 641 | if #compact == 26 then 642 | local id, address, port = decode_node(compact) 643 | if id ~= my_id then 644 | local node1 = add_node(id, address, port, add_node_check) 645 | if node1 then 646 | node1.conected_to = node1.conected_to or node.id 647 | end 648 | end 649 | end 650 | end 651 | end 652 | 653 | free_krpc(query) 654 | end 655 | 656 | function add_peer(id, address, port, announce_node) 657 | 658 | local peers_list = peers.btih[id] 659 | if not peers_list then 660 | peers_list = {} 661 | peers.btih[id] = peers_list 662 | end 663 | 664 | local compact = encode_peer(address, port) 665 | local peer = peers_list[compact] 666 | if not peer then 667 | if mgs_on then 668 | print(string.format("new peer:\t%s:%s\t%s", address, port, hexenc(id))) 669 | end 670 | peer = {} 671 | peers_list[compact] = peer 672 | end 673 | 674 | if announce_node then 675 | peer.announce_node = announce_node 676 | announce_node.peer = id 677 | end 678 | 679 | peer.last_seen = os.time() 680 | 681 | return peer 682 | end 683 | 684 | function add_info(info, node) 685 | if info.id then 686 | torrent_info[info.info_hash] = torrent_info[info.info_hash] or {} 687 | if info.name and #(info.name)>0 then 688 | torrent_info[info.info_hash].name = info.name 689 | end 690 | end 691 | end 692 | 693 | --[[ Send ]]-- 694 | 695 | 696 | local send_krpc_const = { 697 | query = { t = "d", o = { "t", "y", "q", "a", "v" } }, 698 | responce = { t = "d", o = { "t", "y", "r", "v" } }, 699 | error = { t = "d", o = { "t", "y", "e", "v" } }, 700 | 701 | ping = { t = "d", o = { "id" } }, 702 | announce_q = { t = "d", o = { "id", "info_hash", "port", "token" } }, 703 | find_node_q = { t = "d", o = { "id", "target", "want" } }, 704 | get_peers_q = { t = "d", o = { "id", "info_hash", "want" } }, 705 | responce_r = { t = "d" , o = { "id", "nodes", "values", "token" } } 706 | } 707 | 708 | function get_id(node) 709 | local id = my_id 710 | if my_id_plus then 711 | for x = 1, #my_id_plus do 712 | if less(my_id_plus[x], id, node.id) then 713 | id = my_id_plus[x] 714 | end 715 | end 716 | end 717 | return id 718 | end 719 | 720 | function send_krpc(udp_port, node, krpc) 721 | 722 | local const = send_krpc_const 723 | 724 | if krpc.e then 725 | 726 | elseif krpc.r then 727 | if not krpc.r.id then 728 | krpc.r.id = get_id(node) 729 | krpc.r.ip = encode_ipv4(node.address) 730 | end 731 | else 732 | 733 | if not krpc.a.id then 734 | krpc.a.id = get_id(node) 735 | end 736 | end 737 | 738 | if not krpc.v then krpc.v = script_id end 739 | 740 | if mgs_on then 741 | print(string.format("send ( %s )\t<", 742 | (krpc.q 743 | or (krpc.r and ((krpc.r.values and "values") or (krpc.r.nodes and "nodes") or "pong")) 744 | or (krpc.e and "error")))) 745 | end 746 | 747 | --packet_analyze(krpc) 748 | 749 | local data = to_bencode(krpc) 750 | 751 | if krpc.q then 752 | local sended = node.sended 753 | if not sended then 754 | sended = {} 755 | node.sended = sended 756 | end 757 | sended.packet = krpc 758 | sended.time = os.time() 759 | end 760 | 761 | udp_port:sendto( data , node.address, node.port ) 762 | socket.sleep(0.03) 763 | end 764 | 765 | function get_random_tid() 766 | return string.char(math.random(0, 255))..string.char(math.random(0, 255))..string.char(math.random(0, 255)) 767 | end 768 | 769 | function send_error(udp_port, node, number, tid, description) 770 | local krpc = { t = tid, y = "e", e = { nubmer, description or "unknown error" } } 771 | send_krpc(udp_port, node, krpc, "error") 772 | end 773 | 774 | local want = { "n4" } 775 | local krpc_buff = {find_node = {}, ping={}} 776 | 777 | 778 | function on_node_removed(node) 779 | if node.sended and node.sended.packet then 780 | free_krpc(node.sended.packet) 781 | end 782 | end 783 | 784 | function free_krpc(krpc) 785 | if krpc.q and krpc.q == "find_node" then 786 | krpc.a.target = nil 787 | table.insert(krpc_buff.find_node, krpc) 788 | --print("find_node_free:", #(krpc_buff.find_node)) 789 | elseif krpc.q and krpc.q == "ping" then 790 | table.insert(krpc_buff.ping, krpc) 791 | --print("ping_free:", #(krpc_buff.ping)) 792 | end 793 | end 794 | 795 | function send_ping(udp_port, node) 796 | local krpc = table.remove(krpc_buff.ping) 797 | if not krpc then 798 | krpc = { t = get_random_tid(), y = "q", q = "ping", a = {} } 799 | end 800 | send_krpc(udp_port, node, krpc) 801 | end 802 | 803 | function send_get_peers(udp_port, node, info_hash) 804 | local krpc = { t = get_random_tid(), y = "q", q = "get_peers", a = { info_hash = info_hash, want = want } } 805 | send_krpc(udp_port, node, krpc) 806 | end 807 | 808 | function send_find_node(udp_port, node, node_id) 809 | local krpc = table.remove(krpc_buff.find_node) 810 | if not krpc then 811 | krpc = { t = get_random_tid(), y = "q", q = "find_node", a = { want = want } } 812 | end 813 | krpc.a.target = node_id 814 | send_krpc(udp_port, node, krpc) 815 | end 816 | 817 | function send_announce(udp_port, node, info_hash, token, port) 818 | local krpc = { t = get_random_tid(), y = "q", q = "announce_peer", 819 | a = { info_hash = info_hash, port = port, token = token } 820 | } 821 | send_krpc(udp_port, node, krpc) 822 | end 823 | ----------------------- 824 | 825 | for i = 1, 10 do 826 | local mainco = coroutine.create(main) 827 | local ok, err = coroutine.resume(mainco) 828 | if ok then 829 | break 830 | else 831 | errfile = io.open(error_log, "ab+") 832 | errfile:write("\n"..debug.traceback(mainco, err)) 833 | errfile:close() 834 | on_error() 835 | end 836 | end 837 | -------------------------------------------------------------------------------- /lua/encoding.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/lua/encoding.lua -------------------------------------------------------------------------------- /lua/g2.lua: -------------------------------------------------------------------------------- 1 | require("socket") 2 | require("serialize2") 3 | require("string2") 4 | require("compact_encoding") 5 | 6 | --bind = socket.bind("127.0.0.1", 6754) 7 | 8 | old_print, print = print, function(...) 9 | local text = table.concat(arg, "\t").."\n" 10 | io.stderr:write(text) 11 | io.stderr:flush() 12 | io.stdout:write(text) 13 | io.stdout:flush() 14 | end 15 | 16 | function bintonumber(binstr, big_endian) 17 | local number = 0 18 | local n = (big_endian and #binstr) or 1 19 | local m = (big_endian and 1) or (-1) 20 | for i = 1, #binstr do 21 | number = number + binstr:byte(i)*(256^(m*(n - i))) 22 | end 23 | 24 | return number 25 | end 26 | 27 | function numbertobin(number, big_endian) 28 | local bin = "" 29 | while (number > 0) do 30 | local last = math.mod(number, 256) 31 | number = (number - last) / 256 32 | 33 | if big_endian then 34 | bin = string.char(last) .. bin 35 | else 36 | bin = bin .. string.char(last) 37 | end 38 | end 39 | 40 | return bin 41 | end 42 | 43 | function readheader(client) 44 | local control_byte = client:receive(1):byte(1) 45 | 46 | if control_byte == 0 then return false, 1 end 47 | 48 | local packet = { 49 | big_endian = false, 50 | compound_packet = false, 51 | name_len = 1, 52 | len_len = 0 53 | } 54 | 55 | local cnt = 0 56 | while (control_byte > 0) do 57 | local bit = math.mod(control_byte, 2) 58 | control_byte = (control_byte - bit)/2 59 | 60 | if bit > 0 then 61 | if cnt == 0 then 62 | return nil, 1 63 | elseif cnt == 1 then 64 | packet.big_endian = true 65 | elseif cnt == 2 then 66 | packet.compound_packet = true 67 | elseif cnt >= 3 and cnt <= 5 then 68 | packet.name_len = packet.name_len + 2^(cnt-3) 69 | elseif cnt >= 6 and cnt <= 7 then 70 | packet.len_len = packet.len_len + 2^(cnt-6) 71 | end 72 | end 73 | 74 | cnt = cnt + 1 75 | end 76 | 77 | packet.len = 0 78 | if packet.len_len > 0 then 79 | local len = client:receive(packet.len_len) 80 | if not len then return end 81 | packet.len = bintonumber(len) 82 | end 83 | 84 | packet.name = client:receive(packet.name_len) 85 | 86 | print(packet.name, packet.len_len, packet.len) 87 | 88 | return packet, 1 + packet.len_len + packet.name_len 89 | end 90 | 91 | function readpacket(client) 92 | local packet, head_len = readheader(client) 93 | 94 | if not packet then return packet, head_len end 95 | 96 | local data_len = packet.len 97 | 98 | if (data_len > 0) and packet.compound_packet then 99 | packet.childs = {} 100 | local child_packet, child_len 101 | 102 | repeat 103 | child_packet, child_len = readpacket(client) 104 | data_len = data_len - child_len 105 | --print(data_len, "data_len") 106 | if child_packet then table.insert(packet.childs, child_packet) end 107 | until not (child_packet and (data_len > 0)) 108 | 109 | if child_packet == nil then return end 110 | end 111 | 112 | if data_len > 0 then packet.data = client:receive(data_len) end 113 | 114 | return packet, head_len + packet.len 115 | end 116 | 117 | function encode_packet(packet) 118 | local control_byte = 0 119 | local buffer = new_string_builder() 120 | 121 | if packet.big_endian then 122 | control_byte = control_byte + 2 123 | end 124 | 125 | if packet.childs then 126 | control_byte = control_byte + 4 127 | for index, child in ipairs(packet.childs) do 128 | local data = encode_packet(child) 129 | assert(data) 130 | buffer.add(data) 131 | end 132 | end 133 | 134 | if packet.data then 135 | if buffer.len() > 0 then 136 | buffer.add("\000") 137 | end 138 | buffer.add(packet.data) 139 | end 140 | 141 | local bin_len = numbertobin(buffer.len(), packet.big_endian) 142 | local len_len = #bin_len 143 | assert(len_len <= 3) 144 | control_byte = control_byte + len_len * (2^6) 145 | 146 | assert(packet.name) 147 | local name_len = #(packet.name) - 1 148 | assert(name_len < 8) 149 | control_byte = control_byte + name_len * (2^3) 150 | 151 | buffer.insert(string.char(control_byte), bin_len, packet.name) 152 | 153 | return buffer.get() 154 | end 155 | 156 | function send_packet(client, packet) 157 | local data = encode_packet(packet) 158 | print("responce =", safestring(data)) 159 | client:send(data) 160 | end 161 | 162 | function g2_main(client) 163 | local state = 1 164 | local line = "" 165 | 166 | function readline(client) 167 | line = client:receive("*l") 168 | return line 169 | end 170 | 171 | while readline(client) do 172 | print(safestring(line)) 173 | 174 | if line == "" then 175 | if state == 1 then 176 | client:send([[ 177 | GNUTELLA/0.6 200 OK 178 | Listen-IP: 127.0.0.1:6754 179 | Remote-IP: ]]..client:getpeername().."\n".. [[ 180 | User-Agent: g2.lua 0.1 181 | Content-Type: application/x-gnutella2 182 | Accept: application/x-gnutella2 183 | X-Hub: True 184 | X-Hub-Needed: False 185 | 186 | ]]) 187 | state = 2 188 | elseif state == 2 then 189 | local packet = readpacket(client) 190 | while packet do 191 | print(serialize(packet, "packet")) 192 | --print (client:getsockname()) 193 | --print (decode_peer(encode_peer(client:getsockname()))) 194 | if packet.name =="LNI" then 195 | send_packet(client, 196 | { 197 | name = "LNI", 198 | childs = { 199 | { 200 | big_endian = true, 201 | name = "NA", 202 | data = encode_peer_le(client:getsockname()) 203 | }, 204 | { 205 | name = "V", 206 | data = "G2LA" 207 | }, 208 | { 209 | name = "GU", 210 | data = "g2.lua gui test1" 211 | }, 212 | { 213 | name = "HS", 214 | data = "\000\000\001\000" 215 | } 216 | } 217 | } 218 | ) 219 | 220 | elseif packet.name =="PI" then 221 | send_packet(client, { name = "PO" }) 222 | elseif packet.name == "Q2" then 223 | local urn, dn, sz, btih 224 | for index, child in ipairs(packet.childs) do 225 | if child.name == "URN" then 226 | urn = child 227 | if child.data:sub(1,2) = "bt" then 228 | btih = child.data:sub(3, 23) 229 | end 230 | end 231 | if child.name == "DN" then 232 | dn = child 233 | end 234 | if child.name == "SZ" then 235 | sz = child 236 | end 237 | end 238 | if urn then 239 | send_packet(client, 240 | { 241 | name = "QH2", 242 | data = "\001"..packet.data, 243 | childs = { 244 | { 245 | big_endian = true, 246 | name = "NA", 247 | data = encode_peer_le(client:getsockname()) 248 | }, 249 | { 250 | name = "GU", 251 | data = "g2.lua gui test1" 252 | }, 253 | { 254 | name = "H", 255 | childs = { 256 | urn, dn, sz, 257 | { 258 | name = "URL", 259 | --data = "http://downloads.sourceforge.net/shareaza/Shareaza_2.5.3.0_Win32.exe" 260 | } 261 | } 262 | } 263 | } 264 | } 265 | ) 266 | end 267 | end 268 | packet = readpacket(client) 269 | -- send responce 270 | end 271 | end 272 | end 273 | end 274 | end 275 | 276 | -------------------------------------------------------------------------------- /lua/http.lua: -------------------------------------------------------------------------------- 1 | http = {} 2 | 3 | function next_client(tcp_port) 4 | local client, err = tcp_port:accept() 5 | 6 | if not client then 7 | if (err ~= "timeout") then 8 | print("accept error: "..err) 9 | debug.debug() 10 | end 11 | return 12 | end 13 | 14 | local request = client:receive("*l") 15 | if not request then client:close() return end 16 | 17 | if string.find(request, "GNUTELLA CONNECT/0.6") then 18 | return g2_main(client) 19 | end 20 | 21 | -- Brouser -- 22 | if string.find(request, " / ") then 23 | return http.main_page(client) 24 | end 25 | 26 | if string.find(request, " /console ") then 27 | to_http(nil, "text/html") 28 | local function print_fnc(request) 29 | if not client:send(request.."\n") then 30 | client:close() 31 | remove_print_fnc(print_fnc) 32 | end 33 | end 34 | add_print_fnc(print_fnc) 35 | return 36 | end 37 | 38 | if string.find(request, " /info ") then 39 | return http.info(client) 40 | end 41 | 42 | if string.find(request, " /map ") then 43 | return http.svg_map(client) 44 | end 45 | 46 | 47 | -- Tracker -- 48 | local info_hash = string.match(request, "[?&]info_hash=([^& ]+)") 49 | if not info_hash then 50 | client:close() 51 | return 52 | else 53 | info_hash = unescape(info_hash) 54 | if #info_hash ~= 20 then 55 | print("#info_hash", #info_hash, info_hash) 56 | client:close() 57 | return 58 | end 59 | end 60 | 61 | local port = string.match(request, "[?&]port=([0-9]+)") 62 | port = (port and tonumber(port)) 63 | local responce 64 | local table_type 65 | 66 | local header = client:receive("*l") 67 | while header and #header > 0 do 68 | local a, p = string.match(header, "Listen%-IP: ([0-9%.]+):([0-9]+)") 69 | port = port or (p and tonumber(p)) 70 | header = client:receive("*l") 71 | end 72 | 73 | 74 | 75 | if string.find(request, " /announce", 1, true) then 76 | print("\t\t\t> http (announce)") 77 | search_peers(info_hash) 78 | announce_peer(info_hash, port) 79 | responce = {interval = 120} 80 | local values = get_values(info_hash, true) 81 | if values then 82 | responce.peers =table.concat(values) 83 | table_type = {[responce] = announce_responce} 84 | end 85 | elseif string.find(request, " /scrape", 1, true) then 86 | print("\t\t\t> http (scrape)") 87 | responce = {[info_hash] = {complete = 0, downloaded = 0, incomplete = 0}} 88 | table_type = {[responce] = scrape_responce, [responce[info_hash]] = scrape_details} 89 | else 90 | if info_hash then 91 | return http.search(client, info_hash, port, request) 92 | else 93 | return http.not_found(client) 94 | end 95 | end 96 | 97 | client:send(to_http(to_bencode(responce, table_type))) 98 | client:close() 99 | end 100 | 101 | announce_responce = { t = "d", o = {"interval", "peers"} } 102 | scrape_responce = { t = "d" } 103 | scrape_details = {t = "d", o = {"complete","downloaded","incomplete"}} 104 | 105 | ferrmsg = function(text) 106 | return "d14:failure reason"..#text..":"..text.."e" 107 | end 108 | 109 | standart_header = [[ 110 | HTTP/1.1 200 OK 111 | Server: ]]..script_name.."\n"..[[ 112 | Connection: close 113 | ]] 114 | 115 | function to_http(body, content_type) 116 | body = body or ferrmsg("tracker internal error", content_type) 117 | local data = standart_header 118 | data = data.."Content-Type: "..(content_type or "text/plain").."\n" 119 | if body then data = data.."Content-Length: "..string.len(body).."\n" end 120 | data = data.."\n"..(body or '') 121 | return data 122 | end 123 | 124 | 125 | function http.main_page(client) 126 | local buff = new_string_builder() 127 | buff.add("Lua DHT Tracker Info") 128 | buff.add("
"..welcome_msg.."
") 129 | buff.add( 130 | [[ 131 |

132 | node ip: ]]..decode_ip(#my_ip == 4 and my_ip or "\0\0\0\0").."
\n"..[[ 133 | node id: ]]..hexenc(my_id or "").."
\n"..[[ 134 | nodes count: ]]..(nodes.count or "unknown").."\n"..[[ 135 |

136 | 137 |

138 | Console Out
139 | Hosted torrents
140 | Nodes Map
141 |

142 | 143 | 144 |

145 | input btih and click this link:
146 | magnet:?xt=urn:btih:&tr=http://127.0.0.1:]]..port_number..[[/announce 147 |

148 | ]] ) 149 | buff.add("") 150 | client:send(to_http(buff.get("\n"), "text/html")) 151 | client:close() 152 | end 153 | 154 | local maximum_ugol = 255 * 256 * 256 * 256 155 | 156 | function spiral(ugol) 157 | --ugol = maximum_ugol - ugol 158 | local m1 = (ugol/maximum_ugol * 200) 159 | local m2 = (ugol/maximum_ugol * 46) 160 | return (50+math.cos(m1)*(m2)), (50+math.sin(m1)*(m2)) 161 | end 162 | 163 | function http.svg_map(client) 164 | print("\t\t\t>http map") 165 | local buff = { new_string_builder(), new_string_builder(), new_string_builder(), 166 | new_string_builder() } 167 | buff[1].add([[ 168 | 174 | 175 | ]]) 176 | 177 | 178 | buff[1].add([[]]) 179 | buff[2].add([[]]) 180 | buff[3].add([[]]) 181 | buff[4].add([[]]) 182 | 183 | for ip, port_list in pairs(nodes.ap) do 184 | for port, node in pairs(port_list) do 185 | local opacity = (1 - (os.time() - (node.last_seen or node.added)) / (15*60) ) 186 | 187 | if opacity>0 then 188 | opacity = (svg_opacity and ' opacity="'..opacity..'" ') or '' 189 | 190 | local x, y = spiral(statistic_distance(my_id, node.id)) 191 | 192 | local fill = (node.confirmed and ' fill="green" ') or (node.sended and ' fill="lime"') or ( (not node.last_seen) and ' fill="red" ' ) or "" 193 | 194 | buff[2].add([[]]) 196 | 197 | if node.conected_to then 198 | local stroke = (node.confirmed and ' stroke="green" ') or (node.last_seen and ' stroke="bule" ') or "" 199 | local x2, y2 = spiral(statistic_distance(my_id, node.conected_to)) 200 | buff[1].add('') 201 | end 202 | 203 | 204 | if node.announced then 205 | buff[3].add([[]]) 207 | elseif node.peer then 208 | buff[3].add([[]]) 210 | end 211 | end 212 | end 213 | end 214 | local x, y = spiral(0) 215 | 216 | buff[3].add([[]]) 217 | 218 | find_close_id(my_id, 50, 219 | function(node, buff) 220 | local x2, y2 = spiral(statistic_distance(my_id, node.id)) 221 | 222 | buff.add('') 223 | 224 | x = x2 225 | y = y2 226 | return true 227 | end 228 | , {buff[4]} 229 | ) 230 | 231 | buff[1].add("") 232 | buff[2].add("") 233 | buff[3].add("") 234 | buff[4].add("") 235 | buff[4].add("") 236 | client:send(to_http(buff[1].get("\n")..buff[2].get("\n")..buff[3].get("\n")..buff[4].get("\n"), "image/svg+xml")) 237 | client:close() 238 | end 239 | 240 | function sort_by_count(f, s) 241 | if f.count > s.count then 242 | return true 243 | elseif f.count < s.count then 244 | return false 245 | end 246 | 247 | return less(f.hash_raw, s.hash_raw, my_id) 248 | end 249 | 250 | function http.info(client) 251 | print("\t\t\t>http info") 252 | local buff1 = {} 253 | for hash, peer_list in pairs(peers.btih) do 254 | table.insert(buff1, { hash = hexenc(hash), hash_raw = hash, count = count_elements(peer_list), list = peer_list }) 255 | end 256 | table.sort(buff1, sort_by_count) 257 | local buff = new_string_builder() 258 | buff.add("Lua DHT Tracker Info") 259 | buff.add("") 260 | for index, value in pairs(buff1) do 261 | local name = (torrent_info[value.hash_raw] and torrent_info[value.hash_raw].name) or value.hash 262 | buff.add(string.format("") 267 | end 268 | buff.add("
BitTorrent Info Hash or Namecountlist
%s%s",value.hash, port_number, name, value.hash, name, value.count )) 263 | for compact, peer in pairs(value.list) do 264 | buff.add(string.format("%s:%s ", decode_peer(compact))) 265 | end 266 | buff.add("
") 269 | client:send(to_http(buff.get("\n"), "text/html")) 270 | client:close() 271 | return 272 | end 273 | 274 | function http.search(client, info_hash, port, request) 275 | print("\t\t\t> http (search)") 276 | 277 | local buff = new_string_builder() 278 | buff.add([[ 279 | 280 | 281 | search by info_hash 282 | 283 | 284 |

This page refresh every 120 seconds. Whait plz.

285 |

286 | ]]) 287 | local protocol = string.match(request, "[?&]protocol=([^& ]+)") or "bittorrent" 288 | local file_size = string.match(request, "[?&]xl=([^& ]+)") or "" 289 | local file_name = string.match(request, "[?&]dn=([^& ]+)") or "" 290 | local btih = string.match(request, "[?&]urn:btih:([^& ]+)") or hexenc(info_hash) 291 | local file_urn = string.match(request, "[?&](urn:[^& ]+)") or "urn:btih:"..btih 292 | local ed2k_hash = string.match(request, "[?&]urn:ed2k:([^& ]+)") or "" 293 | 294 | buff.add(string.format([[protocol: %s
295 | file_size: %s
296 | file_name: %s
297 | file_urn: %s
298 | btih: %s
299 | info_hash: %s
300 | ed2k_hash: %s
301 |

]], protocol , file_size, file_name, 302 | file_urn, btih, hexenc(info_hash), ed2k_hash)) 303 | 304 | local peers = get_peers(info_hash, port) 305 | local x_alt = "" 306 | 307 | if peers then 308 | 309 | if protocol == "ed2k" then 310 | 311 | buff.add(string.format("%s:%s
\n", a, p, file_urn, a, p)) 325 | elseif protocol == "p2p-radio" then 326 | buff.add(string.format("%s:%s
\n", a, p, a, p)) 327 | else 328 | buff.add(string.format("%s:%s
\n", a, p, btih, a, p)) 329 | end 330 | --end 331 | end 332 | 333 | if protocol == "ed2k" then 334 | buff.add("|/") 335 | end 336 | 337 | if not peers_x_alt.empty() then 338 | x_alt = "X-Alt: "..peers_x_alt.get(",").."\n" 339 | end 340 | end 341 | 342 | buff.add([[ 343 |

344 | 345 | 346 | ]]) 347 | local content = buff.get() 348 | client:send([[ 349 | HTTP/1.1 503 Bisy 350 | Server: ]]..script_name.."\n"..[[ 351 | X-Queue: position=2,length=10,limit=20,pollMin=10,pollMax=100"); 352 | Retry-After: 120 353 | Refresh: 120]] 354 | .."\n"..x_alt.."Content-Length: "..#content.."\n\n"..content) 355 | 356 | client:close() 357 | return 358 | end 359 | 360 | function http.not_found(client) 361 | client:send([[ 362 | HTTP/1.1 404 Not Found 363 | Server: ]]..script_name.."\n".. 364 | [[Content-Length: 9 365 | 366 | Not Found]]) 367 | end -------------------------------------------------------------------------------- /lua/ltn12.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- LTN12 - Filters, sources, sinks and pumps. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module 10 | ----------------------------------------------------------------------------- 11 | local string = require("string") 12 | local table = require("table") 13 | local base = _G 14 | module("ltn12") 15 | 16 | filter = {} 17 | source = {} 18 | sink = {} 19 | pump = {} 20 | 21 | -- 2048 seems to be better in windows... 22 | BLOCKSIZE = 2048 23 | _VERSION = "LTN12 1.0.1" 24 | 25 | ----------------------------------------------------------------------------- 26 | -- Filter stuff 27 | ----------------------------------------------------------------------------- 28 | -- returns a high level filter that cycles a low-level filter 29 | function filter.cycle(low, ctx, extra) 30 | base.assert(low) 31 | return function(chunk) 32 | local ret 33 | ret, ctx = low(ctx, chunk, extra) 34 | return ret 35 | end 36 | end 37 | 38 | -- chains a bunch of filters together 39 | -- (thanks to Wim Couwenberg) 40 | function filter.chain(...) 41 | local n = table.getn(arg) 42 | local top, index = 1, 1 43 | local retry = "" 44 | return function(chunk) 45 | retry = chunk and retry 46 | while true do 47 | if index == top then 48 | chunk = arg[index](chunk) 49 | if chunk == "" or top == n then return chunk 50 | elseif chunk then index = index + 1 51 | else 52 | top = top+1 53 | index = top 54 | end 55 | else 56 | chunk = arg[index](chunk or "") 57 | if chunk == "" then 58 | index = index - 1 59 | chunk = retry 60 | elseif chunk then 61 | if index == n then return chunk 62 | else index = index + 1 end 63 | else base.error("filter returned inappropriate nil") end 64 | end 65 | end 66 | end 67 | end 68 | 69 | ----------------------------------------------------------------------------- 70 | -- Source stuff 71 | ----------------------------------------------------------------------------- 72 | -- create an empty source 73 | local function empty() 74 | return nil 75 | end 76 | 77 | function source.empty() 78 | return empty 79 | end 80 | 81 | -- returns a source that just outputs an error 82 | function source.error(err) 83 | return function() 84 | return nil, err 85 | end 86 | end 87 | 88 | -- creates a file source 89 | function source.file(handle, io_err) 90 | if handle then 91 | return function() 92 | local chunk = handle:read(BLOCKSIZE) 93 | if not chunk then handle:close() end 94 | return chunk 95 | end 96 | else return source.error(io_err or "unable to open file") end 97 | end 98 | 99 | -- turns a fancy source into a simple source 100 | function source.simplify(src) 101 | base.assert(src) 102 | return function() 103 | local chunk, err_or_new = src() 104 | src = err_or_new or src 105 | if not chunk then return nil, err_or_new 106 | else return chunk end 107 | end 108 | end 109 | 110 | -- creates string source 111 | function source.string(s) 112 | if s then 113 | local i = 1 114 | return function() 115 | local chunk = string.sub(s, i, i+BLOCKSIZE-1) 116 | i = i + BLOCKSIZE 117 | if chunk ~= "" then return chunk 118 | else return nil end 119 | end 120 | else return source.empty() end 121 | end 122 | 123 | -- creates rewindable source 124 | function source.rewind(src) 125 | base.assert(src) 126 | local t = {} 127 | return function(chunk) 128 | if not chunk then 129 | chunk = table.remove(t) 130 | if not chunk then return src() 131 | else return chunk end 132 | else 133 | table.insert(t, chunk) 134 | end 135 | end 136 | end 137 | 138 | function source.chain(src, f) 139 | base.assert(src and f) 140 | local last_in, last_out = "", "" 141 | local state = "feeding" 142 | local err 143 | return function() 144 | if not last_out then 145 | base.error('source is empty!', 2) 146 | end 147 | while true do 148 | if state == "feeding" then 149 | last_in, err = src() 150 | if err then return nil, err end 151 | last_out = f(last_in) 152 | if not last_out then 153 | if last_in then 154 | base.error('filter returned inappropriate nil') 155 | else 156 | return nil 157 | end 158 | elseif last_out ~= "" then 159 | state = "eating" 160 | if last_in then last_in = "" end 161 | return last_out 162 | end 163 | else 164 | last_out = f(last_in) 165 | if last_out == "" then 166 | if last_in == "" then 167 | state = "feeding" 168 | else 169 | base.error('filter returned ""') 170 | end 171 | elseif not last_out then 172 | if last_in then 173 | base.error('filter returned inappropriate nil') 174 | else 175 | return nil 176 | end 177 | else 178 | return last_out 179 | end 180 | end 181 | end 182 | end 183 | end 184 | 185 | -- creates a source that produces contents of several sources, one after the 186 | -- other, as if they were concatenated 187 | -- (thanks to Wim Couwenberg) 188 | function source.cat(...) 189 | local src = table.remove(arg, 1) 190 | return function() 191 | while src do 192 | local chunk, err = src() 193 | if chunk then return chunk end 194 | if err then return nil, err end 195 | src = table.remove(arg, 1) 196 | end 197 | end 198 | end 199 | 200 | ----------------------------------------------------------------------------- 201 | -- Sink stuff 202 | ----------------------------------------------------------------------------- 203 | -- creates a sink that stores into a table 204 | function sink.table(t) 205 | t = t or {} 206 | local f = function(chunk, err) 207 | if chunk then table.insert(t, chunk) end 208 | return 1 209 | end 210 | return f, t 211 | end 212 | 213 | -- turns a fancy sink into a simple sink 214 | function sink.simplify(snk) 215 | base.assert(snk) 216 | return function(chunk, err) 217 | local ret, err_or_new = snk(chunk, err) 218 | if not ret then return nil, err_or_new end 219 | snk = err_or_new or snk 220 | return 1 221 | end 222 | end 223 | 224 | -- creates a file sink 225 | function sink.file(handle, io_err) 226 | if handle then 227 | return function(chunk, err) 228 | if not chunk then 229 | handle:close() 230 | return 1 231 | else return handle:write(chunk) end 232 | end 233 | else return sink.error(io_err or "unable to open file") end 234 | end 235 | 236 | -- creates a sink that discards data 237 | local function null() 238 | return 1 239 | end 240 | 241 | function sink.null() 242 | return null 243 | end 244 | 245 | -- creates a sink that just returns an error 246 | function sink.error(err) 247 | return function() 248 | return nil, err 249 | end 250 | end 251 | 252 | -- chains a sink with a filter 253 | function sink.chain(f, snk) 254 | base.assert(f and snk) 255 | return function(chunk, err) 256 | if chunk ~= "" then 257 | local filtered = f(chunk) 258 | local done = chunk and "" 259 | while true do 260 | local ret, snkerr = snk(filtered, err) 261 | if not ret then return nil, snkerr end 262 | if filtered == done then return 1 end 263 | filtered = f(done) 264 | end 265 | else return 1 end 266 | end 267 | end 268 | 269 | ----------------------------------------------------------------------------- 270 | -- Pump stuff 271 | ----------------------------------------------------------------------------- 272 | -- pumps one chunk from the source to the sink 273 | function pump.step(src, snk) 274 | local chunk, src_err = src() 275 | local ret, snk_err = snk(chunk, src_err) 276 | if chunk and ret then return 1 277 | else return nil, src_err or snk_err end 278 | end 279 | 280 | -- pumps all data from a source to a sink, using a step function 281 | function pump.all(src, snk, step) 282 | base.assert(src and snk) 283 | step = step or pump.step 284 | while true do 285 | local ret, err = step(src, snk) 286 | if not ret then 287 | if err then return nil, err 288 | else return 1 end 289 | end 290 | end 291 | end 292 | 293 | -------------------------------------------------------------------------------- /lua/magnet_to_uri_res.lua: -------------------------------------------------------------------------------- 1 | require("binstd") 2 | require("encoding") 3 | require("settings") 4 | local dbg = false 5 | 6 | function dbgmsg(...) 7 | if dbg then 8 | usermsg(unpack(arg)) 9 | end 10 | end 11 | function usermsg(...) 12 | io.stderr:write(table.concat(arg, "\t")) 13 | end 14 | local urn = {} 15 | 16 | local urn_name 17 | local hash 18 | 19 | while not next(urn) do 20 | if not arg[1] then 21 | usermsg("\ninput urn or magnet, or press Enter to exit: \n") 22 | end 23 | magnet = arg[1] or io.stdin:read("*l") 24 | if #magnet <= 1 then os.exit(0) end 25 | dbgmsg("\nmagnet: ", magnet) 26 | 27 | string.gsub(magnet, "urn:([0-9A-Za-z:%./]+)", function(urn_part) 28 | if string.match(urn_part, "^tree:tiger") then 29 | urn["tree:tiger:"] = string.match(urn_part, ":([0-9A-Za-z]+)$") 30 | elseif string.match(urn_part, "^bitprint:") then 31 | urn["sha1:"], urn["tree:tiger:"] = string.match(urn_part, ":([0-9A-Za-z]+).([0-9A-Za-z]+)$") 32 | elseif string.match(urn_part, "^ed2k") then 33 | urn["ed2k:"] = string.match(urn_part, ":([0-9A-Za-z]+)$") 34 | elseif string.match(urn_part, "^sha1:") then 35 | urn["sha1:"] = string.match(urn_part, ":([0-9A-Za-z]+)$") 36 | elseif string.match(urn_part, "^btih:") then 37 | urn["btih:"] = string.match(urn_part, ":([0-9A-Za-z]+)$") 38 | end 39 | end) 40 | 41 | if urn["tree:tiger:"] then 42 | urn_name = "tree:tiger:" 43 | hash = urn[urn_name] 44 | decode = "b32dec" 45 | elseif urn["ed2k:"] then 46 | urn_name = "ed2k:" 47 | hash = urn[urn_name] 48 | decode = "hexdec" 49 | elseif urn["sha1:"] then 50 | urn_name = "sha1:" 51 | hash = urn[urn_name] 52 | decode = "b32dec" 53 | elseif urn["btih:"] then 54 | urn_name = "btih:" 55 | hash = urn[urn_name] 56 | decode = "hexdec" 57 | end 58 | 59 | if not next(urn) then 60 | usermsg("\nurn not found; pattern: (urn:[0-9A-Za-z:%./]+)") 61 | arg[3] = nil 62 | else 63 | usermsg("\nurn found: ", urn_name, hash) 64 | end 65 | end 66 | 67 | if hash then 68 | local cmd = {} 69 | table.insert(cmd, [[lua5.1 -lbinstd -lencoding -e "io.write('d'..bencode(']]..urn_name..[[')..bencode(]]..decode..[[(']].. 70 | hash..[['))..'8:protocol7:uri-rese')"]]) 71 | table.insert(cmd, [[rhash -p"io.write('http://127.0.0.1:]]..port_number..[[/uri-res/N2R?urn:]]..urn_name..hash.. 72 | [=[&info_hash='..urlhex([[%@h]]))" -]=]) 73 | table.insert(cmd, [[lua5.1 -lbinstd -lencoding -]]) 74 | dbgmsg(table.concat(cmd, "|")) 75 | os.execute(table.concat(cmd, "|")) 76 | end 77 | -------------------------------------------------------------------------------- /lua/mime.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- MIME support for the Lua language. 3 | -- Author: Diego Nehab 4 | -- Conforming to RFCs 2045-2049 5 | -- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module and import dependencies 10 | ----------------------------------------------------------------------------- 11 | local base = _G 12 | local ltn12 = require("ltn12") 13 | local mime = require("mime.core") 14 | local io = require("io") 15 | local string = require("string") 16 | module("mime") 17 | 18 | -- encode, decode and wrap algorithm tables 19 | encodet = {} 20 | decodet = {} 21 | wrapt = {} 22 | 23 | -- creates a function that chooses a filter by name from a given table 24 | local function choose(table) 25 | return function(name, opt1, opt2) 26 | if base.type(name) ~= "string" then 27 | name, opt1, opt2 = "default", name, opt1 28 | end 29 | local f = table[name or "nil"] 30 | if not f then 31 | base.error("unknown key (" .. base.tostring(name) .. ")", 3) 32 | else return f(opt1, opt2) end 33 | end 34 | end 35 | 36 | -- define the encoding filters 37 | encodet['base64'] = function() 38 | return ltn12.filter.cycle(b64, "") 39 | end 40 | 41 | encodet['quoted-printable'] = function(mode) 42 | return ltn12.filter.cycle(qp, "", 43 | (mode == "binary") and "=0D=0A" or "\r\n") 44 | end 45 | 46 | -- define the decoding filters 47 | decodet['base64'] = function() 48 | return ltn12.filter.cycle(unb64, "") 49 | end 50 | 51 | decodet['quoted-printable'] = function() 52 | return ltn12.filter.cycle(unqp, "") 53 | end 54 | 55 | local function format(chunk) 56 | if chunk then 57 | if chunk == "" then return "''" 58 | else return string.len(chunk) end 59 | else return "nil" end 60 | end 61 | 62 | -- define the line-wrap filters 63 | wrapt['text'] = function(length) 64 | length = length or 76 65 | return ltn12.filter.cycle(wrp, length, length) 66 | end 67 | wrapt['base64'] = wrapt['text'] 68 | wrapt['default'] = wrapt['text'] 69 | 70 | wrapt['quoted-printable'] = function() 71 | return ltn12.filter.cycle(qpwrp, 76, 76) 72 | end 73 | 74 | -- function that choose the encoding, decoding or wrap algorithm 75 | encode = choose(encodet) 76 | decode = choose(decodet) 77 | wrap = choose(wrapt) 78 | 79 | -- define the end-of-line normalization filter 80 | function normalize(marker) 81 | return ltn12.filter.cycle(eol, 0, marker) 82 | end 83 | 84 | -- high level stuffing filter 85 | function stuff() 86 | return ltn12.filter.cycle(dot, 2) 87 | end 88 | -------------------------------------------------------------------------------- /lua/nodes.lua: -------------------------------------------------------------------------------- 1 | require("compact_encoding") 2 | 3 | nodes = {ap = {}, by_id={}, id_map={}} 4 | 5 | 6 | function getbit(number) 7 | if number == 0 then 8 | return 0, number 9 | elseif number > 0 then 10 | local bit = math.mod(number,2) 11 | return bit, (number-bit)/2 12 | else 13 | return nil, "number must be >= 0" 14 | end 15 | end 16 | 17 | 18 | function xor(n1, n2, hamming_distance) 19 | local getbit = getbit 20 | local n3 = 0 21 | local cnt = 1 22 | local bit1 = 0 23 | local bit2 = 0 24 | while (n1 > 0) or (n2 > 0) do 25 | 26 | bit1, n1 = getbit(n1) 27 | bit2, n2 = getbit(n2) 28 | 29 | if(bit1 ~= bit2) then 30 | n3 = n3 + cnt 31 | end 32 | 33 | if not hamming_distance then 34 | cnt = cnt * 2 35 | end 36 | end 37 | 38 | return n3 39 | end 40 | 41 | function hamming_distance(n1, n2) 42 | return xor(n1, n2, true) 43 | end 44 | 45 | function table.get_by_keys(tbl, ...) 46 | local keys = {...} 47 | local value = tbl 48 | for i, key in ipairs(keys) do 49 | if type(value) == "table" then 50 | value = value[key] 51 | else 52 | return nil 53 | end 54 | end 55 | return value 56 | end 57 | 58 | function node_by_ip_port(ip, port) 59 | return table.get_by_keys(nodes.ap, ip, port) 60 | end 61 | 62 | function node_by_id_ip(id, ip) 63 | return table.get_by_keys(nodes.by_id, id, ip) 64 | end 65 | 66 | function add_node(id, address, port, check_fnc, ... ) 67 | if is_blocked_port(port) then return end 68 | if is_local(address) then return end 69 | 70 | local node = node_by_ip_port(address, port) 71 | 72 | if (not node) then 73 | node = node_by_id_ip(id, address) 74 | end 75 | 76 | if (not node) then 77 | node = { address = address, port = port, id = id, added = os.time() } 78 | elseif check_fnc then 79 | check_fnc(id, address, port, node, unpack(arg)) 80 | end 81 | 82 | add_id_ip(node) 83 | add_ip_port(node) 84 | add_id(nodes.id_map, node.id) 85 | 86 | return node 87 | end 88 | 89 | function add_id_ip(node) 90 | local by_ip = nodes.by_id[node.id] 91 | if not by_ip then 92 | by_ip = {} 93 | nodes.by_id[node.id] = by_ip 94 | end 95 | 96 | by_ip[node.address] = node 97 | end 98 | 99 | function add_ip_port(node) 100 | local port_list = nodes.ap[node.address] 101 | 102 | if not port_list then 103 | port_list = {} 104 | nodes.ap[node.address] = port_list 105 | end 106 | 107 | if port_list[node.port] ~= node then 108 | port_list[node.port] = node 109 | nodes.count = (nodes.count or 0) + 1 110 | nodes.counted = false 111 | end 112 | end 113 | 114 | 115 | function add_id(list, id, index) 116 | index = index or 1 117 | local sub_list = list[id:byte(index)] 118 | if not sub_list then 119 | list[id:byte(index)] = id 120 | elseif type(sub_list) == "table" then 121 | add_id(sub_list, id, index + 1) 122 | elseif (type(sub_list) == "string") and not (sub_list == id) then 123 | local id2 = sub_list 124 | local new_list = {} 125 | list[id:byte(index)] = new_list 126 | for i = index + 1, #id2 do 127 | local id_byte = id:byte(i) 128 | local id2_byte = id2:byte(i) 129 | if id_byte == id2_byte then 130 | new_list[id_byte] = {} 131 | new_list = new_list[id_byte] 132 | else 133 | new_list[id_byte] = id 134 | new_list[id2_byte] = id2 135 | break 136 | end 137 | end 138 | end 139 | end 140 | 141 | 142 | function remove_node_id_ip(node) 143 | list = nodes.by_id[node.id] 144 | if list then 145 | list[node.address] = nil 146 | if not next(list) then nodes.by_id[node.id] = nil end 147 | end 148 | end 149 | 150 | function remove_node_ip_port(node) 151 | local port_list = nodes.ap[node.address] 152 | if port_list and (port_list[node.port] == node) then 153 | port_list[node.port] = nil 154 | nodes.count = (nodes.count or 0) - 1 155 | nodes.counted = false 156 | if not next(port_list) then 157 | nodes.ap[node.address] = nil 158 | end 159 | end 160 | end 161 | 162 | function count_nodes() 163 | local count = 0 164 | 165 | for ip, port_list in pairs(nodes.ap) do 166 | for port, node in pairs(port_list) do 167 | count = count + 1 168 | end 169 | end 170 | 171 | nodes.count = count 172 | nodes.counted = true 173 | end 174 | 175 | function check_node(key, node, nodes_list, node_id) 176 | local valid = true 177 | if node.id ~= node_id then 178 | nodes_list[key] = nil 179 | valid = false 180 | end 181 | 182 | if (not nodes.counted) and ((nodes.count or 0) <= 300) then 183 | count_nodes() 184 | end 185 | 186 | if nodes_clear and (nodes.count > 300) 187 | and (((node.last_seen or node.added) < (os.time() - 30*60)) 188 | or (node.sended and node.sended.time and (node.sended.time < (os.time() - 3*60)))) 189 | then 190 | remove_node_ip_port(node) 191 | remove_node_id_ip(node) 192 | valid = false 193 | if on_node_removed then 194 | on_node_removed(node) 195 | end 196 | end 197 | 198 | return valid 199 | end 200 | 201 | function find_close_id(id, count, check_fnc, args, list, index, k_nodes) 202 | count = count or 8 203 | index = index or 1 204 | k_nodes = k_nodes or {} 205 | list = list or nodes.id_map 206 | args = args or {} 207 | 208 | for i = 0, 255 do 209 | local byte = xor(id:byte(index), i) 210 | local el = list[byte] 211 | if type(el) == "table" then 212 | _, list[byte] = find_close_id( id, count, check_fnc, args, el, index + 1, k_nodes) 213 | elseif el then 214 | local node_id = el 215 | local nodes_list = nodes.by_id[node_id] 216 | if nodes_list and next(nodes_list) then 217 | for key, node in pairs(nodes_list) do 218 | if check_node(key, node, nodes_list, node_id) then 219 | if (not check_fnc) or check_fnc(node, unpack(args)) then 220 | --print(b32enc(node_id), "("..#node_id..")", statistic_distance(id, node_id)) 221 | table.insert(k_nodes, node) 222 | end 223 | end 224 | end 225 | end 226 | 227 | if (not nodes_list) or (not next(nodes_list)) then 228 | if nodes_list then nodes.by_id[node_id] = nil end 229 | list[byte] = nil 230 | end 231 | 232 | end 233 | 234 | if #k_nodes >= count then break end 235 | end 236 | 237 | local index, el = next(list) 238 | if not index then 239 | list = nil 240 | elseif not next(list, index) and type(el) == "string" then 241 | list = el 242 | end 243 | return k_nodes, list 244 | end 245 | 246 | function is_local(ip) 247 | local ip = ipv4_array(ip) 248 | if ip[1] == 10 then 249 | return true 250 | elseif (ip[1] == 172) and (ip[2] >= 16) and (ip[2] <= 32) then 251 | return true 252 | elseif (ip[1] == 192) and (ip[2] == 168) then 253 | return true 254 | elseif (ip[1] == 127) then 255 | return true 256 | end 257 | end 258 | 259 | function is_blocked_port(port) 260 | return (port < 1024) or (port > 65535) 261 | end 262 | -------------------------------------------------------------------------------- /lua/nodes2.lua: -------------------------------------------------------------------------------- 1 | require("compact_encoding") 2 | 3 | nodes = {ap = {}, by_id={}, id_map={}} 4 | 5 | function add_node(id, address, port, check_fnc, ... ) 6 | if is_local(address) then return end 7 | 8 | local address_port = address..":"..port 9 | 10 | local node = nodes.ap[address_port] 11 | if (not node) then 12 | local by_address = nodes.by_id[id] 13 | node = by_address and by_address[address] 14 | end 15 | 16 | if (not node) then 17 | node = { address = address, port = port, id = id, added = os.time() } 18 | nodes.ap[address_port] = node 19 | else 20 | check_fnc(id, address, port, node, unpack(arg)) 21 | end 22 | 23 | local by_address = nodes.by_id[node.id] 24 | if not by_address then 25 | by_address = {} 26 | nodes.by_id[node.id] = by_address 27 | end 28 | 29 | by_address[address] = node 30 | 31 | add_id(nodes.id_map, node.id) 32 | 33 | return node 34 | end 35 | 36 | 37 | function add_id(list, id, index) 38 | index = index or 1 39 | local sub_list = list[id:byte(index)] 40 | if not sub_list then 41 | list[id:byte(index)] = id:sub(index + 1) 42 | elseif type(sub_list) == "table" then 43 | add_id(sub_list, id, index + 1) 44 | elseif (type(sub_list) == "string") and not (sub_list == id:sub(index + 1)) then 45 | local new_list = {} 46 | list[id:byte(index)] = new_list 47 | local i = 1 48 | while i <= #sub_list do 49 | if id:byte(index + i) == sub_list:byte(i) then 50 | new_list[sub_list:byte(i)] = {} 51 | new_list = new_list[sub_list:byte(i)] 52 | else 53 | break 54 | end 55 | i = i + 1 56 | end 57 | 58 | new_list[sub_list:byte(i)] = (#sub_list < (i + 1)) or sub_list:sub(i + 1) 59 | new_list[id:byte(index + i)] = (#id < (index + i + 1)) or id:sub(index + i + 1) 60 | end 61 | end 62 | 63 | function remove_id(id) 64 | 65 | end 66 | 67 | 68 | function remove_node_id_ip(node) 69 | nodes.by_id[node.id][node.address] = nil 70 | end 71 | 72 | function remove_node_ip_port(node) 73 | nodes.ap[node.address..":"..node.port] = nil 74 | end 75 | 76 | 77 | function find_close_id(id, count, check_fnc, args) 78 | count = count or 8 79 | args = args or {} 80 | local k_nodes = {} 81 | local k_count = 0 82 | 83 | local function check_el(byte, el, list, id_part) 84 | if type(el) == "table" then 85 | _, list[byte] = find_close_id( id, count, check_fnc, args, el, index + 1, id_part..string.char(byte), k_nodes) 86 | elseif el then 87 | local node_id = id_part..string.char(byte)..(((type(el) == "string") and el) or "") 88 | local nodes_list = nodes.by_id[node_id] 89 | if nodes_list and next(nodes_list) then 90 | for key, node in pairs(nodes_list) do 91 | if (node.id == node_id) then 92 | if check_fnc(node, unpack(args)) then 93 | --print(b32enc(node_id), "("..#node_id..")", statistic_distance(id, node_id)) 94 | table.insert(k_nodes, node) 95 | end 96 | else 97 | nodes_list[key] = nil 98 | end 99 | end 100 | end 101 | 102 | if (not nodes_list) or (not next(nodes_list)) then 103 | if nodes_list then nodes.by_id[node_id] = nil end 104 | list[byte] = nil 105 | end 106 | 107 | end 108 | end 109 | 110 | local function small_count(index, list, id_part) 111 | local id_byte = id:byte(index) 112 | repeat 113 | local byte_len = 256 114 | local byte_value = nil 115 | local byte_el = nil 116 | local count = 0 117 | for byte, sub_el in pairs(list) do 118 | local len = xor(id_byte, byte) 119 | if (cecked > len) and (len < byte_len) then 120 | byte_len = len 121 | byte_value = byte 122 | byte_el = sub_el 123 | end 124 | if count then 125 | count = count + 1 126 | if count > 15 then 127 | return false 128 | end 129 | end 130 | end 131 | count = nil 132 | cecked = byte_len 133 | if byte_el then 134 | check_el(byte_value, byte_el, list, id_part) 135 | end 136 | until byte_len == 256 or k_count >= count 137 | return true 138 | end 139 | end 140 | 141 | 142 | function find_close_id_old(id, count, check_fnc, args, list, index, id_part, k_nodes) 143 | count = count or 8 144 | index = index or 1 145 | id_part = id_part or "" 146 | 147 | local list = list or nodes.id_map 148 | args = args or {} 149 | 150 | 151 | 152 | function small_count() 153 | repeat 154 | local byte_len = 256 155 | local byte_value = nil 156 | local byte_el = nil 157 | local count = 0 158 | for byte, sub_el in pairs(list[byte]) do 159 | local len = xor(id:byte(index), byte) 160 | if (cecked > len) and (len < byte_len) then 161 | byte_len = len 162 | byte_value = byte 163 | byte_el = sub_el 164 | end 165 | if count then 166 | count = count + 1 167 | if count > 15 then 168 | return false 169 | end 170 | end 171 | end 172 | cecked = byte_len 173 | if byte_el then 174 | check_el(byte_value, byte_el) 175 | end 176 | until byte_len == 256 or #k_nodes >= count 177 | 178 | end 179 | 180 | 181 | if not small_count() then 182 | large_count() 183 | end 184 | 185 | 186 | 187 | 188 | if id_count[list] and (id_count[list] < 15) then 189 | local cecked = -1 190 | 191 | else 192 | for i = 0, 255 do 193 | local byte = xor(id:byte(index), i) 194 | local el = list[byte] 195 | if byte_el then 196 | check_el(byte_value, byte_el) 197 | end 198 | 199 | if #k_nodes >= count then break end 200 | end 201 | end 202 | 203 | if not next(list) then list = nil end 204 | return k_nodes, list 205 | end 206 | 207 | function check_id_list() 208 | for id, nodes_list in pairs(nodes.by_id) do 209 | for key, node in pairs(nodes_list) do 210 | if node.id ~= id then 211 | nodes_list[key] = nil 212 | end 213 | end 214 | if not next(nodes_list) then 215 | nodes.by_id[id] = nil 216 | remove_id(id) 217 | end 218 | end 219 | end 220 | 221 | function is_local(address) 222 | local ip = ipv4_array(address) 223 | if ip[1] == 10 then 224 | return true 225 | elseif (ip[1] == 172) and (ip[2] >= 16) and (ip[2] <= 32) then 226 | return true 227 | elseif (ip[1] == 192) and (ip[2] == 168) then 228 | return true 229 | elseif (ip[1] == 127) then 230 | return true 231 | end 232 | end -------------------------------------------------------------------------------- /lua/print_override.lua: -------------------------------------------------------------------------------- 1 | print_list = {} 2 | 3 | function add_print_fnc(print_fnc) 4 | print_list[print_fnc] = true 5 | end 6 | 7 | function remove_print_fnc(print_fnc) 8 | print_list[print_fnc] = nil 9 | end 10 | 11 | function nb_print(...) 12 | io.stderr:write(table.concat(arg, "\t")) 13 | end 14 | 15 | function nb_out(...) 16 | io.stdout:write(table.concat(arg, "\t")) 17 | end 18 | 19 | function flush() 20 | io.stderr:flush() 21 | io.stdout:flush() 22 | end 23 | 24 | function status_print(...) 25 | local line = table.concat(arg, " ") 26 | if #line < 79 then 27 | line = string.rep(" ", 79 - #line)..line.."\r" 28 | end 29 | nb_print(line) 30 | end 31 | 32 | old_print = print 33 | 34 | function print(...) 35 | local line = table.concat(arg, "\t") 36 | nb_print(line.."\n") 37 | for print_fnc, _ in pairs( print_list ) do 38 | print_fnc( line ) 39 | end 40 | end 41 | 42 | 43 | 44 | function print2(...) 45 | local line = table.concat(arg, "\t") 46 | print(line) 47 | old_print(line) 48 | io.stdout:flush() 49 | end 50 | 51 | function mid_print(...) 52 | local line = table.concat(arg, " ") 53 | if #line < 79 then 54 | local hlf = math.floor((79 - #line) / 2) 55 | line = string.rep(" ", hlf)..line 56 | end 57 | print(line) 58 | end 59 | 60 | function print_msg(...) 61 | print(string.rep("-", 79)) 62 | print(unpack(arg)) 63 | print(string.rep("-", 79)) 64 | end 65 | -------------------------------------------------------------------------------- /lua/serialize2.lua: -------------------------------------------------------------------------------- 1 | 2 | -- local table1={nil,nil,nil,[{"no linked table as key 1"}]={"nn1"},"test1"} 3 | -- local table2={table1,nil,nil,[{"no linked table as key 2",table1}]={"nn2"},"test2"} 4 | -- local table3={table1,table2,nil,[{"no \\ \n linked table as key 3",table1,table2,[{"no linked table as key 3.1"}]="3.1"}]={"nn3"},"test3"} 5 | -- table1[1]=table1 6 | -- table1[2]=table2 7 | -- table1[3]=table2 8 | -- table2[2]=table2 9 | -- table2[3]=table3 10 | -- table3[3]=table3 11 | -- table1[table1]="table1.1" 12 | -- table1[table2]="table1.2" 13 | -- table1[table3]="table1.3" 14 | -- table2[table1]=table1 15 | -- table2[table2]=table2 16 | -- table2[table3]=table3 17 | -- table3[table1]=table1 18 | 19 | 20 | function safestring(value) 21 | if type(value)=="string" then 22 | local v = string.gsub(value, "([\\\10\13%c%z\128-\255\"])([0-9]?)", function(chr, digit) 23 | local b = string.byte(chr) 24 | if #digit == 1 then 25 | if string.len(b)<2 then b="0"..b end 26 | if string.len(b)<3 then b="0"..b end 27 | end 28 | b="\\"..b..digit 29 | return b 30 | end) 31 | return '"'..v..'"' 32 | end 33 | end 34 | 35 | function serialize(tTable, sTableName,sNewLine ,sTab, fSkipValue) 36 | assert(tTable, "tTable equals nil"); 37 | sTableName = sTableName or ""; 38 | assert(type(tTable) == "table", "tTable must be a table!"); 39 | assert(type(sTableName) == "string", "sTableName must be a string!"); 40 | if not sNewLine then sNewLine="\n" end 41 | if not sTab then sTab="\t" end 42 | local tTablesCollector={} 43 | local kidx = 0 44 | 45 | local function next2(tbl, index) 46 | local new_index, new_value = next(tbl, index) 47 | while new_index and fSkipValue(tbl, new_index, new_value) do 48 | new_index, new_value = next(tbl, new_index) 49 | end 50 | return new_index, new_value 51 | end 52 | 53 | local function pairs2(tbl) 54 | return next2, tbl, nil 55 | end 56 | 57 | if not fSkipValue then pairs2 = pairs end 58 | 59 | local function SerializeInternal(tTable, sTableName, sTabs, sLongKey) 60 | local tRepear = {} 61 | local tTmp = {} 62 | sLongKey = sLongKey or sTableName; 63 | sTabs = sTabs or ""; 64 | if tTablesCollector[tTable] then 65 | local sKey = tTablesCollector[tTable] 66 | table.insert(tRepear, sNewLine..sTab..sLongKey.."="..sKey..";") 67 | if #sKey > #sLongKey then 68 | tTablesCollector[tTable] = sLongKey; 69 | end 70 | return nil, tRepear 71 | else 72 | tTablesCollector[tTable] = sLongKey 73 | end 74 | 75 | if not next(tTable) then 76 | table.insert(tTmp, sTabs..sTableName.."={}") 77 | return tTmp, tRepear 78 | end 79 | 80 | if sTableName~="" then 81 | table.insert(tTmp, sTabs..sTableName.."={") 82 | end 83 | 84 | local bEmpty = true 85 | for key, value in pairs2(tTable) do 86 | local sKey 87 | local bToRepear = false 88 | if (type(key) == "table") then 89 | if tTablesCollector[key] then 90 | sKey="["..tTablesCollector[key].."]" 91 | else 92 | kidx=kidx+1 93 | sKey="keys["..kidx.."]" 94 | local tTmp2, tRepear2 = SerializeInternal(key, sKey, sTab) 95 | table.insert(tRepear, sNewLine..table.concat(tTmp2, "")..";"..table.concat(tRepear2, "")) 96 | sKey="["..sKey.."]" 97 | end 98 | bToRepear = true 99 | elseif (type(key) == "string") then 100 | local m = string.match(key, "([A-Za-z_]+)") 101 | if m and (#m == #key) then 102 | sKey = m 103 | else 104 | sKey = "["..safestring(key).."]" 105 | end 106 | elseif (type(key) == "number") then 107 | sKey = string.format("[%d]",key); 108 | else 109 | sKey = "["..tostring(key).."]" 110 | end 111 | 112 | local prefix = (bEmpty and sNewLine) or ","..sNewLine 113 | 114 | if(type(value) == "table") then 115 | local tTmp2, tRepear2=SerializeInternal(value, sKey,(bToRepear and sTab) or sTabs..sTab, sLongKey..((sKey:sub(1,1)=="[" and "") or ".")..sKey) 116 | 117 | if tTmp2 and next(tTmp2) then 118 | if bToRepear then 119 | table.insert(tRepear, sNewLine..sTab..sLongKey..table.concat(tTmp2, "")..";") 120 | else 121 | table.insert(tTmp, prefix..table.concat(tTmp2, "")); 122 | bEmpty = false 123 | end 124 | end 125 | 126 | if tRepear2 and next(tRepear2) then 127 | table.insert(tRepear, table.concat(tRepear2, "")) 128 | end 129 | else 130 | local sValue = ((type(value) == "string") and safestring(value)) or tostring(value); 131 | if bToRepear then 132 | table.insert(tRepear, sNewLine..sTab..sLongKey..sKey.."="..sValue..";") 133 | else 134 | table.insert(tTmp, prefix..sTabs..sTab..sKey.."="..sValue) 135 | bEmpty = false 136 | end 137 | end 138 | 139 | end 140 | 141 | if sTableName~="" then 142 | if bEmpty then 143 | table.insert(tTmp, "}") 144 | else 145 | table.insert(tTmp, sNewLine..sTabs.."}") 146 | end 147 | end 148 | 149 | return tTmp, tRepear 150 | end 151 | 152 | local ret=nil 153 | if sTableName=="return" then 154 | ret="temp" 155 | sTableName=ret 156 | end 157 | 158 | local tResult, tRepear = SerializeInternal(tTable,sTableName,sTab) 159 | local prefix, suffix = (ret and "return ") or "", "" 160 | 161 | 162 | if tRepear and next(tRepear) then 163 | if not ret then 164 | prefix = sTableName.."=" 165 | end 166 | if tResult[1] and tResult[1]:sub(1,1) == "\t" then 167 | tResult[1] = tResult[1]:sub(2) 168 | end 169 | prefix = prefix.."(function()"..sNewLine..sTab.."local keys={};"..sNewLine..sTab.."local " 170 | suffix = ";"..sNewLine..sTab..table.concat(tRepear, "")..sNewLine..sTab.."return "..sTableName..";"..sNewLine.."end)()" 171 | elseif ret then 172 | local st,ed = string.find(tResult[1],ret.."=",1,true) 173 | if st and ed then 174 | tResult[1] = string.sub(tResult[1], ed+1) 175 | end 176 | end 177 | 178 | collectgarbage() 179 | --print(sResult) 180 | return prefix..table.concat(tResult, "")..suffix 181 | end 182 | 183 | 184 | 185 | -- local seri=serialize(table1,"testtable").."\n return testtable" 186 | -- print(seri) 187 | -- local file = io.open("D:\\xxx.txt","w") 188 | -- file:write(seri) 189 | -- for i=0,1000 do 190 | -- seri=serialize(assert(loadstring(seri))(),"testtable").."\n return testtable" 191 | -- end 192 | -- print(seri) 193 | -------------------------------------------------------------------------------- /lua/serialize3.lua: -------------------------------------------------------------------------------- 1 | --local table1 = _G 2 | 3 | -- local table0 = {"self key", name_test = 1, } 4 | -- table0.self = table0 5 | -- table0[table0]={"oiewrhter"} 6 | -- local table1={nil,nil,nil, recovery = 1, recovery0 = 2, recovery1 = 5, [{x = 1, "no linked table as key 1"}]={"nn1"},"test1", [table0] = table0} 7 | -- local table2={table1,nil,nil,[{x = 2, "no linked table as key 2",table1}]={"nn2"},"test2"} 8 | -- local table3={table1,table2,nil,[{x = 3, "no \\ \n linked table as key 3",table1,table2,[{"no linked table as key 3.1"}]="3.1"}]={"nn3"},"test3"} 9 | -- table1[1]=table3 10 | -- table1["numbers test"] = {0/0, -1/0, 1/0, 1234567890.1234567890, 1,2,3,4,5,6, 6.2 } 11 | -- table1["boolean test"] = {true, false} 12 | -- table1["string test"] = "\0\1\2\3\4\5\6\7\8\9\10\t\n\\" 13 | -- table1["order test"] = {"1", "2", [5] = "5", "3 or 6?"} 14 | -- table1["lng str"] = "too long string copy test." 15 | -- table1["lng str copy"] = table1["lng str"] 16 | -- table1[table1["lng str"]] = "lng str as key" 17 | -- table1["function test"] = function() return "test" end 18 | -- table1[table1["function test"]] = "function as key" 19 | --table1[table1]={[table1]={[table1]="multi key test"}} 20 | --table1["nodes test"] = dofile("nodes.tbl") 21 | -- table1[2]=table2 22 | -- table1[3]=table2 23 | -- table2[2]=table2 24 | -- table2[3]=table3 25 | -- table3[3]=table1 26 | -- table1[table1]="table1.1" 27 | -- table1[table2]="table1.2" 28 | -- table1[table3]="table1.3" 29 | -- table2[table1]=table1 30 | -- table2[table2]=table2 31 | -- table2[table3]=table3 32 | -- table3[table1]=table1 33 | 34 | --table1._G=_G 35 | 36 | 37 | 38 | -- local seri=serialize(table1,"testtable").."\n return testtable" 39 | -- print(seri) 40 | -- local file = io.open("D:\\xxx.txt","w") 41 | -- file:write(seri) 42 | -- for i=0,1000 do 43 | -- seri=serialize(assert(loadstring(seri))(),"testtable").."\n return testtable" 44 | -- end 45 | -- print(seri) 46 | 47 | --[[ 48 | return ({ 49 | value1, 50 | value2, 51 | key1 = value3, 52 | key2 = value4, 53 | ... 54 | function recovery(self) 55 | 56 | end 57 | }):recovery() 58 | ]] 59 | function safe_string(value, cache) 60 | if type(value) == "string" then 61 | local c = cache and cache[value] 62 | if c then 63 | return c 64 | end 65 | 66 | local v = '"'..string.gsub(value, "([\\\10\13%c%z\"])([0-9]?)", function(chr, digit) 67 | local b = string.byte(chr) 68 | if #digit == 1 then 69 | if string.len(b) == 1 then return "\\00"..b..digit end 70 | if string.len(b) == 2 then return "\\0"..b..digit end 71 | end 72 | return "\\"..b..digit 73 | end)..'"' 74 | 75 | if cache then 76 | cache[value] = v 77 | end 78 | 79 | return v 80 | 81 | --return string.format('%q', value) 82 | elseif type(value) == "number" then 83 | if not ( (value > 0) or (value < 0) or (value == 0) ) then -- indeterminate form 84 | return "0/0" 85 | elseif value == 1/0 then -- infinity 86 | return "1/0" 87 | elseif value == -1/0 then -- negative infinity 88 | return "-1/0" 89 | else 90 | return tostring(value) 91 | end 92 | elseif type(value) == "function" then 93 | local ok, dump = pcall(string.dump, value) 94 | if ok then 95 | return "loadstring("..safe_string(dump, cache)..")" 96 | end 97 | elseif type(value) == "boolean" then 98 | return tostring(value) 99 | elseif type(value) == "nil" then 100 | --return "nil" 101 | end 102 | 103 | return nil 104 | end 105 | 106 | local function safe_key(key, cache, safe_string_cache) 107 | local c = cache and cache[key] 108 | if c then 109 | return c[1], c[2] 110 | end 111 | 112 | local safe_name = string.match(key, "^([A-Za-z_]+[A-Za-z0-9_]*)$") 113 | local dot = "." 114 | 115 | 116 | if not safe_name then 117 | safe_name = "["..safe_string(key, safe_string_cache).."]" 118 | dot = "" 119 | end 120 | 121 | if cache then 122 | cache[key] = {safe_name, dot} 123 | end 124 | return safe_name, dot 125 | end 126 | 127 | local function testname(name) 128 | --[[assert( (type(name) == "table") 129 | or (name:sub(1,1) == ".") 130 | or (name:sub(1,1) == "[") 131 | or (name:sub(1,4) == "root") 132 | or (name:sub(1,3) == "key") 133 | )]] 134 | end 135 | 136 | function serialize(value, fnc_write, skip) 137 | if type(value) == "table" then 138 | local obj_map = {} 139 | local keys = {order = {}, links = {}} 140 | local recover = {} 141 | local recovery_name = "recovery" 142 | local deep = 0 143 | local return_string = (not fnc_write) and {} 144 | local safe_string_cache = {} 145 | local safe_key_cache = {} 146 | 147 | if return_string then 148 | fnc_write = function(text) 149 | table.insert(return_string, text) 150 | end 151 | end 152 | 153 | 154 | local function add_key_value(tabl, key, value) 155 | local links = keys.links[key] 156 | if not links then 157 | table.insert(keys.order, key) 158 | links = {} 159 | keys.links[key] = links 160 | end 161 | table.insert(links, {tabl = tabl, value = value}) 162 | end 163 | 164 | local serialize_table 165 | local function serialize_value(value, name, dot, parent, prefix) 166 | if type(value) == "table" then 167 | local not_empty, deferred_creation = serialize_table(value, name, dot, parent, prefix); 168 | return (not_empty or (not deferred_creation)), deferred_creation 169 | else 170 | local serialized = safe_string(value, safe_string_cache) 171 | if serialized then 172 | if prefix then fnc_write(prefix) end 173 | fnc_write(serialized) 174 | 175 | if (type(value) == "function") then 176 | obj_map[value] = {name = name, dot = dot , parent = parent} 177 | end 178 | return true 179 | end 180 | end 181 | end 182 | 183 | local function tbl_new_line(not_empty, deep) 184 | return string.format((not_empty and ",\n%s") or "\n%s", string.rep("\t", deep)) 185 | end 186 | 187 | 188 | 189 | 190 | local function serialize_by_index(tabl, prefix) 191 | local not_empty, last_index 192 | 193 | for index, value in ipairs(tabl) do 194 | if obj_map[value] then 195 | break 196 | end 197 | if (not skip) or not skip(tabl, index, value) then 198 | local value_prefix = ((not_empty and "") or prefix or "") .. tbl_new_line(not_empty, deep) 199 | local writen, deferred_creation = serialize_value(value, string.format("[%i]", index), nil, tabl, value_prefix) 200 | if writen then 201 | last_index = index 202 | not_empty = true 203 | elseif deferred_creation then 204 | return not_empty, index-1, deferred_creation 205 | else 206 | return not_empty, last_index 207 | end 208 | else 209 | break 210 | end 211 | end 212 | return not_empty, last_index 213 | end 214 | 215 | local function check_in_parents(obj, parent) 216 | 217 | if not parent then 218 | return false 219 | elseif obj == parent then 220 | return true 221 | end 222 | local info = obj_map[parent] 223 | return check_in_parents(obj, info.parent) 224 | end 225 | 226 | local function serialize_by_keys(tabl, last_index, parent, not_empty, prefix) 227 | local have_recover, parent_link, obj_as_key 228 | for key, value in pairs(tabl) do 229 | if (not skip) or not skip(tabl, key, value) then 230 | 231 | 232 | if (type(key) == "table") 233 | or (type(key) == "function") 234 | then 235 | obj_as_key = true 236 | add_key_value(tabl, key, value) 237 | elseif (not last_index) 238 | or (type(key) ~= "number") 239 | or (key > last_index) 240 | or (key < 1) 241 | or (key > math.floor(key)) 242 | then 243 | local name, dot = safe_key(key, safe_key_cache, safe_string_cache) 244 | local value_prefix = string.format("%s%s%s=", ((not_empty and "") or prefix or ""), tbl_new_line(not_empty, deep), name) 245 | local info = obj_map[value] 246 | if info then 247 | parent_link = check_in_parents(value, parent) 248 | have_recover = true 249 | table.insert(recover, {tabl = tabl, dot = dot, name = name, value = value}) 250 | else 251 | local writed, deferred_creation = serialize_value( value, 252 | name, 253 | dot, 254 | tabl, 255 | value_prefix ) 256 | if writed then 257 | not_empty = true 258 | elseif deferred_creation then 259 | have_recover = true 260 | end 261 | end 262 | end 263 | end 264 | end 265 | return not_empty, have_recover and not(parent_link or obj_as_key) 266 | end 267 | 268 | function serialize_table(tabl, name, dot, parent, prefix, open) 269 | assert(obj_map[tabl] == nil) 270 | obj_map[tabl] = {name = name, dot = dot , parent = parent} 271 | testname(name) 272 | 273 | if prefix then 274 | prefix = prefix.."{" 275 | else 276 | prefix = "{" 277 | end 278 | 279 | deep = deep + 1 280 | 281 | local not_empty, last_index, deferred_creation = serialize_by_index(tabl, prefix) 282 | 283 | 284 | if not_empty then 285 | serialize_by_keys(tabl, last_index, parent, not_empty) 286 | else 287 | not_empty, deferred_creation = serialize_by_keys(tabl, last_index, parent, not_empty, prefix) 288 | end 289 | 290 | deep = deep - 1 291 | if not open then 292 | if not_empty then 293 | fnc_write(string.format("\n%s}", string.rep("\t", deep))) 294 | elseif deferred_creation and parent then 295 | obj_map[tabl].deferred_creation = deferred_creation 296 | else 297 | fnc_write(prefix) 298 | fnc_write("}") 299 | end 300 | end 301 | return not_empty, deferred_creation 302 | end 303 | 304 | local get_key, format_key 305 | 306 | function format_key(key) 307 | if obj_map[key] then 308 | return string.format("[%s]", get_key(key)) 309 | else 310 | return key 311 | end 312 | end 313 | 314 | 315 | 316 | function get_key(obj) 317 | local key = {} 318 | local info = obj_map[obj] 319 | local open_objects = 0 320 | local dot = "" 321 | while info do 322 | if info.deferred_creation then 323 | table.insert(key, 1, "={") 324 | open_objects = open_objects + 1 325 | info.deferred_creation = false; 326 | else 327 | table.insert(key, 1, dot) 328 | end 329 | dot = info.dot or "" 330 | table.insert(key, 1, format_key(info.name)) 331 | info = obj_map[info.parent] 332 | end 333 | return table.concat(key), open_objects 334 | end 335 | 336 | fnc_write("(") 337 | 338 | if value[recovery_name] then 339 | local indx = 0 340 | local new_name 341 | repeat 342 | new_name = recovery_name..indx 343 | indx = indx + 1 344 | until not value[new_name] 345 | recovery_name = new_name 346 | end 347 | 348 | local not_empty = serialize_table(value, "root", nil, nil, nil, true) 349 | 350 | -- recover links 351 | if next(keys.order) or next(recover) then 352 | deep = deep + 2 353 | local tabs = string.rep("\t", deep) 354 | fnc_write(string.format("%s%s=function(root)\n\t\troot.%s=nil;\n", 355 | ((not_empty and ",\n\t") or ""), recovery_name, recovery_name)) 356 | local idx = 0 357 | if next(keys.order) then 358 | 359 | local local_key = "\t\tlocal key={};\n" 360 | repeat 361 | local keys_old = keys 362 | keys = {order = {}, links = {}} 363 | for _, key in ipairs(keys_old.order) do 364 | if not obj_map[key] then 365 | idx = idx + 1 366 | 367 | local key_name = string.format("key[%i]", idx) 368 | local prefix = string.format("%s\t\t%s=", local_key, key_name) 369 | if serialize_value(key, key_name, nil, nil, prefix) then 370 | fnc_write(";\n") 371 | local_key = "" 372 | end 373 | end 374 | if obj_map[key] then 375 | for _, link in pairs(keys_old.links[key]) do 376 | if obj_map[link.value] then 377 | table.insert(recover, {tabl = link.tabl, name = key, value = link.value}) 378 | elseif serialize_value( link.value, 379 | key, 380 | nil, 381 | link.tabl, 382 | string.format("%s%s[%s]=", tabs, get_key(link.tabl), get_key(key))) 383 | then 384 | fnc_write(";\n") 385 | end 386 | end 387 | end 388 | end 389 | until not next(keys.order) 390 | end 391 | for _, rec in ipairs(recover) do 392 | local in_key, open_objects = get_key(rec.tabl) 393 | local dot = "" 394 | if open_objects == 0 and rec.dot then 395 | dot = rec.dot 396 | end 397 | 398 | fnc_write(string.format("%s%s%s%s=%s%s;\n", tabs, in_key, dot, format_key(rec.name), get_key(rec.value), string.rep("}", open_objects))) 399 | end 400 | fnc_write(string.format("\t\treturn root;\n\tend\n}):%s()", recovery_name)) 401 | else 402 | fnc_write("})") 403 | end 404 | if return_string then 405 | return table.concat(return_string) 406 | end 407 | else 408 | local serialized = safe_string(value) 409 | if serialized then 410 | if fnc_write then 411 | fnc_write(serialized) 412 | else 413 | return serialized 414 | end 415 | end 416 | end 417 | end 418 | --[[ 419 | local buf 420 | function step_by_step(text) 421 | io.write(text) 422 | table.insert(buf, text) 423 | --io.read(1) 424 | end 425 | 426 | for i = 1, 1 do 427 | buf = {} 428 | step_by_step("return ") 429 | serialize(table1, step_by_step) 430 | f = io.open("rex.txt", "wb") 431 | f:write(table.concat(buf)) 432 | f:close() 433 | local t, e = loadstring(table.concat(buf)) 434 | print(e) 435 | table1 = t() 436 | end]] 437 | -------------------------------------------------------------------------------- /lua/socket.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- LuaSocket helper module 3 | -- Author: Diego Nehab 4 | -- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $ 5 | ----------------------------------------------------------------------------- 6 | 7 | ----------------------------------------------------------------------------- 8 | -- Declare module and import dependencies 9 | ----------------------------------------------------------------------------- 10 | local base = _G 11 | local string = require("string") 12 | local math = require("math") 13 | local socket = require("socket.core") 14 | module("socket") 15 | 16 | ----------------------------------------------------------------------------- 17 | -- Exported auxiliar functions 18 | ----------------------------------------------------------------------------- 19 | function connect(address, port, laddress, lport) 20 | local sock, err = socket.tcp() 21 | if not sock then return nil, err end 22 | if laddress then 23 | local res, err = sock:bind(laddress, lport, -1) 24 | if not res then return nil, err end 25 | end 26 | local res, err = sock:connect(address, port) 27 | if not res then return nil, err end 28 | return sock 29 | end 30 | 31 | function bind(host, port, backlog) 32 | local sock, err = socket.tcp() 33 | if not sock then return nil, err end 34 | sock:setoption("reuseaddr", true) 35 | local res, err = sock:bind(host, port) 36 | if not res then return nil, err end 37 | res, err = sock:listen(backlog) 38 | if not res then return nil, err end 39 | return sock 40 | end 41 | 42 | try = newtry() 43 | 44 | function choose(table) 45 | return function(name, opt1, opt2) 46 | if base.type(name) ~= "string" then 47 | name, opt1, opt2 = "default", name, opt1 48 | end 49 | local f = table[name or "nil"] 50 | if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) 51 | else return f(opt1, opt2) end 52 | end 53 | end 54 | 55 | ----------------------------------------------------------------------------- 56 | -- Socket sources and sinks, conforming to LTN12 57 | ----------------------------------------------------------------------------- 58 | -- create namespaces inside LuaSocket namespace 59 | sourcet = {} 60 | sinkt = {} 61 | 62 | BLOCKSIZE = 2048 63 | 64 | sinkt["close-when-done"] = function(sock) 65 | return base.setmetatable({ 66 | getfd = function() return sock:getfd() end, 67 | dirty = function() return sock:dirty() end 68 | }, { 69 | __call = function(self, chunk, err) 70 | if not chunk then 71 | sock:close() 72 | return 1 73 | else return sock:send(chunk) end 74 | end 75 | }) 76 | end 77 | 78 | sinkt["keep-open"] = function(sock) 79 | return base.setmetatable({ 80 | getfd = function() return sock:getfd() end, 81 | dirty = function() return sock:dirty() end 82 | }, { 83 | __call = function(self, chunk, err) 84 | if chunk then return sock:send(chunk) 85 | else return 1 end 86 | end 87 | }) 88 | end 89 | 90 | sinkt["default"] = sinkt["keep-open"] 91 | 92 | sink = choose(sinkt) 93 | 94 | sourcet["by-length"] = function(sock, length) 95 | return base.setmetatable({ 96 | getfd = function() return sock:getfd() end, 97 | dirty = function() return sock:dirty() end 98 | }, { 99 | __call = function() 100 | if length <= 0 then return nil end 101 | local size = math.min(socket.BLOCKSIZE, length) 102 | local chunk, err = sock:receive(size) 103 | if err then return nil, err end 104 | length = length - string.len(chunk) 105 | return chunk 106 | end 107 | }) 108 | end 109 | 110 | sourcet["until-closed"] = function(sock) 111 | local done 112 | return base.setmetatable({ 113 | getfd = function() return sock:getfd() end, 114 | dirty = function() return sock:dirty() end 115 | }, { 116 | __call = function() 117 | if done then return nil end 118 | local chunk, err, partial = sock:receive(socket.BLOCKSIZE) 119 | if not err then return chunk 120 | elseif err == "closed" then 121 | sock:close() 122 | done = 1 123 | return partial 124 | else return nil, err end 125 | end 126 | }) 127 | end 128 | 129 | 130 | sourcet["default"] = sourcet["until-closed"] 131 | 132 | source = choose(sourcet) 133 | 134 | -------------------------------------------------------------------------------- /lua/socket/ftp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- FTP support for the Lua language 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module and import dependencies 10 | ----------------------------------------------------------------------------- 11 | local base = _G 12 | local table = require("table") 13 | local string = require("string") 14 | local math = require("math") 15 | local socket = require("socket") 16 | local url = require("socket.url") 17 | local tp = require("socket.tp") 18 | local ltn12 = require("ltn12") 19 | module("socket.ftp") 20 | 21 | ----------------------------------------------------------------------------- 22 | -- Program constants 23 | ----------------------------------------------------------------------------- 24 | -- timeout in seconds before the program gives up on a connection 25 | TIMEOUT = 60 26 | -- default port for ftp service 27 | PORT = 21 28 | -- this is the default anonymous password. used when no password is 29 | -- provided in url. should be changed to your e-mail. 30 | USER = "ftp" 31 | PASSWORD = "anonymous@anonymous.org" 32 | 33 | ----------------------------------------------------------------------------- 34 | -- Low level FTP API 35 | ----------------------------------------------------------------------------- 36 | local metat = { __index = {} } 37 | 38 | function open(server, port, create) 39 | local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) 40 | local f = base.setmetatable({ tp = tp }, metat) 41 | -- make sure everything gets closed in an exception 42 | f.try = socket.newtry(function() f:close() end) 43 | return f 44 | end 45 | 46 | function metat.__index:portconnect() 47 | self.try(self.server:settimeout(TIMEOUT)) 48 | self.data = self.try(self.server:accept()) 49 | self.try(self.data:settimeout(TIMEOUT)) 50 | end 51 | 52 | function metat.__index:pasvconnect() 53 | self.data = self.try(socket.tcp()) 54 | self.try(self.data:settimeout(TIMEOUT)) 55 | self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) 56 | end 57 | 58 | function metat.__index:login(user, password) 59 | self.try(self.tp:command("user", user or USER)) 60 | local code, reply = self.try(self.tp:check{"2..", 331}) 61 | if code == 331 then 62 | self.try(self.tp:command("pass", password or PASSWORD)) 63 | self.try(self.tp:check("2..")) 64 | end 65 | return 1 66 | end 67 | 68 | function metat.__index:pasv() 69 | self.try(self.tp:command("pasv")) 70 | local code, reply = self.try(self.tp:check("2..")) 71 | local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" 72 | local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) 73 | self.try(a and b and c and d and p1 and p2, reply) 74 | self.pasvt = { 75 | ip = string.format("%d.%d.%d.%d", a, b, c, d), 76 | port = p1*256 + p2 77 | } 78 | if self.server then 79 | self.server:close() 80 | self.server = nil 81 | end 82 | return self.pasvt.ip, self.pasvt.port 83 | end 84 | 85 | function metat.__index:port(ip, port) 86 | self.pasvt = nil 87 | if not ip then 88 | ip, port = self.try(self.tp:getcontrol():getsockname()) 89 | self.server = self.try(socket.bind(ip, 0)) 90 | ip, port = self.try(self.server:getsockname()) 91 | self.try(self.server:settimeout(TIMEOUT)) 92 | end 93 | local pl = math.mod(port, 256) 94 | local ph = (port - pl)/256 95 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") 96 | self.try(self.tp:command("port", arg)) 97 | self.try(self.tp:check("2..")) 98 | return 1 99 | end 100 | 101 | function metat.__index:send(sendt) 102 | self.try(self.pasvt or self.server, "need port or pasv first") 103 | -- if there is a pasvt table, we already sent a PASV command 104 | -- we just get the data connection into self.data 105 | if self.pasvt then self:pasvconnect() end 106 | -- get the transfer argument and command 107 | local argument = sendt.argument or 108 | url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) 109 | if argument == "" then argument = nil end 110 | local command = sendt.command or "stor" 111 | -- send the transfer command and check the reply 112 | self.try(self.tp:command(command, argument)) 113 | local code, reply = self.try(self.tp:check{"2..", "1.."}) 114 | -- if there is not a a pasvt table, then there is a server 115 | -- and we already sent a PORT command 116 | if not self.pasvt then self:portconnect() end 117 | -- get the sink, source and step for the transfer 118 | local step = sendt.step or ltn12.pump.step 119 | local readt = {self.tp.c} 120 | local checkstep = function(src, snk) 121 | -- check status in control connection while downloading 122 | local readyt = socket.select(readt, nil, 0) 123 | if readyt[tp] then code = self.try(self.tp:check("2..")) end 124 | return step(src, snk) 125 | end 126 | local sink = socket.sink("close-when-done", self.data) 127 | -- transfer all data and check error 128 | self.try(ltn12.pump.all(sendt.source, sink, checkstep)) 129 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 130 | -- done with data connection 131 | self.data:close() 132 | -- find out how many bytes were sent 133 | local sent = socket.skip(1, self.data:getstats()) 134 | self.data = nil 135 | return sent 136 | end 137 | 138 | function metat.__index:receive(recvt) 139 | self.try(self.pasvt or self.server, "need port or pasv first") 140 | if self.pasvt then self:pasvconnect() end 141 | local argument = recvt.argument or 142 | url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) 143 | if argument == "" then argument = nil end 144 | local command = recvt.command or "retr" 145 | self.try(self.tp:command(command, argument)) 146 | local code = self.try(self.tp:check{"1..", "2.."}) 147 | if not self.pasvt then self:portconnect() end 148 | local source = socket.source("until-closed", self.data) 149 | local step = recvt.step or ltn12.pump.step 150 | self.try(ltn12.pump.all(source, recvt.sink, step)) 151 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 152 | self.data:close() 153 | self.data = nil 154 | return 1 155 | end 156 | 157 | function metat.__index:cwd(dir) 158 | self.try(self.tp:command("cwd", dir)) 159 | self.try(self.tp:check(250)) 160 | return 1 161 | end 162 | 163 | function metat.__index:type(type) 164 | self.try(self.tp:command("type", type)) 165 | self.try(self.tp:check(200)) 166 | return 1 167 | end 168 | 169 | function metat.__index:greet() 170 | local code = self.try(self.tp:check{"1..", "2.."}) 171 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end 172 | return 1 173 | end 174 | 175 | function metat.__index:quit() 176 | self.try(self.tp:command("quit")) 177 | self.try(self.tp:check("2..")) 178 | return 1 179 | end 180 | 181 | function metat.__index:close() 182 | if self.data then self.data:close() end 183 | if self.server then self.server:close() end 184 | return self.tp:close() 185 | end 186 | 187 | ----------------------------------------------------------------------------- 188 | -- High level FTP API 189 | ----------------------------------------------------------------------------- 190 | local function override(t) 191 | if t.url then 192 | local u = url.parse(t.url) 193 | for i,v in base.pairs(t) do 194 | u[i] = v 195 | end 196 | return u 197 | else return t end 198 | end 199 | 200 | local function tput(putt) 201 | putt = override(putt) 202 | socket.try(putt.host, "missing hostname") 203 | local f = open(putt.host, putt.port, putt.create) 204 | f:greet() 205 | f:login(putt.user, putt.password) 206 | if putt.type then f:type(putt.type) end 207 | f:pasv() 208 | local sent = f:send(putt) 209 | f:quit() 210 | f:close() 211 | return sent 212 | end 213 | 214 | local default = { 215 | path = "/", 216 | scheme = "ftp" 217 | } 218 | 219 | local function parse(u) 220 | local t = socket.try(url.parse(u, default)) 221 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") 222 | socket.try(t.host, "missing hostname") 223 | local pat = "^type=(.)$" 224 | if t.params then 225 | t.type = socket.skip(2, string.find(t.params, pat)) 226 | socket.try(t.type == "a" or t.type == "i", 227 | "invalid type '" .. t.type .. "'") 228 | end 229 | return t 230 | end 231 | 232 | local function sput(u, body) 233 | local putt = parse(u) 234 | putt.source = ltn12.source.string(body) 235 | return tput(putt) 236 | end 237 | 238 | put = socket.protect(function(putt, body) 239 | if base.type(putt) == "string" then return sput(putt, body) 240 | else return tput(putt) end 241 | end) 242 | 243 | local function tget(gett) 244 | gett = override(gett) 245 | socket.try(gett.host, "missing hostname") 246 | local f = open(gett.host, gett.port, gett.create) 247 | f:greet() 248 | f:login(gett.user, gett.password) 249 | if gett.type then f:type(gett.type) end 250 | f:pasv() 251 | f:receive(gett) 252 | f:quit() 253 | return f:close() 254 | end 255 | 256 | local function sget(u) 257 | local gett = parse(u) 258 | local t = {} 259 | gett.sink = ltn12.sink.table(t) 260 | tget(gett) 261 | return table.concat(t) 262 | end 263 | 264 | command = socket.protect(function(cmdt) 265 | cmdt = override(cmdt) 266 | socket.try(cmdt.host, "missing hostname") 267 | socket.try(cmdt.command, "missing command") 268 | local f = open(cmdt.host, cmdt.port, cmdt.create) 269 | f:greet() 270 | f:login(cmdt.user, cmdt.password) 271 | f.try(f.tp:command(cmdt.command, cmdt.argument)) 272 | if cmdt.check then f.try(f.tp:check(cmdt.check)) end 273 | f:quit() 274 | return f:close() 275 | end) 276 | 277 | get = socket.protect(function(gett) 278 | if base.type(gett) == "string" then return sget(gett) 279 | else return tget(gett) end 280 | end) 281 | 282 | -------------------------------------------------------------------------------- /lua/socket/http.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- HTTP/1.1 client support for the Lua language. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: http.lua,v 1.70 2007/03/12 04:08:40 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module and import dependencies 10 | ------------------------------------------------------------------------------- 11 | local socket = require("socket") 12 | local url = require("socket.url") 13 | local ltn12 = require("ltn12") 14 | local mime = require("mime") 15 | local string = require("string") 16 | local base = _G 17 | local table = require("table") 18 | module("socket.http") 19 | 20 | ----------------------------------------------------------------------------- 21 | -- Program constants 22 | ----------------------------------------------------------------------------- 23 | -- connection timeout in seconds 24 | TIMEOUT = 60 25 | -- default port for document retrieval 26 | PORT = 80 27 | -- user agent field sent in request 28 | USERAGENT = socket._VERSION 29 | 30 | ----------------------------------------------------------------------------- 31 | -- Reads MIME headers from a connection, unfolding where needed 32 | ----------------------------------------------------------------------------- 33 | local function receiveheaders(sock, headers) 34 | local line, name, value, err 35 | headers = headers or {} 36 | -- get first line 37 | line, err = sock:receive() 38 | if err then return nil, err end 39 | -- headers go until a blank line is found 40 | while line ~= "" do 41 | -- get field-name and value 42 | name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) 43 | if not (name and value) then return nil, "malformed reponse headers" end 44 | name = string.lower(name) 45 | -- get next line (value might be folded) 46 | line, err = sock:receive() 47 | if err then return nil, err end 48 | -- unfold any folded values 49 | while string.find(line, "^%s") do 50 | value = value .. line 51 | line = sock:receive() 52 | if err then return nil, err end 53 | end 54 | -- save pair in table 55 | if headers[name] then headers[name] = headers[name] .. ", " .. value 56 | else headers[name] = value end 57 | end 58 | return headers 59 | end 60 | 61 | ----------------------------------------------------------------------------- 62 | -- Extra sources and sinks 63 | ----------------------------------------------------------------------------- 64 | socket.sourcet["http-chunked"] = function(sock, headers) 65 | return base.setmetatable({ 66 | getfd = function() return sock:getfd() end, 67 | dirty = function() return sock:dirty() end 68 | }, { 69 | __call = function() 70 | -- get chunk size, skip extention 71 | local line, err = sock:receive() 72 | if err then return nil, err end 73 | local size = base.tonumber(string.gsub(line, ";.*", ""), 16) 74 | if not size then return nil, "invalid chunk size" end 75 | -- was it the last chunk? 76 | if size > 0 then 77 | -- if not, get chunk and skip terminating CRLF 78 | local chunk, err, part = sock:receive(size) 79 | if chunk then sock:receive() end 80 | return chunk, err 81 | else 82 | -- if it was, read trailers into headers table 83 | headers, err = receiveheaders(sock, headers) 84 | if not headers then return nil, err end 85 | end 86 | end 87 | }) 88 | end 89 | 90 | socket.sinkt["http-chunked"] = function(sock) 91 | return base.setmetatable({ 92 | getfd = function() return sock:getfd() end, 93 | dirty = function() return sock:dirty() end 94 | }, { 95 | __call = function(self, chunk, err) 96 | if not chunk then return sock:send("0\r\n\r\n") end 97 | local size = string.format("%X\r\n", string.len(chunk)) 98 | return sock:send(size .. chunk .. "\r\n") 99 | end 100 | }) 101 | end 102 | 103 | ----------------------------------------------------------------------------- 104 | -- Low level HTTP API 105 | ----------------------------------------------------------------------------- 106 | local metat = { __index = {} } 107 | 108 | function open(host, port, create) 109 | -- create socket with user connect function, or with default 110 | local c = socket.try((create or socket.tcp)()) 111 | local h = base.setmetatable({ c = c }, metat) 112 | -- create finalized try 113 | h.try = socket.newtry(function() h:close() end) 114 | -- set timeout before connecting 115 | h.try(c:settimeout(TIMEOUT)) 116 | h.try(c:connect(host, port or PORT)) 117 | -- here everything worked 118 | return h 119 | end 120 | 121 | function metat.__index:sendrequestline(method, uri) 122 | local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) 123 | return self.try(self.c:send(reqline)) 124 | end 125 | 126 | function metat.__index:sendheaders(headers) 127 | local h = "\r\n" 128 | for i, v in base.pairs(headers) do 129 | h = i .. ": " .. v .. "\r\n" .. h 130 | end 131 | self.try(self.c:send(h)) 132 | return 1 133 | end 134 | 135 | function metat.__index:sendbody(headers, source, step) 136 | source = source or ltn12.source.empty() 137 | step = step or ltn12.pump.step 138 | -- if we don't know the size in advance, send chunked and hope for the best 139 | local mode = "http-chunked" 140 | if headers["content-length"] then mode = "keep-open" end 141 | return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) 142 | end 143 | 144 | function metat.__index:receivestatusline() 145 | local status = self.try(self.c:receive(5)) 146 | -- identify HTTP/0.9 responses, which do not contain a status line 147 | -- this is just a heuristic, but is what the RFC recommends 148 | if status ~= "HTTP/" then return nil, status end 149 | -- otherwise proceed reading a status line 150 | status = self.try(self.c:receive("*l", status)) 151 | local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) 152 | return self.try(base.tonumber(code), status) 153 | end 154 | 155 | function metat.__index:receiveheaders() 156 | return self.try(receiveheaders(self.c)) 157 | end 158 | 159 | function metat.__index:receivebody(headers, sink, step) 160 | sink = sink or ltn12.sink.null() 161 | step = step or ltn12.pump.step 162 | local length = base.tonumber(headers["content-length"]) 163 | local t = headers["transfer-encoding"] -- shortcut 164 | local mode = "default" -- connection close 165 | if t and t ~= "identity" then mode = "http-chunked" 166 | elseif base.tonumber(headers["content-length"]) then mode = "by-length" end 167 | return self.try(ltn12.pump.all(socket.source(mode, self.c, length), 168 | sink, step)) 169 | end 170 | 171 | function metat.__index:receive09body(status, sink, step) 172 | local source = ltn12.source.rewind(socket.source("until-closed", self.c)) 173 | source(status) 174 | return self.try(ltn12.pump.all(source, sink, step)) 175 | end 176 | 177 | function metat.__index:close() 178 | return self.c:close() 179 | end 180 | 181 | ----------------------------------------------------------------------------- 182 | -- High level HTTP API 183 | ----------------------------------------------------------------------------- 184 | local function adjusturi(reqt) 185 | local u = reqt 186 | -- if there is a proxy, we need the full url. otherwise, just a part. 187 | if not reqt.proxy and not PROXY then 188 | u = { 189 | path = socket.try(reqt.path, "invalid path 'nil'"), 190 | params = reqt.params, 191 | query = reqt.query, 192 | fragment = reqt.fragment 193 | } 194 | end 195 | return url.build(u) 196 | end 197 | 198 | local function adjustproxy(reqt) 199 | local proxy = reqt.proxy or PROXY 200 | if proxy then 201 | proxy = url.parse(proxy) 202 | return proxy.host, proxy.port or 3128 203 | else 204 | return reqt.host, reqt.port 205 | end 206 | end 207 | 208 | local function adjustheaders(reqt) 209 | -- default headers 210 | local lower = { 211 | ["user-agent"] = USERAGENT, 212 | ["host"] = reqt.host, 213 | ["connection"] = "close, TE", 214 | ["te"] = "trailers" 215 | } 216 | -- if we have authentication information, pass it along 217 | if reqt.user and reqt.password then 218 | lower["authorization"] = 219 | "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) 220 | end 221 | -- override with user headers 222 | for i,v in base.pairs(reqt.headers or lower) do 223 | lower[string.lower(i)] = v 224 | end 225 | return lower 226 | end 227 | 228 | -- default url parts 229 | local default = { 230 | host = "", 231 | port = PORT, 232 | path ="/", 233 | scheme = "http" 234 | } 235 | 236 | local function adjustrequest(reqt) 237 | -- parse url if provided 238 | local nreqt = reqt.url and url.parse(reqt.url, default) or {} 239 | -- explicit components override url 240 | for i,v in base.pairs(reqt) do nreqt[i] = v end 241 | if nreqt.port == "" then nreqt.port = 80 end 242 | socket.try(nreqt.host and nreqt.host ~= "", 243 | "invalid host '" .. base.tostring(nreqt.host) .. "'") 244 | -- compute uri if user hasn't overriden 245 | nreqt.uri = reqt.uri or adjusturi(nreqt) 246 | -- ajust host and port if there is a proxy 247 | nreqt.host, nreqt.port = adjustproxy(nreqt) 248 | -- adjust headers in request 249 | nreqt.headers = adjustheaders(nreqt) 250 | return nreqt 251 | end 252 | 253 | local function shouldredirect(reqt, code, headers) 254 | return headers.location and 255 | string.gsub(headers.location, "%s", "") ~= "" and 256 | (reqt.redirect ~= false) and 257 | (code == 301 or code == 302) and 258 | (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") 259 | and (not reqt.nredirects or reqt.nredirects < 5) 260 | end 261 | 262 | local function shouldreceivebody(reqt, code) 263 | if reqt.method == "HEAD" then return nil end 264 | if code == 204 or code == 304 then return nil end 265 | if code >= 100 and code < 200 then return nil end 266 | return 1 267 | end 268 | 269 | -- forward declarations 270 | local trequest, tredirect 271 | 272 | function tredirect(reqt, location) 273 | local result, code, headers, status = trequest { 274 | -- the RFC says the redirect URL has to be absolute, but some 275 | -- servers do not respect that 276 | url = url.absolute(reqt.url, location), 277 | source = reqt.source, 278 | sink = reqt.sink, 279 | headers = reqt.headers, 280 | proxy = reqt.proxy, 281 | nredirects = (reqt.nredirects or 0) + 1, 282 | create = reqt.create 283 | } 284 | -- pass location header back as a hint we redirected 285 | headers = headers or {} 286 | headers.location = headers.location or location 287 | return result, code, headers, status 288 | end 289 | 290 | function trequest(reqt) 291 | -- we loop until we get what we want, or 292 | -- until we are sure there is no way to get it 293 | local nreqt = adjustrequest(reqt) 294 | local h = open(nreqt.host, nreqt.port, nreqt.create) 295 | -- send request line and headers 296 | h:sendrequestline(nreqt.method, nreqt.uri) 297 | h:sendheaders(nreqt.headers) 298 | -- if there is a body, send it 299 | if nreqt.source then 300 | h:sendbody(nreqt.headers, nreqt.source, nreqt.step) 301 | end 302 | local code, status = h:receivestatusline() 303 | -- if it is an HTTP/0.9 server, simply get the body and we are done 304 | if not code then 305 | h:receive09body(status, nreqt.sink, nreqt.step) 306 | return 1, 200 307 | end 308 | local headers 309 | -- ignore any 100-continue messages 310 | while code == 100 do 311 | headers = h:receiveheaders() 312 | code, status = h:receivestatusline() 313 | end 314 | headers = h:receiveheaders() 315 | -- at this point we should have a honest reply from the server 316 | -- we can't redirect if we already used the source, so we report the error 317 | if shouldredirect(nreqt, code, headers) and not nreqt.source then 318 | h:close() 319 | return tredirect(reqt, headers.location) 320 | end 321 | -- here we are finally done 322 | if shouldreceivebody(nreqt, code) then 323 | h:receivebody(headers, nreqt.sink, nreqt.step) 324 | end 325 | h:close() 326 | return 1, code, headers, status 327 | end 328 | 329 | local function srequest(u, b) 330 | local t = {} 331 | local reqt = { 332 | url = u, 333 | sink = ltn12.sink.table(t) 334 | } 335 | if b then 336 | reqt.source = ltn12.source.string(b) 337 | reqt.headers = { 338 | ["content-length"] = string.len(b), 339 | ["content-type"] = "application/x-www-form-urlencoded" 340 | } 341 | reqt.method = "POST" 342 | end 343 | local code, headers, status = socket.skip(1, trequest(reqt)) 344 | return table.concat(t), code, headers, status 345 | end 346 | 347 | request = socket.protect(function(reqt, body) 348 | if base.type(reqt) == "string" then return srequest(reqt, body) 349 | else return trequest(reqt) end 350 | end) 351 | -------------------------------------------------------------------------------- /lua/socket/smtp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- SMTP client support for the Lua language. 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: smtp.lua,v 1.46 2007/03/12 04:08:40 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module and import dependencies 10 | ----------------------------------------------------------------------------- 11 | local base = _G 12 | local coroutine = require("coroutine") 13 | local string = require("string") 14 | local math = require("math") 15 | local os = require("os") 16 | local socket = require("socket") 17 | local tp = require("socket.tp") 18 | local ltn12 = require("ltn12") 19 | local mime = require("mime") 20 | module("socket.smtp") 21 | 22 | ----------------------------------------------------------------------------- 23 | -- Program constants 24 | ----------------------------------------------------------------------------- 25 | -- timeout for connection 26 | TIMEOUT = 60 27 | -- default server used to send e-mails 28 | SERVER = "localhost" 29 | -- default port 30 | PORT = 25 31 | -- domain used in HELO command and default sendmail 32 | -- If we are under a CGI, try to get from environment 33 | DOMAIN = os.getenv("SERVER_NAME") or "localhost" 34 | -- default time zone (means we don't know) 35 | ZONE = "-0000" 36 | 37 | --------------------------------------------------------------------------- 38 | -- Low level SMTP API 39 | ----------------------------------------------------------------------------- 40 | local metat = { __index = {} } 41 | 42 | function metat.__index:greet(domain) 43 | self.try(self.tp:check("2..")) 44 | self.try(self.tp:command("EHLO", domain or DOMAIN)) 45 | return socket.skip(1, self.try(self.tp:check("2.."))) 46 | end 47 | 48 | function metat.__index:mail(from) 49 | self.try(self.tp:command("MAIL", "FROM:" .. from)) 50 | return self.try(self.tp:check("2..")) 51 | end 52 | 53 | function metat.__index:rcpt(to) 54 | self.try(self.tp:command("RCPT", "TO:" .. to)) 55 | return self.try(self.tp:check("2..")) 56 | end 57 | 58 | function metat.__index:data(src, step) 59 | self.try(self.tp:command("DATA")) 60 | self.try(self.tp:check("3..")) 61 | self.try(self.tp:source(src, step)) 62 | self.try(self.tp:send("\r\n.\r\n")) 63 | return self.try(self.tp:check("2..")) 64 | end 65 | 66 | function metat.__index:quit() 67 | self.try(self.tp:command("QUIT")) 68 | return self.try(self.tp:check("2..")) 69 | end 70 | 71 | function metat.__index:close() 72 | return self.tp:close() 73 | end 74 | 75 | function metat.__index:login(user, password) 76 | self.try(self.tp:command("AUTH", "LOGIN")) 77 | self.try(self.tp:check("3..")) 78 | self.try(self.tp:command(mime.b64(user))) 79 | self.try(self.tp:check("3..")) 80 | self.try(self.tp:command(mime.b64(password))) 81 | return self.try(self.tp:check("2..")) 82 | end 83 | 84 | function metat.__index:plain(user, password) 85 | local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) 86 | self.try(self.tp:command("AUTH", auth)) 87 | return self.try(self.tp:check("2..")) 88 | end 89 | 90 | function metat.__index:auth(user, password, ext) 91 | if not user or not password then return 1 end 92 | if string.find(ext, "AUTH[^\n]+LOGIN") then 93 | return self:login(user, password) 94 | elseif string.find(ext, "AUTH[^\n]+PLAIN") then 95 | return self:plain(user, password) 96 | else 97 | self.try(nil, "authentication not supported") 98 | end 99 | end 100 | 101 | -- send message or throw an exception 102 | function metat.__index:send(mailt) 103 | self:mail(mailt.from) 104 | if base.type(mailt.rcpt) == "table" then 105 | for i,v in base.ipairs(mailt.rcpt) do 106 | self:rcpt(v) 107 | end 108 | else 109 | self:rcpt(mailt.rcpt) 110 | end 111 | self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) 112 | end 113 | 114 | function open(server, port, create) 115 | local tp = socket.try(tp.connect(server or SERVER, port or PORT, 116 | TIMEOUT, create)) 117 | local s = base.setmetatable({tp = tp}, metat) 118 | -- make sure tp is closed if we get an exception 119 | s.try = socket.newtry(function() 120 | s:close() 121 | end) 122 | return s 123 | end 124 | 125 | -- convert headers to lowercase 126 | local function lower_headers(headers) 127 | local lower = {} 128 | for i,v in base.pairs(headers or lower) do 129 | lower[string.lower(i)] = v 130 | end 131 | return lower 132 | end 133 | 134 | --------------------------------------------------------------------------- 135 | -- Multipart message source 136 | ----------------------------------------------------------------------------- 137 | -- returns a hopefully unique mime boundary 138 | local seqno = 0 139 | local function newboundary() 140 | seqno = seqno + 1 141 | return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), 142 | math.random(0, 99999), seqno) 143 | end 144 | 145 | -- send_message forward declaration 146 | local send_message 147 | 148 | -- yield the headers all at once, it's faster 149 | local function send_headers(headers) 150 | local h = "\r\n" 151 | for i,v in base.pairs(headers) do 152 | h = i .. ': ' .. v .. "\r\n" .. h 153 | end 154 | coroutine.yield(h) 155 | end 156 | 157 | -- yield multipart message body from a multipart message table 158 | local function send_multipart(mesgt) 159 | -- make sure we have our boundary and send headers 160 | local bd = newboundary() 161 | local headers = lower_headers(mesgt.headers or {}) 162 | headers['content-type'] = headers['content-type'] or 'multipart/mixed' 163 | headers['content-type'] = headers['content-type'] .. 164 | '; boundary="' .. bd .. '"' 165 | send_headers(headers) 166 | -- send preamble 167 | if mesgt.body.preamble then 168 | coroutine.yield(mesgt.body.preamble) 169 | coroutine.yield("\r\n") 170 | end 171 | -- send each part separated by a boundary 172 | for i, m in base.ipairs(mesgt.body) do 173 | coroutine.yield("\r\n--" .. bd .. "\r\n") 174 | send_message(m) 175 | end 176 | -- send last boundary 177 | coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") 178 | -- send epilogue 179 | if mesgt.body.epilogue then 180 | coroutine.yield(mesgt.body.epilogue) 181 | coroutine.yield("\r\n") 182 | end 183 | end 184 | 185 | -- yield message body from a source 186 | local function send_source(mesgt) 187 | -- make sure we have a content-type 188 | local headers = lower_headers(mesgt.headers or {}) 189 | headers['content-type'] = headers['content-type'] or 190 | 'text/plain; charset="iso-8859-1"' 191 | send_headers(headers) 192 | -- send body from source 193 | while true do 194 | local chunk, err = mesgt.body() 195 | if err then coroutine.yield(nil, err) 196 | elseif chunk then coroutine.yield(chunk) 197 | else break end 198 | end 199 | end 200 | 201 | -- yield message body from a string 202 | local function send_string(mesgt) 203 | -- make sure we have a content-type 204 | local headers = lower_headers(mesgt.headers or {}) 205 | headers['content-type'] = headers['content-type'] or 206 | 'text/plain; charset="iso-8859-1"' 207 | send_headers(headers) 208 | -- send body from string 209 | coroutine.yield(mesgt.body) 210 | end 211 | 212 | -- message source 213 | function send_message(mesgt) 214 | if base.type(mesgt.body) == "table" then send_multipart(mesgt) 215 | elseif base.type(mesgt.body) == "function" then send_source(mesgt) 216 | else send_string(mesgt) end 217 | end 218 | 219 | -- set defaul headers 220 | local function adjust_headers(mesgt) 221 | local lower = lower_headers(mesgt.headers) 222 | lower["date"] = lower["date"] or 223 | os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) 224 | lower["x-mailer"] = lower["x-mailer"] or socket._VERSION 225 | -- this can't be overriden 226 | lower["mime-version"] = "1.0" 227 | return lower 228 | end 229 | 230 | function message(mesgt) 231 | mesgt.headers = adjust_headers(mesgt) 232 | -- create and return message source 233 | local co = coroutine.create(function() send_message(mesgt) end) 234 | return function() 235 | local ret, a, b = coroutine.resume(co) 236 | if ret then return a, b 237 | else return nil, a end 238 | end 239 | end 240 | 241 | --------------------------------------------------------------------------- 242 | -- High level SMTP API 243 | ----------------------------------------------------------------------------- 244 | send = socket.protect(function(mailt) 245 | local s = open(mailt.server, mailt.port, mailt.create) 246 | local ext = s:greet(mailt.domain) 247 | s:auth(mailt.user, mailt.password, ext) 248 | s:send(mailt) 249 | s:quit() 250 | return s:close() 251 | end) 252 | -------------------------------------------------------------------------------- /lua/socket/tp.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- Unified SMTP/FTP subsystem 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: tp.lua,v 1.22 2006/03/14 09:04:15 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module and import dependencies 10 | ----------------------------------------------------------------------------- 11 | local base = _G 12 | local string = require("string") 13 | local socket = require("socket") 14 | local ltn12 = require("ltn12") 15 | module("socket.tp") 16 | 17 | ----------------------------------------------------------------------------- 18 | -- Program constants 19 | ----------------------------------------------------------------------------- 20 | TIMEOUT = 60 21 | 22 | ----------------------------------------------------------------------------- 23 | -- Implementation 24 | ----------------------------------------------------------------------------- 25 | -- gets server reply (works for SMTP and FTP) 26 | local function get_reply(c) 27 | local code, current, sep 28 | local line, err = c:receive() 29 | local reply = line 30 | if err then return nil, err end 31 | code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) 32 | if not code then return nil, "invalid server reply" end 33 | if sep == "-" then -- reply is multiline 34 | repeat 35 | line, err = c:receive() 36 | if err then return nil, err end 37 | current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) 38 | reply = reply .. "\n" .. line 39 | -- reply ends with same code 40 | until code == current and sep == " " 41 | end 42 | return code, reply 43 | end 44 | 45 | -- metatable for sock object 46 | local metat = { __index = {} } 47 | 48 | function metat.__index:check(ok) 49 | local code, reply = get_reply(self.c) 50 | if not code then return nil, reply end 51 | if base.type(ok) ~= "function" then 52 | if base.type(ok) == "table" then 53 | for i, v in base.ipairs(ok) do 54 | if string.find(code, v) then 55 | return base.tonumber(code), reply 56 | end 57 | end 58 | return nil, reply 59 | else 60 | if string.find(code, ok) then return base.tonumber(code), reply 61 | else return nil, reply end 62 | end 63 | else return ok(base.tonumber(code), reply) end 64 | end 65 | 66 | function metat.__index:command(cmd, arg) 67 | if arg then 68 | return self.c:send(cmd .. " " .. arg.. "\r\n") 69 | else 70 | return self.c:send(cmd .. "\r\n") 71 | end 72 | end 73 | 74 | function metat.__index:sink(snk, pat) 75 | local chunk, err = c:receive(pat) 76 | return snk(chunk, err) 77 | end 78 | 79 | function metat.__index:send(data) 80 | return self.c:send(data) 81 | end 82 | 83 | function metat.__index:receive(pat) 84 | return self.c:receive(pat) 85 | end 86 | 87 | function metat.__index:getfd() 88 | return self.c:getfd() 89 | end 90 | 91 | function metat.__index:dirty() 92 | return self.c:dirty() 93 | end 94 | 95 | function metat.__index:getcontrol() 96 | return self.c 97 | end 98 | 99 | function metat.__index:source(source, step) 100 | local sink = socket.sink("keep-open", self.c) 101 | local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) 102 | return ret, err 103 | end 104 | 105 | -- closes the underlying c 106 | function metat.__index:close() 107 | self.c:close() 108 | return 1 109 | end 110 | 111 | -- connect with server and return c object 112 | function connect(host, port, timeout, create) 113 | local c, e = (create or socket.tcp)() 114 | if not c then return nil, e end 115 | c:settimeout(timeout or TIMEOUT) 116 | local r, e = c:connect(host, port) 117 | if not r then 118 | c:close() 119 | return nil, e 120 | end 121 | return base.setmetatable({c = c}, metat) 122 | end 123 | 124 | -------------------------------------------------------------------------------- /lua/socket/url.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- URI parsing, composition and relative URL resolution 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module 10 | ----------------------------------------------------------------------------- 11 | local string = require("string") 12 | local base = _G 13 | local table = require("table") 14 | module("socket.url") 15 | 16 | ----------------------------------------------------------------------------- 17 | -- Module version 18 | ----------------------------------------------------------------------------- 19 | _VERSION = "URL 1.0.1" 20 | 21 | ----------------------------------------------------------------------------- 22 | -- Encodes a string into its escaped hexadecimal representation 23 | -- Input 24 | -- s: binary string to be encoded 25 | -- Returns 26 | -- escaped representation of string binary 27 | ----------------------------------------------------------------------------- 28 | function escape(s) 29 | return string.gsub(s, "([^A-Za-z0-9_])", function(c) 30 | return string.format("%%%02x", string.byte(c)) 31 | end) 32 | end 33 | 34 | ----------------------------------------------------------------------------- 35 | -- Protects a path segment, to prevent it from interfering with the 36 | -- url parsing. 37 | -- Input 38 | -- s: binary string to be encoded 39 | -- Returns 40 | -- escaped representation of string binary 41 | ----------------------------------------------------------------------------- 42 | local function make_set(t) 43 | local s = {} 44 | for i,v in base.ipairs(t) do 45 | s[t[i]] = 1 46 | end 47 | return s 48 | end 49 | 50 | -- these are allowed withing a path segment, along with alphanum 51 | -- other characters must be escaped 52 | local segment_set = make_set { 53 | "-", "_", ".", "!", "~", "*", "'", "(", 54 | ")", ":", "@", "&", "=", "+", "$", ",", 55 | } 56 | 57 | local function protect_segment(s) 58 | return string.gsub(s, "([^A-Za-z0-9_])", function (c) 59 | if segment_set[c] then return c 60 | else return string.format("%%%02x", string.byte(c)) end 61 | end) 62 | end 63 | 64 | ----------------------------------------------------------------------------- 65 | -- Encodes a string into its escaped hexadecimal representation 66 | -- Input 67 | -- s: binary string to be encoded 68 | -- Returns 69 | -- escaped representation of string binary 70 | ----------------------------------------------------------------------------- 71 | function unescape(s) 72 | return string.gsub(s, "%%(%x%x)", function(hex) 73 | return string.char(base.tonumber(hex, 16)) 74 | end) 75 | end 76 | 77 | ----------------------------------------------------------------------------- 78 | -- Builds a path from a base path and a relative path 79 | -- Input 80 | -- base_path 81 | -- relative_path 82 | -- Returns 83 | -- corresponding absolute path 84 | ----------------------------------------------------------------------------- 85 | local function absolute_path(base_path, relative_path) 86 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end 87 | local path = string.gsub(base_path, "[^/]*$", "") 88 | path = path .. relative_path 89 | path = string.gsub(path, "([^/]*%./)", function (s) 90 | if s ~= "./" then return s else return "" end 91 | end) 92 | path = string.gsub(path, "/%.$", "/") 93 | local reduced 94 | while reduced ~= path do 95 | reduced = path 96 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 97 | if s ~= "../../" then return "" else return s end 98 | end) 99 | end 100 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) 101 | if s ~= "../.." then return "" else return s end 102 | end) 103 | return path 104 | end 105 | 106 | ----------------------------------------------------------------------------- 107 | -- Parses a url and returns a table with all its parts according to RFC 2396 108 | -- The following grammar describes the names given to the URL parts 109 | -- ::= :///;?# 110 | -- ::= @: 111 | -- ::= [:] 112 | -- :: = {/} 113 | -- Input 114 | -- url: uniform resource locator of request 115 | -- default: table with default values for each field 116 | -- Returns 117 | -- table with the following fields, where RFC naming conventions have 118 | -- been preserved: 119 | -- scheme, authority, userinfo, user, password, host, port, 120 | -- path, params, query, fragment 121 | -- Obs: 122 | -- the leading '/' in {/} is considered part of 123 | ----------------------------------------------------------------------------- 124 | function parse(url, default) 125 | -- initialize default parameters 126 | local parsed = {} 127 | for i,v in base.pairs(default or parsed) do parsed[i] = v end 128 | -- empty url is parsed to nil 129 | if not url or url == "" then return nil, "invalid url" end 130 | -- remove whitespace 131 | -- url = string.gsub(url, "%s", "") 132 | -- get fragment 133 | url = string.gsub(url, "#(.*)$", function(f) 134 | parsed.fragment = f 135 | return "" 136 | end) 137 | -- get scheme 138 | url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 139 | function(s) parsed.scheme = s; return "" end) 140 | -- get authority 141 | url = string.gsub(url, "^//([^/]*)", function(n) 142 | parsed.authority = n 143 | return "" 144 | end) 145 | -- get query stringing 146 | url = string.gsub(url, "%?(.*)", function(q) 147 | parsed.query = q 148 | return "" 149 | end) 150 | -- get params 151 | url = string.gsub(url, "%;(.*)", function(p) 152 | parsed.params = p 153 | return "" 154 | end) 155 | -- path is whatever was left 156 | if url ~= "" then parsed.path = url end 157 | local authority = parsed.authority 158 | if not authority then return parsed end 159 | authority = string.gsub(authority,"^([^@]*)@", 160 | function(u) parsed.userinfo = u; return "" end) 161 | authority = string.gsub(authority, ":([^:]*)$", 162 | function(p) parsed.port = p; return "" end) 163 | if authority ~= "" then parsed.host = authority end 164 | local userinfo = parsed.userinfo 165 | if not userinfo then return parsed end 166 | userinfo = string.gsub(userinfo, ":([^:]*)$", 167 | function(p) parsed.password = p; return "" end) 168 | parsed.user = userinfo 169 | return parsed 170 | end 171 | 172 | ----------------------------------------------------------------------------- 173 | -- Rebuilds a parsed URL from its components. 174 | -- Components are protected if any reserved or unallowed characters are found 175 | -- Input 176 | -- parsed: parsed URL, as returned by parse 177 | -- Returns 178 | -- a stringing with the corresponding URL 179 | ----------------------------------------------------------------------------- 180 | function build(parsed) 181 | local ppath = parse_path(parsed.path or "") 182 | local url = build_path(ppath) 183 | if parsed.params then url = url .. ";" .. parsed.params end 184 | if parsed.query then url = url .. "?" .. parsed.query end 185 | local authority = parsed.authority 186 | if parsed.host then 187 | authority = parsed.host 188 | if parsed.port then authority = authority .. ":" .. parsed.port end 189 | local userinfo = parsed.userinfo 190 | if parsed.user then 191 | userinfo = parsed.user 192 | if parsed.password then 193 | userinfo = userinfo .. ":" .. parsed.password 194 | end 195 | end 196 | if userinfo then authority = userinfo .. "@" .. authority end 197 | end 198 | if authority then url = "//" .. authority .. url end 199 | if parsed.scheme then url = parsed.scheme .. ":" .. url end 200 | if parsed.fragment then url = url .. "#" .. parsed.fragment end 201 | -- url = string.gsub(url, "%s", "") 202 | return url 203 | end 204 | 205 | ----------------------------------------------------------------------------- 206 | -- Builds a absolute URL from a base and a relative URL according to RFC 2396 207 | -- Input 208 | -- base_url 209 | -- relative_url 210 | -- Returns 211 | -- corresponding absolute url 212 | ----------------------------------------------------------------------------- 213 | function absolute(base_url, relative_url) 214 | if base.type(base_url) == "table" then 215 | base_parsed = base_url 216 | base_url = build(base_parsed) 217 | else 218 | base_parsed = parse(base_url) 219 | end 220 | local relative_parsed = parse(relative_url) 221 | if not base_parsed then return relative_url 222 | elseif not relative_parsed then return base_url 223 | elseif relative_parsed.scheme then return relative_url 224 | else 225 | relative_parsed.scheme = base_parsed.scheme 226 | if not relative_parsed.authority then 227 | relative_parsed.authority = base_parsed.authority 228 | if not relative_parsed.path then 229 | relative_parsed.path = base_parsed.path 230 | if not relative_parsed.params then 231 | relative_parsed.params = base_parsed.params 232 | if not relative_parsed.query then 233 | relative_parsed.query = base_parsed.query 234 | end 235 | end 236 | else 237 | relative_parsed.path = absolute_path(base_parsed.path or "", 238 | relative_parsed.path) 239 | end 240 | end 241 | return build(relative_parsed) 242 | end 243 | end 244 | 245 | ----------------------------------------------------------------------------- 246 | -- Breaks a path into its segments, unescaping the segments 247 | -- Input 248 | -- path 249 | -- Returns 250 | -- segment: a table with one entry per segment 251 | ----------------------------------------------------------------------------- 252 | function parse_path(path) 253 | local parsed = {} 254 | path = path or "" 255 | --path = string.gsub(path, "%s", "") 256 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 257 | for i = 1, table.getn(parsed) do 258 | parsed[i] = unescape(parsed[i]) 259 | end 260 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 261 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 262 | return parsed 263 | end 264 | 265 | ----------------------------------------------------------------------------- 266 | -- Builds a path component from its segments, escaping protected characters. 267 | -- Input 268 | -- parsed: path segments 269 | -- unsafe: if true, segments are not protected before path is built 270 | -- Returns 271 | -- path: corresponding path stringing 272 | ----------------------------------------------------------------------------- 273 | function build_path(parsed, unsafe) 274 | local path = "" 275 | local n = table.getn(parsed) 276 | if unsafe then 277 | for i = 1, n-1 do 278 | path = path .. parsed[i] 279 | path = path .. "/" 280 | end 281 | if n > 0 then 282 | path = path .. parsed[n] 283 | if parsed.is_directory then path = path .. "/" end 284 | end 285 | else 286 | for i = 1, n-1 do 287 | path = path .. protect_segment(parsed[i]) 288 | path = path .. "/" 289 | end 290 | if n > 0 then 291 | path = path .. protect_segment(parsed[n]) 292 | if parsed.is_directory then path = path .. "/" end 293 | end 294 | end 295 | if parsed.is_absolute then path = "/" .. path end 296 | return path 297 | end 298 | -------------------------------------------------------------------------------- /lua/string2.lua: -------------------------------------------------------------------------------- 1 | function new_string_builder() 2 | local string_table = {} 3 | local object = {} 4 | 5 | function object.insert(...) 6 | for idx, str in ipairs(arg) do 7 | table.insert(string_table, idx, str) 8 | end 9 | end 10 | 11 | function object.add(...) 12 | for idx, str in ipairs(arg) do 13 | table.insert(string_table, str) 14 | end 15 | end 16 | 17 | function object.get(spliter) 18 | spliter = spliter or "" 19 | return table.concat(string_table, spliter) 20 | end 21 | 22 | function object.empty() 23 | return not next(string_table) 24 | end 25 | 26 | function object.len() 27 | local len = 0 28 | for index, str in ipairs(string_table) do 29 | len = len + #str 30 | end 31 | return len 32 | end 33 | 34 | return object 35 | end 36 | -------------------------------------------------------------------------------- /lua/torred.lua: -------------------------------------------------------------------------------- 1 | 2 | function is_dictionary(data) 3 | return data:sub(1,1) == "d" and data:sub(-1) == "e" 4 | end 5 | 6 | local numbers={["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9} 7 | 8 | function from_bencode(data, no_info) 9 | local table_info 10 | if not no_info then 11 | table_info = {} 12 | end 13 | 14 | local function parse(index) 15 | if index > #data then return end 16 | local t = data:byte(index) 17 | 18 | if (t == 105) or (t >=48 and t <=57) then -- (i)nteger or string 19 | local start, endindex, number = string.find(data, "i?(%-?[0-9]+)[e:]", index) 20 | 21 | if start ~= index then return end 22 | 23 | number = tonumber(number) 24 | 25 | if not number then return end 26 | 27 | if t == 105 then -- is (i)nteger? 28 | return number, endindex 29 | end 30 | 31 | if ( number < 0 ) or (math.floor(number) ~= number) or ( endindex + number > #data ) then return end 32 | local value = data:sub(endindex + 1, endindex + number) 33 | return value, endindex + number 34 | 35 | elseif (t == 108) then -- (l)ist 36 | local value, endindex = parse(index + 1) 37 | local list={} 38 | -- 39 | while value do 40 | table.insert(list,value) 41 | value, endindex = parse(endindex + 1) 42 | end 43 | 44 | if not endindex then return end 45 | 46 | return list, endindex 47 | elseif (t == 100) then -- (d)ictionary 48 | local dict={} 49 | local dict_info 50 | if table_info then 51 | dict_info = { o = {} } 52 | table_info[dict] = dict_info 53 | end 54 | local key, endindex = parse(index + 1) 55 | local value 56 | while key do 57 | value, endindex = parse(endindex + 1) 58 | if value then 59 | if dict_info then 60 | table.insert(dict_info.o, key) 61 | end 62 | dict[key]=value 63 | else 64 | break 65 | end 66 | if not endindex then return end 67 | key, endindex = parse(endindex + 1) 68 | if not endindex then return end 69 | end 70 | return dict, endindex 71 | elseif t == 101 then 72 | return nil, index 73 | end 74 | end 75 | return parse(1), table_info 76 | end 77 | 78 | function to_bencode(value, table_info, buff) 79 | --print(type(value)) 80 | if not buff then 81 | local buff = {} 82 | to_bencode(value, table_info, buff) 83 | return table.concat(buff) 84 | elseif type(value) == "number" then 85 | table.insert(buff, "i"..value.."e") 86 | elseif type(value) == "string" then 87 | table.insert(buff, #value..":"..value) 88 | elseif type(value) == "table" then 89 | 90 | 91 | if value[1] then 92 | table.insert(buff, "l") 93 | for _, v in ipairs(value) do 94 | to_bencode(v, table_info, buff) 95 | end 96 | else 97 | local dict 98 | if table_info then 99 | dict = table_info[value] 100 | end 101 | 102 | table.insert(buff, "d") 103 | if dict and dict.o then 104 | for _, k in ipairs(dict.o) do 105 | if value[k] then 106 | to_bencode(k , table_info, buff) 107 | to_bencode(value[k] , table_info, buff) 108 | end 109 | end 110 | else 111 | for k, v in pairs(value) do 112 | to_bencode(k , table_info, buff) 113 | to_bencode(v , table_info, buff) 114 | end 115 | end 116 | end 117 | 118 | table.insert(buff, "e") 119 | end 120 | end 121 | 122 | function set_value(tbl, table_info, value, key) 123 | local info = table_info[tbl] 124 | if key and value then 125 | if not info then 126 | info = {t = "d", o = {}} 127 | table_info[tbl] = info 128 | end 129 | tbl[key] = value 130 | table.insert( info.o, key ) 131 | elseif value and not (info or key) then 132 | table.insert( tbl, value ) 133 | end 134 | end -------------------------------------------------------------------------------- /lua/util.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/lua/util.lua -------------------------------------------------------------------------------- /lua5.1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/lua5.1.dll -------------------------------------------------------------------------------- /lua5.1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/lua5.1.exe -------------------------------------------------------------------------------- /magnet_to_uri_res.cmd: -------------------------------------------------------------------------------- 1 | %~d0 2 | cd %~p0 3 | del temp2.txt 4 | lua5.1.exe lua\magnet_to_uri_res.lua > temp2.txt 5 | temp2.txt 6 | pause -------------------------------------------------------------------------------- /msgs.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/msgs.txt -------------------------------------------------------------------------------- /rhash.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/rhash.exe -------------------------------------------------------------------------------- /settings.lua: -------------------------------------------------------------------------------- 1 | script_id = "LD\000\002" 2 | script_name = "Lua DHT Tracker 0.2" 3 | nodes_file = "nodes.tbl" 4 | error_log = "dht_error.log" 5 | global_token = "save" 6 | port_number = 6543 7 | svg_opacity = false 8 | packet_analyze_on = true 9 | mgs_on = true 10 | welcome_msg = [[ 11 | Welcome to Lua DHT 12 | http://sourceforge.net/apps/phpbb/shareaza/viewtopic.php?f=2&t=673 13 | 14 | add new tracker for all torrents: 15 | http://127.0.0.1:]]..port_number..[[/announce 16 | 17 | info: 18 | http://127.0.0.1:]]..port_number..[[/ 19 | ]] 20 | 21 | -------------------------------------------------------------------------------- /socket/core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan386/lua-dht/c4b547ac613940f3a30a7defe97497e13b0130d2/socket/core.dll -------------------------------------------------------------------------------- /start.cmd: -------------------------------------------------------------------------------- 1 | %~d0 2 | cd %~p0 3 | lua5.1.exe lua\dht.lua > msgs.txt 4 | pause -------------------------------------------------------------------------------- /uninstall.cmd: -------------------------------------------------------------------------------- 1 | %~d0 2 | cd %~p0 3 | LuaService -u 4 | pause --------------------------------------------------------------------------------