From e7cd4230864b15e57b95d7580a9f2ba0c9c9c285 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Thu, 19 Nov 2020 22:29:31 -0500 Subject: [PATCH] Include everything needed for jeejah MAME plugin --- README.md | 17 ++ link/mame.fnl | 13 +- support/mame/jeejah/init.lua | 25 +++ support/mame/jeejah/plugin.json | 10 + support/mame/socket.lua | 149 ++++++++++++++ support/mame/socket/core.so | Bin 0 -> 81024 bytes support/mame/socket/ftp.lua | 285 +++++++++++++++++++++++++ support/mame/socket/headers.lua | 104 ++++++++++ support/mame/socket/http.lua | 354 ++++++++++++++++++++++++++++++++ support/mame/socket/serial.so | Bin 0 -> 50776 bytes support/mame/socket/smtp.lua | 256 +++++++++++++++++++++++ support/mame/socket/tp.lua | 126 ++++++++++++ support/mame/socket/unix.so | Bin 0 -> 55512 bytes support/mame/socket/url.lua | 307 +++++++++++++++++++++++++++ 14 files changed, 1644 insertions(+), 2 deletions(-) create mode 100644 support/mame/jeejah/init.lua create mode 100644 support/mame/jeejah/plugin.json create mode 100644 support/mame/socket.lua create mode 100755 support/mame/socket/core.so create mode 100644 support/mame/socket/ftp.lua create mode 100644 support/mame/socket/headers.lua create mode 100644 support/mame/socket/http.lua create mode 100755 support/mame/socket/serial.so create mode 100644 support/mame/socket/smtp.lua create mode 100644 support/mame/socket/tp.lua create mode 100755 support/mame/socket/unix.so create mode 100644 support/mame/socket/url.lua diff --git a/README.md b/README.md index fa1b9e6..4bf9c44 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,21 @@ Tower by its developer, but it is potentially generally interesting and useful to others. Its design is focussed on the molding of the tool to your individual needs; you are encouraged to fork it and modify any part to suit your taste. +## Getting Started + +Right now, Honeylisp is developed solely on Linux and is not tested on MacOS or +Windows. You may have problems on those platforms. + +Honeylisp assumes that you have LOVE2D and MAME installed on your system. +Install them from your distribution's package manager. + +To check out the code and launch the editor, run the following commands from +a terminal: + +``` +$ git clone https://bitbucket.org/SpindleyQ/honeylisp.git +$ cd honeylisp +$ love . +``` + diff --git a/link/mame.fnl b/link/mame.fnl index 4c3e257..83ae937 100644 --- a/link/mame.fnl +++ b/link/mame.fnl @@ -5,8 +5,17 @@ (local bencode (require :lib.bencode)) (local lume (require :lib.lume)) +(fn current-pluginspath [] + (local f (io.popen "mame -showconfig" :r)) + (var pluginspath "") + (each [line (f:lines)] + (local path (line:match "pluginspath%s+(.*)")) + (when path (set pluginspath path))) + pluginspath) + +(local pluginspath (.. (current-pluginspath) ";" (love.filesystem.getWorkingDirectory) "/support/mame")) (fn start-mame [platform] - (spawn [:mame :-debug :-plugins :-pluginspath "/home/jeremy/src;/usr/share/mame/plugins" :-plugin :jeejah platform])) + (spawn [:mame :-debug :-plugins :-pluginspath pluginspath :-plugin :jeejah platform])) (local Machine (Session:extend)) (fn Machine.new [self] @@ -74,7 +83,7 @@ (when action (action))) (fn Machine.disconnect [self] (self:shutdown-session) - (self.monitor:shutdown-session) + (when self.monitor (self.monitor:shutdown-session)) (when (nrepl:connected?) (nrepl:disconnect)) (set self.breakpoints {})) diff --git a/support/mame/jeejah/init.lua b/support/mame/jeejah/init.lua new file mode 100644 index 0000000..fbb7598 --- /dev/null +++ b/support/mame/jeejah/init.lua @@ -0,0 +1,25 @@ +local modname, modfile = ... +local modpath = modfile:sub(1, (-1 - modfile:reverse():find("/"))) +print("PATH:", package.cpath) + +if package.path:find("vendor/jeejah/") == nil then + package.path = package.path .. ";" .. modpath .. "/../../../vendor/jeejah/?.lua" .. ";" .. modpath .. "/../../../vendor/?.lua" + package.cpath = package.cpath .. ";" .. modpath .. "/../?.so" -- todo: windows / mac support? + + local fennel = require "fennel" + fennel.path = './?.fnl;' .. modpath .. "/../../../vendor/jeejah/?.fnl" + table.insert(package.loaders, fennel.make_searcher({correlate=true})) +end + +local fennel = require "fennel" +local jeejah = require "jeejah.jeejah" +local exports = {} +function exports.startplugin() + -- work around table that segfaults when you index a non-existent key + getmetatable(emu.thread).__fennelview = function (self) return "<>" end + local coro = jeejah.start(7888, {fennel = true, debug = true}) + emu.register_periodic(function() coroutine.resume(coro) end) + emu.register_stop(function() coroutine.resume(coro, "stop") end) +end + +return exports diff --git a/support/mame/jeejah/plugin.json b/support/mame/jeejah/plugin.json new file mode 100644 index 0000000..28922f6 --- /dev/null +++ b/support/mame/jeejah/plugin.json @@ -0,0 +1,10 @@ +{ + "plugin": { + "name": "jeejah", + "description": "Jeejah nREPL server", + "version": "0.0.1", + "author": "Phil Hagelberg", + "type": "plugin", + "start": "false" + } +} \ No newline at end of file diff --git a/support/mame/socket.lua b/support/mame/socket.lua new file mode 100644 index 0000000..3913e6f --- /dev/null +++ b/support/mame/socket.lua @@ -0,0 +1,149 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M diff --git a/support/mame/socket/core.so b/support/mame/socket/core.so new file mode 100755 index 0000000000000000000000000000000000000000..fa1aeae97b33747b0d3eb068091e2e63551f91a0 GIT binary patch literal 81024 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

    TCyTcm3UDUA&}#YtzZPWEU`|{E>ph*nD1&8}b!^ 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 literal 0 HcmV?d00001 diff --git a/support/mame/socket/ftp.lua b/support/mame/socket/ftp.lua new file mode 100644 index 0000000..ea1145b --- /dev/null +++ b/support/mame/socket/ftp.lua @@ -0,0 +1,285 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +_M.PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or _M.PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + ip = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.ip, self.pasvt.port +end + +function metat.__index:port(ip, port) + self.pasvt = nil + if not ip then + ip, port = self.try(self.tp:getcontrol():getsockname()) + self.server = self.try(socket.bind(ip, 0)) + ip, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = {self.tp.c} + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:pasv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function parse(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +local function sput(u, body) + local putt = parse(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:pasv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = parse(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M \ No newline at end of file diff --git a/support/mame/socket/headers.lua b/support/mame/socket/headers.lua new file mode 100644 index 0000000..1eb8223 --- /dev/null +++ b/support/mame/socket/headers.lua @@ -0,0 +1,104 @@ +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M \ No newline at end of file diff --git a/support/mame/socket/http.lua b/support/mame/socket/http.lua new file mode 100644 index 0000000..ac4b2d6 --- /dev/null +++ b/support/mame/socket/http.lua @@ -0,0 +1,354 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +TIMEOUT = 60 +-- default port for document retrieval +_M.PORT = 80 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try((create or socket.tcp)()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port or _M.PORT)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = reqt.host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + host = "", + port = _M.PORT, + path ="/", + scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = 80 end + socket.try(nreqt.host and nreqt.host ~= "", + "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + return headers.location and + string.gsub(headers.location, "%s", "") ~= "" and + (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +local function srequest(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t) + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + local code, headers, status = socket.skip(1, trequest(reqt)) + return table.concat(t), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +return _M \ No newline at end of file diff --git a/support/mame/socket/serial.so b/support/mame/socket/serial.so new file mode 100755 index 0000000000000000000000000000000000000000..f9ac42d5cf1dc88841d2900961d8a111d27bc929 GIT binary patch literal 50776 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 HSmS>I%3eZc literal 0 HcmV?d00001 diff --git a/support/mame/socket/url.lua b/support/mame/socket/url.lua new file mode 100644 index 0000000..7809535 --- /dev/null +++ b/support/mame/socket/url.lua @@ -0,0 +1,307 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed withing a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02x", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + local ppath = _M.parse_path(parsed.path or "") + local url = _M.build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local relative_parsed = _M.parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + return _M.build(relative_parsed) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +return _M