From ccae0382bb750c1deded19e05b34933a8303465e Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 14 Feb 2016 10:58:18 +0100 Subject: Added plugins and boot.lua as startup script [Miodrag Milanovic] --- .gitignore | 1 + plugins/boot.lua | 26 ++ plugins/coro-channel/LICENSE | 22 ++ plugins/coro-channel/README.md | 2 + plugins/coro-channel/init.lua | 128 +++++++ plugins/coro-fs/LICENSE | 22 ++ plugins/coro-fs/README.md | 2 + plugins/coro-fs/init.lua | 222 ++++++++++++ plugins/coro-http/LICENSE | 22 ++ plugins/coro-http/README.md | 2 + plugins/coro-http/init.lua | 187 ++++++++++ plugins/coro-net/LICENSE | 22 ++ plugins/coro-net/README.md | 2 + plugins/coro-net/init.lua | 113 ++++++ plugins/coro-tls/LICENSE | 22 ++ plugins/coro-tls/README.md | 2 + plugins/coro-tls/init.lua | 122 +++++++ plugins/coro-wrapper/LICENSE | 22 ++ plugins/coro-wrapper/README.md | 2 + plugins/coro-wrapper/init.lua | 41 +++ plugins/http-codec/LICENSE | 202 +++++++++++ plugins/http-codec/README.md | 2 + plugins/http-codec/init.lua | 291 +++++++++++++++ plugins/json/LICENSE | 22 ++ plugins/json/README.md | 2 + plugins/json/init.lua | 732 ++++++++++++++++++++++++++++++++++++++ plugins/mime/LICENSE | 22 ++ plugins/mime/README.md | 2 + plugins/mime/init.lua | 194 ++++++++++ plugins/path/LICENSE | 202 +++++++++++ plugins/path/README.md | 2 + plugins/path/init.lua | 139 ++++++++ plugins/pretty-print/LICENSE | 202 +++++++++++ plugins/pretty-print/README.md | 2 + plugins/pretty-print/init.lua | 362 +++++++++++++++++++ plugins/querystring/LICENSE | 202 +++++++++++ plugins/querystring/README.md | 2 + plugins/querystring/init.lua | 105 ++++++ plugins/weblit/LICENSE | 22 ++ plugins/weblit/README.md | 239 +++++++++++++ plugins/weblit/app.lua | 261 ++++++++++++++ plugins/weblit/auto-headers.lua | 92 +++++ plugins/weblit/etag-cache.lua | 39 ++ plugins/weblit/init.lua | 8 + plugins/weblit/logger.lua | 10 + plugins/weblit/static.lua | 62 ++++ plugins/weblit/websocket.lua | 82 +++++ plugins/websocket-codec/LICENSE | 22 ++ plugins/websocket-codec/README.md | 2 + plugins/websocket-codec/init.lua | 261 ++++++++++++++ src/emu/debug/debugcpu.cpp | 3 + src/emu/emuopts.cpp | 1 + src/emu/emuopts.h | 2 + src/emu/machine.cpp | 4 +- src/emu/mame.cpp | 9 + 55 files changed, 4789 insertions(+), 1 deletion(-) create mode 100644 plugins/boot.lua create mode 100644 plugins/coro-channel/LICENSE create mode 100644 plugins/coro-channel/README.md create mode 100644 plugins/coro-channel/init.lua create mode 100644 plugins/coro-fs/LICENSE create mode 100644 plugins/coro-fs/README.md create mode 100644 plugins/coro-fs/init.lua create mode 100644 plugins/coro-http/LICENSE create mode 100644 plugins/coro-http/README.md create mode 100644 plugins/coro-http/init.lua create mode 100644 plugins/coro-net/LICENSE create mode 100644 plugins/coro-net/README.md create mode 100644 plugins/coro-net/init.lua create mode 100644 plugins/coro-tls/LICENSE create mode 100644 plugins/coro-tls/README.md create mode 100644 plugins/coro-tls/init.lua create mode 100644 plugins/coro-wrapper/LICENSE create mode 100644 plugins/coro-wrapper/README.md create mode 100644 plugins/coro-wrapper/init.lua create mode 100644 plugins/http-codec/LICENSE create mode 100644 plugins/http-codec/README.md create mode 100644 plugins/http-codec/init.lua create mode 100644 plugins/json/LICENSE create mode 100644 plugins/json/README.md create mode 100644 plugins/json/init.lua create mode 100644 plugins/mime/LICENSE create mode 100644 plugins/mime/README.md create mode 100644 plugins/mime/init.lua create mode 100644 plugins/path/LICENSE create mode 100644 plugins/path/README.md create mode 100644 plugins/path/init.lua create mode 100644 plugins/pretty-print/LICENSE create mode 100644 plugins/pretty-print/README.md create mode 100644 plugins/pretty-print/init.lua create mode 100644 plugins/querystring/LICENSE create mode 100644 plugins/querystring/README.md create mode 100644 plugins/querystring/init.lua create mode 100644 plugins/weblit/LICENSE create mode 100644 plugins/weblit/README.md create mode 100644 plugins/weblit/app.lua create mode 100644 plugins/weblit/auto-headers.lua create mode 100644 plugins/weblit/etag-cache.lua create mode 100644 plugins/weblit/init.lua create mode 100644 plugins/weblit/logger.lua create mode 100644 plugins/weblit/static.lua create mode 100644 plugins/weblit/websocket.lua create mode 100644 plugins/websocket-codec/LICENSE create mode 100644 plugins/websocket-codec/README.md create mode 100644 plugins/websocket-codec/init.lua diff --git a/.gitignore b/.gitignore index a3357981d32..148b004be92 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ !/hlsl/ !/keymaps/ !/nl_examples/ +!/plugins/ !/regtests/ !/samples/ !/scripts/ diff --git a/plugins/boot.lua b/plugins/boot.lua new file mode 100644 index 00000000000..e6cdde58004 --- /dev/null +++ b/plugins/boot.lua @@ -0,0 +1,26 @@ +local uv = require('luv') +local cwd = uv.cwd() +package.path = cwd .. "/plugins/?.lua;" .. cwd .. "/plugins/?/init.lua" + +require('weblit/app') + + .bind({ + host = "0.0.0.0", + port = 8080 + }) + + .use(require('weblit/logger')) + .use(require('weblit/auto-headers')) + .use(require('weblit/etag-cache')) + + .route({ + method = "GET", + path = "/", + }, function (req, res, go) + res.code = 200 + res.headers["Content-Type"] = "text/html" + res.body = "

Hello!

\n" + end) + + .start() + diff --git a/plugins/coro-channel/LICENSE b/plugins/coro-channel/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-channel/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-channel/README.md b/plugins/coro-channel/README.md new file mode 100644 index 00000000000..3f5bb397c69 --- /dev/null +++ b/plugins/coro-channel/README.md @@ -0,0 +1,2 @@ +# luv-coro-channel +A luv port of creationix/coro-channel from lit.luvit.io diff --git a/plugins/coro-channel/init.lua b/plugins/coro-channel/init.lua new file mode 100644 index 00000000000..0b715a0d470 --- /dev/null +++ b/plugins/coro-channel/init.lua @@ -0,0 +1,128 @@ +local exports = {} +exports.name = "creationix/coro-channel" +exports.version = "1.2.0" +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-channel.lua" +exports.description = "An adapter for wrapping uv streams as coro-streams and chaining filters." +exports.tags = {"coro", "adapter"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local function wrapRead(socket) + local paused = true + local queue = {} + local waiting + local onRead + + function onRead(err, chunk) + local data = err and {nil, err} or {chunk} + if waiting then + local thread = waiting + waiting = nil + assert(coroutine.resume(thread, unpack(data))) + else + queue[#queue + 1] = data + if not paused then + paused = true + assert(socket:read_stop()) + end + end + end + + return function () + if #queue > 0 then + return unpack(table.remove(queue, 1)) + end + if paused then + paused = false + assert(socket:read_start(onRead)) + end + waiting = coroutine.running() + return coroutine.yield() + end + +end + +local function wrapWrite(socket) + + local function wait() + local thread = coroutine.running() + return function (err) + assert(coroutine.resume(thread, err)) + end + end + + local function shutdown() + socket:shutdown(wait()) + coroutine.yield() + if not socket:is_closing() then + socket:close() + end + end + + return function (chunk) + if chunk == nil then + return shutdown() + end + assert(socket:write(chunk, wait())) + local err = coroutine.yield() + return not err, err + end + +end + +exports.wrapRead = wrapRead +exports.wrapWrite = wrapWrite + +-- Given a raw uv_stream_t userdata, return coro-friendly read/write functions. +function exports.wrapStream(socket) + return wrapRead(socket), wrapWrite(socket) +end + + +function exports.chain(...) + local args = {...} + local nargs = select("#", ...) + return function (read, write) + local threads = {} -- coroutine thread for each item + local waiting = {} -- flag when waiting to pull from upstream + local boxes = {} -- storage when waiting to write to downstream + for i = 1, nargs do + threads[i] = coroutine.create(args[i]) + waiting[i] = false + local r, w + if i == 1 then + r = read + else + function r() + local j = i - 1 + if boxes[j] then + local data = boxes[j] + boxes[j] = nil + assert(coroutine.resume(threads[j])) + return unpack(data) + else + waiting[i] = true + return coroutine.yield() + end + end + end + if i == nargs then + w = write + else + function w(...) + local j = i + 1 + if waiting[j] then + waiting[j] = false + assert(coroutine.resume(threads[j], ...)) + else + boxes[i] = {...} + coroutine.yield() + end + end + end + assert(coroutine.resume(threads[i], r, w)) + end + end +end + +return exports diff --git a/plugins/coro-fs/LICENSE b/plugins/coro-fs/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-fs/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-fs/README.md b/plugins/coro-fs/README.md new file mode 100644 index 00000000000..79ef57330a0 --- /dev/null +++ b/plugins/coro-fs/README.md @@ -0,0 +1,2 @@ +# luv-coro-fs +A luv port of lit's coro-fs module diff --git a/plugins/coro-fs/init.lua b/plugins/coro-fs/init.lua new file mode 100644 index 00000000000..f4be5bff832 --- /dev/null +++ b/plugins/coro-fs/init.lua @@ -0,0 +1,222 @@ +local exports = {} +exports.name = "creationix/coro-fs" +exports.version = "1.3.0" +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-fs.lua" +exports.description = "A coro style interface to the filesystem." +exports.tags = {"coro", "fs"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local uv = require('luv') +local fs = exports +local pathJoin = require('path').join + +local function noop() end + +local function makeCallback() + local thread = coroutine.running() + return function (err, value, ...) + if err then + assert(coroutine.resume(thread, nil, err)) + else + assert(coroutine.resume(thread, value == nil and true or value, ...)) + end + end +end + +function fs.mkdir(path, mode) + uv.fs_mkdir(path, mode or 511, makeCallback()) + return coroutine.yield() +end +function fs.open(path, flags, mode) + uv.fs_open(path, flags or "r", mode or 438, makeCallback()) + return coroutine.yield() +end +function fs.unlink(path) + uv.fs_unlink(path, makeCallback()) + return coroutine.yield() +end +function fs.stat(path) + uv.fs_stat(path, makeCallback()) + return coroutine.yield() +end +function fs.lstat(path) + uv.fs_lstat(path, makeCallback()) + return coroutine.yield() +end +function fs.symlink(target, path) + uv.fs_symlink(target, path, makeCallback()) + return coroutine.yield() +end +function fs.readlink(path) + uv.fs_readlink(path, makeCallback()) + return coroutine.yield() +end +function fs.fstat(fd) + uv.fs_fstat(fd, makeCallback()) + return coroutine.yield() +end +function fs.chmod(fd, path) + uv.fs_chmod(fd, path, makeCallback()) + return coroutine.yield() +end +function fs.fchmod(fd, mode) + uv.fs_fchmod(fd, mode, makeCallback()) + return coroutine.yield() +end +function fs.read(fd, length, offset) + uv.fs_read(fd, length or 1024*48, offset or -1, makeCallback()) + return coroutine.yield() +end +function fs.write(fd, data, offset) + uv.fs_write(fd, data, offset or -1, makeCallback()) + return coroutine.yield() +end +function fs.close(fd) + uv.fs_close(fd, makeCallback()) + return coroutine.yield() +end +function fs.access(path, flags) + uv.fs_access(path, flags or "", makeCallback()) + return coroutine.yield() +end +function fs.rename(path, newPath) + uv.fs_rename(path, newPath, makeCallback()) + return coroutine.yield() +end +function fs.rmdir(path) + uv.fs_rmdir(path, makeCallback()) + return coroutine.yield() +end +function fs.rmrf(path) + local success, err + success, err = fs.rmdir(path) + if success then return success end + if err:match("^ENOTDIR:") then return fs.unlink(path) end + if not err:match("^ENOTEMPTY:") then return success, err end + for entry in assert(fs.scandir(path)) do + local subPath = pathJoin(path, entry.name) + if entry.type == "directory" then + success, err = fs.rmrf(pathJoin(path, entry.name)) + else + success, err = fs.unlink(subPath) + end + if not success then return success, err end + end + return fs.rmdir(path) +end +function fs.scandir(path) + uv.fs_scandir(path, makeCallback()) + local req, err = coroutine.yield() + if not req then return nil, err end + return function () + return uv.fs_scandir_next(req) + end +end + +function fs.readFile(path) + local fd, stat, data, err + fd, err = fs.open(path) + if err then return nil, err end + stat, err = fs.fstat(fd) + if stat then + data, err = fs.read(fd, stat.size) + end + uv.fs_close(fd, noop) + return data, err +end + +function fs.writeFile(path, data, mkdir) + local fd, success, err + fd, err = fs.open(path, "w") + if err then + if mkdir and string.match(err, "^ENOENT:") then + success, err = fs.mkdirp(pathJoin(path, "..")) + if success then return fs.writeFile(path, data) end + end + return nil, err + end + success, err = fs.write(fd, data) + uv.fs_close(fd, noop) + return success, err +end + +function fs.mkdirp(path, mode) + local success, err = fs.mkdir(path, mode) + if success or string.match(err, "^EEXIST") then + return true + end + if string.match(err, "^ENOENT:") then + success, err = fs.mkdirp(pathJoin(path, ".."), mode) + if not success then return nil, err end + return fs.mkdir(path, mode) + end + return nil, err +end + +function fs.chroot(base) + local chroot = { + base = base, + fstat = fs.fstat, + fchmod = fs.fchmod, + read = fs.read, + write = fs.write, + close = fs.close, + } + local function resolve(path) + assert(path, "path missing") + return pathJoin(base, pathJoin(path)) + end + function chroot.mkdir(path, mode) + return fs.mkdir(resolve(path), mode) + end + function chroot.mkdirp(path, mode) + return fs.mkdirp(resolve(path), mode) + end + function chroot.open(path, flags, mode) + return fs.open(resolve(path), flags, mode) + end + function chroot.unlink(path) + return fs.unlink(resolve(path)) + end + function chroot.stat(path) + return fs.stat(resolve(path)) + end + function chroot.lstat(path) + return fs.lstat(resolve(path)) + end + function chroot.symlink(target, path) + -- TODO: should we resolve absolute target paths or treat it as opaque data? + return fs.symlink(target, resolve(path)) + end + function chroot.readlink(path) + return fs.readlink(resolve(path)) + end + function chroot.chmod(path, mode) + return fs.chmod(resolve(path), mode) + end + function chroot.access(path, flags) + return fs.access(resolve(path), flags) + end + function chroot.rename(path, newPath) + return fs.rename(resolve(path), resolve(newPath)) + end + function chroot.rmdir(path) + return fs.rmdir(resolve(path)) + end + function chroot.rmrf(path) + return fs.rmrf(resolve(path)) + end + function chroot.scandir(path, iter) + return fs.scandir(resolve(path), iter) + end + function chroot.readFile(path) + return fs.readFile(resolve(path)) + end + function chroot.writeFile(path, data, mkdir) + return fs.writeFile(resolve(path), data, mkdir) + end + return chroot +end + +return exports diff --git a/plugins/coro-http/LICENSE b/plugins/coro-http/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-http/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-http/README.md b/plugins/coro-http/README.md new file mode 100644 index 00000000000..271b6698f5d --- /dev/null +++ b/plugins/coro-http/README.md @@ -0,0 +1,2 @@ +# luv-coro-http +A luv port of lit's coro-http library. diff --git a/plugins/coro-http/init.lua b/plugins/coro-http/init.lua new file mode 100644 index 00000000000..49613403eaf --- /dev/null +++ b/plugins/coro-http/init.lua @@ -0,0 +1,187 @@ +local exports = {} +exports.name = "creationix/coro-http" +exports.version = "1.2.1-1" +exports.dependencies = { + "creationix/coro-net@1.1.1", + "creationix/coro-tls@1.2.1", + "creationix/coro-wrapper@1.0.0", + "luvit/http-codec@1.0.0" +} +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-http.lua" +exports.description = "An coro style http(s) client and server helper." +exports.tags = {"coro", "http"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local httpCodec = require('http-codec') +local net = require('coro-net') +local connect = net.connect +local createServer = net.createServer +--local tlsWrap = require('coro-tls').wrap +local wrapper = require('coro-wrapper') + +function exports.createServer(options, onConnect) + createServer(options, function (rawRead, rawWrite, socket) + local read = wrapper.reader(rawRead, httpCodec.decoder()) + local write = wrapper.writer(rawWrite, httpCodec.encoder()) + for head in read do + local parts = {} + for part in read do + if #part > 0 then + parts[#parts + 1] = part + else + break + end + end + local body = table.concat(parts) + head, body = onConnect(head, body, socket) + write(head) + if body then write(body) end + write("") + if not head.keepAlive then break end + end + end) +end + +local function parseUrl(url) + local protocol, host, hostname, port, path = url:match("^(https?:)//(([^/:]+):?([0-9]*))(/?.*)$") + if not protocol then error("Not a valid http url: " .. url) end + local tls = protocol == "https:" + port = port and tonumber(port) or (tls and 443 or 80) + if path == "" then path = "/" end + return { + tls = tls, + host = host, + hostname = hostname, + port = port, + path = path + } +end +exports.parseUrl = parseUrl + +local connections = {} + +local function getConnection(host, port, tls) + for i = #connections, 1, -1 do + local connection = connections[i] + if connection.host == host and connection.port == port and connection.tls == tls then + table.remove(connections, i) + -- Make sure the connection is still alive before reusing it. + if not connection.socket:is_closing() then + connection.reused = true + connection.socket:ref() + return connection + end + end + end + local read, write, socket = assert(connect({host=host,port=port})) + --if tls then + --read, write = tlsWrap(read, write) + --end + local httpRead, updateRead = wrapper.reader(read, httpCodec.decoder()) + return { + socket = socket, + host = host, + port = port, + tls = tls, + read = httpRead, + write = wrapper.writer(write, httpCodec.encoder()), + reset = function () + -- This is called after parsing the response head from a HEAD request. + -- If you forget, the codec might hang waiting for a body that doesn't exist. + updateRead(httpCodec.decoder()) + end + } +end +exports.getConnection = getConnection + +local function saveConnection(connection) + if connection.socket:is_closing() then return end + connections[#connections + 1] = connection + connection.socket:unref() +end +exports.saveConnection = saveConnection + +function exports.request(method, url, headers, body) + local uri = parseUrl(url) + local connection = getConnection(uri.hostname, uri.port, uri.tls) + local read = connection.read + local write = connection.write + + local req = { + method = method, + path = uri.path, + {"Host", uri.host} + } + local contentLength + local chunked + if headers then + for i = 1, #headers do + local key, value = unpack(headers[i]) + key = key:lower() + if key == "content-length" then + contentLength = value + elseif key == "content-encoding" and value:lower() == "chunked" then + chunked = true + end + req[#req + 1] = headers[i] + end + end + + if type(body) == "string" then + if not chunked and not contentLength then + req[#req + 1] = {"Content-Length", #body} + end + end + + write(req) + if body then write(body) end + local res = read() + if not res then + write() + -- If we get an immediate close on a reused socket, try again with a new socket. + -- TODO: think about if this could resend requests with side effects and cause + -- them to double execute in the remote server. + if connection.reused then + return exports.request(method, url, headers, body) + end + error("Connection closed") + end + + body = {} + if req.method == "HEAD" then + connection.reset() + else + while true do + local item = read() + if not item then + res.keepAlive = false + break + end + if #item == 0 then + break + end + body[#body + 1] = item + end + end + + if res.keepAlive then + saveConnection(connection) + else + write() + end + + -- Follow redirects + if method == "GET" and (res.code == 302 or res.code == 307) then + for i = 1, #res do + local key, location = unpack(res[i]) + if key:lower() == "location" then + return exports.request(method, location, headers) + end + end + end + + return res, table.concat(body) +end + +return exports diff --git a/plugins/coro-net/LICENSE b/plugins/coro-net/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-net/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-net/README.md b/plugins/coro-net/README.md new file mode 100644 index 00000000000..c8a94b9980a --- /dev/null +++ b/plugins/coro-net/README.md @@ -0,0 +1,2 @@ +# luv-coro-net +A luv port of creationix/coro-net from lit.luvit.io diff --git a/plugins/coro-net/init.lua b/plugins/coro-net/init.lua new file mode 100644 index 00000000000..3ae6c2ad824 --- /dev/null +++ b/plugins/coro-net/init.lua @@ -0,0 +1,113 @@ +local exports = {} +exports.name = "creationix/coro-net" +exports.version = "1.1.1-1" +exports.dependencies = { + "creationix/coro-channel@1.2.0" +} +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-net.lua" +exports.description = "An coro style client and server helper for tcp and pipes." +exports.tags = {"coro", "tcp", "pipe", "net"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local uv = require('luv') +local wrapStream = require('coro-channel').wrapStream + +local function makeCallback(timeout) + local thread = coroutine.running() + local timer, done + if timeout then + timer = uv.new_timer() + timer:start(timeout, 0, function () + if done then return end + done = true + timer:close() + return assert(coroutine.resume(thread, nil, "timeout")) + end) + end + return function (err, data) + if done then return end + done = true + if timer then timer:close() end + if err then + return assert(coroutine.resume(thread, nil, err)) + end + return assert(coroutine.resume(thread, data or true)) + end +end +exports.makeCallback = makeCallback + +local function normalize(options) + local t = type(options) + if t == "string" then + options = {path=options} + elseif t == "number" then + options = {port=options} + elseif t ~= "table" then + assert("Net options must be table, string, or number") + end + if options.port or options.host then + return true, + options.host or "127.0.0.1", + assert(options.port, "options.port is required for tcp connections") + elseif options.path then + return false, options.path + else + error("Must set either options.path or options.port") + end +end + +function exports.connect(options) + local socket, success, err + local isTcp, host, port = normalize(options) + if isTcp then + assert(uv.getaddrinfo(host, port, { + socktype = options.socktype or "stream", + family = options.family or "inet", + }, makeCallback(options.timeout))) + local res + res, err = coroutine.yield() + if not res then return nil, err end + socket = uv.new_tcp() + socket:connect(res[1].addr, res[1].port, makeCallback(options.timeout)) + else + socket = uv.new_pipe(false) + socket:connect(host, makeCallback(options.timeout)) + end + success, err = coroutine.yield() + if not success then return nil, err end + local read, write = wrapStream(socket) + return read, write, socket +end + +function exports.createServer(options, onConnect) + local server + local isTcp, host, port = normalize(options) + if isTcp then + server = uv.new_tcp() + assert(server:bind(host, port)) + else + server = uv.new_pipe(false) + assert(server:bind(host)) + end + assert(server:listen(256, function (err) + assert(not err, err) + local socket = isTcp and uv.new_tcp() or uv.new_pipe(false) + server:accept(socket) + coroutine.wrap(function () + local success, failure = xpcall(function () + local read, write = wrapStream(socket) + return onConnect(read, write, socket) + end, debug.traceback) + if not success then + print(failure) + end + if not socket:is_closing() then + socket:close() + end + end)() + end)) + return server +end + +return exports diff --git a/plugins/coro-tls/LICENSE b/plugins/coro-tls/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-tls/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-tls/README.md b/plugins/coro-tls/README.md new file mode 100644 index 00000000000..50aae3f7657 --- /dev/null +++ b/plugins/coro-tls/README.md @@ -0,0 +1,2 @@ +# luv-coro-tls +A luv port of lit's coro-tls module diff --git a/plugins/coro-tls/init.lua b/plugins/coro-tls/init.lua new file mode 100644 index 00000000000..03d6e1c3f76 --- /dev/null +++ b/plugins/coro-tls/init.lua @@ -0,0 +1,122 @@ +local exports = {} +exports.name = "creationix/coro-tls" +exports.version = "1.2.1" +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-tls.lua" +exports.description = "A coro-stream wrapper implementing tls sessions." +exports.tags = {"coro", "tls", "ssl"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local openssl = require('openssl') +local bit = require('bit') + +local DEFAULT_CIPHERS = 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' .. -- TLS 1.2 + 'RC4:HIGH:!MD5:!aNULL:!EDH' -- TLS 1.0 + +-- Given a read/write pair, return a new read/write pair for plaintext +exports.wrap = function (read, write, options) + if not options then + options = {} + end + + local ctx = openssl.ssl.ctx_new(options.protocol or 'TLSv1_2', options.ciphers or DEFAULT_CIPHERS) + + local key, cert, ca + if options.key then + key = assert(openssl.pkey.read(options.key, true, 'pem')) + end + if options.cert then + cert = assert(openssl.x509.read(options.cert)) + end + if options.ca then + if type(options.ca) == "string" then + ca = { assert(openssl.x509.read(options.ca)) } + elseif type(options.ca) == "table" then + ca = {} + for i = 1, #options.ca do + ca[i] = assert(openssl.x509.read(options.ca[i])) + end + else + error("options.ca must be string or table of strings") + end + end + if key and cert then + assert(ctx:use(key, cert)) + end + if ca then + local store = openssl.x509.store:new() + for i = 1, #ca do + assert(store:add(ca[i])) + end + ctx:cert_store(store) + else + ctx:verify_mode({"none"}) + end + + ctx:options(bit.bor( + openssl.ssl.no_sslv2, + openssl.ssl.no_sslv3, + openssl.ssl.no_compression)) + local bin, bout = openssl.bio.mem(8192), openssl.bio.mem(8192) + local ssl = ctx:ssl(bin, bout, options.server) + + local function flush() + while bout:pending() > 0 do + write(bout:read()) + end + end + + -- Do handshake + while true do + if ssl:handshake() then break end + flush() + local chunk = read() + if chunk then + bin:write(chunk) + else + error("disconnect while handshaking") + end + end + flush() + + local done = false + local function shutdown() + if done then return end + done = true + while true do + if ssl:shutdown() then break end + flush() + local chunk = read() + if chunk then + bin:write(chunk) + else + break + end + end + flush() + write() + end + + local function plainRead() + while true do + local chunk = ssl:read() + if chunk then return chunk end + local cipher = read() + if not cipher then return end + bin:write(cipher) + end + end + + local function plainWrite(plain) + if not plain then + return shutdown() + end + ssl:write(plain) + flush() + end + + return plainRead, plainWrite + +end + +return exports diff --git a/plugins/coro-wrapper/LICENSE b/plugins/coro-wrapper/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/coro-wrapper/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/coro-wrapper/README.md b/plugins/coro-wrapper/README.md new file mode 100644 index 00000000000..a9351a7f531 --- /dev/null +++ b/plugins/coro-wrapper/README.md @@ -0,0 +1,2 @@ +# luv-coro-wrapper +A luv port of lit's coro-wrapper module diff --git a/plugins/coro-wrapper/init.lua b/plugins/coro-wrapper/init.lua new file mode 100644 index 00000000000..aab2683baf3 --- /dev/null +++ b/plugins/coro-wrapper/init.lua @@ -0,0 +1,41 @@ +local exports = {} +exports.name = "creationix/coro-wrapper" +exports.version = "1.0.0-1" +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua" +exports.description = "An adapter for applying decoders to coro-streams." +exports.tags = {"coro", "decoder", "adapter"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +function exports.reader(read, decode) + local buffer = "" + return function () + while true do + local item, extra = decode(buffer) + if item then + buffer = extra + return item + end + local chunk = read() + if not chunk then return end + buffer = buffer .. chunk + end + end, + function (newDecode) + decode = newDecode + end +end + +function exports.writer(write, encode) + return function (item) + if not item then + return write() + end + return write(encode(item)) + end, + function (newEncode) + encode = newEncode + end +end + +return exports diff --git a/plugins/http-codec/LICENSE b/plugins/http-codec/LICENSE new file mode 100644 index 00000000000..8f71f43fee3 --- /dev/null +++ b/plugins/http-codec/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/http-codec/README.md b/plugins/http-codec/README.md new file mode 100644 index 00000000000..80d050416ae --- /dev/null +++ b/plugins/http-codec/README.md @@ -0,0 +1,2 @@ +# luv-http-codec +A luv port of luvit's http-codec diff --git a/plugins/http-codec/init.lua b/plugins/http-codec/init.lua new file mode 100644 index 00000000000..11a32945729 --- /dev/null +++ b/plugins/http-codec/init.lua @@ -0,0 +1,291 @@ +--[[ + +Copyright 2014-2015 The Luvit Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--]] + +local exports = {} + +exports.name = "luvit/http-codec" +exports.version = "1.0.0-1" +exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/http-codec.lua" +exports.description = "A simple pair of functions for converting between hex and raw strings." +exports.tags = {"codec", "http"} +exports.license = "Apache 2" +exports.author = { name = "Tim Caswell" } + +local sub = string.sub +local gsub = string.gsub +local lower = string.lower +local find = string.find +local format = string.format +local concat = table.concat +local match = string.match + +local STATUS_CODES = { + [100] = 'Continue', + [101] = 'Switching Protocols', + [102] = 'Processing', -- RFC 2518, obsoleted by RFC 4918 + [200] = 'OK', + [201] = 'Created', + [202] = 'Accepted', + [203] = 'Non-Authoritative Information', + [204] = 'No Content', + [205] = 'Reset Content', + [206] = 'Partial Content', + [207] = 'Multi-Status', -- RFC 4918 + [300] = 'Multiple Choices', + [301] = 'Moved Permanently', + [302] = 'Moved Temporarily', + [303] = 'See Other', + [304] = 'Not Modified', + [305] = 'Use Proxy', + [307] = 'Temporary Redirect', + [400] = 'Bad Request', + [401] = 'Unauthorized', + [402] = 'Payment Required', + [403] = 'Forbidden', + [404] = 'Not Found', + [405] = 'Method Not Allowed', + [406] = 'Not Acceptable', + [407] = 'Proxy Authentication Required', + [408] = 'Request Time-out', + [409] = 'Conflict', + [410] = 'Gone', + [411] = 'Length Required', + [412] = 'Precondition Failed', + [413] = 'Request Entity Too Large', + [414] = 'Request-URI Too Large', + [415] = 'Unsupported Media Type', + [416] = 'Requested Range Not Satisfiable', + [417] = 'Expectation Failed', + [418] = "I'm a teapot", -- RFC 2324 + [422] = 'Unprocessable Entity', -- RFC 4918 + [423] = 'Locked', -- RFC 4918 + [424] = 'Failed Dependency', -- RFC 4918 + [425] = 'Unordered Collection', -- RFC 4918 + [426] = 'Upgrade Required', -- RFC 2817 + [500] = 'Internal Server Error', + [501] = 'Not Implemented', + [502] = 'Bad Gateway', + [503] = 'Service Unavailable', + [504] = 'Gateway Time-out', + [505] = 'HTTP Version not supported', + [506] = 'Variant Also Negotiates', -- RFC 2295 + [507] = 'Insufficient Storage', -- RFC 4918 + [509] = 'Bandwidth Limit Exceeded', + [510] = 'Not Extended' -- RFC 2774 +} + +exports.encoder = function () + + local mode + local encodeHead, encodeRaw, encodeChunked + + function encodeHead(item) + if not item or item == "" then + return item + elseif not (type(item) == "table") then + error("expected a table but got a " .. type(item) .. " when encoding data") + end + local head, chunkedEncoding + local version = item.version or 1.1 + if item.method then + local path = item.path + assert(path and #path > 0, "expected non-empty path") + head = { item.method .. ' ' .. item.path .. ' HTTP/' .. version .. '\r\n' } + else + local reason = item.reason or STATUS_CODES[item.code] + head = { 'HTTP/' .. version .. ' ' .. item.code .. ' ' .. reason .. '\r\n' } + end + for i = 1, #item do + local key, value = unpack(item[i]) + local lowerKey = lower(key) + if lowerKey == "transfer-encoding" then + chunkedEncoding = lower(value) == "chunked" + end + value = gsub(tostring(value), "[\r\n]+", " ") + head[#head + 1] = key .. ': ' .. tostring(value) .. '\r\n' + end + head[#head + 1] = '\r\n' + + mode = chunkedEncoding and encodeChunked or encodeRaw + return concat(head) + end + + function encodeRaw(item) + if type(item) ~= "string" then + mode = encodeHead + return encodeHead(item) + end + return item + end + + function encodeChunked(item) + if type(item) ~= "string" then + mode = encodeHead + local extra = encodeHead(item) + if extra then + return "0\r\n\r\n" .. extra + else + return "0\r\n\r\n" + end + end + if #item == 0 then + mode = encodeHead + end + return format("%x", #item) .. "\r\n" .. item .. "\r\n" + end + + mode = encodeHead + return function (item) + return mode(item) + end +end + +exports.decoder = function () + + -- This decoder is somewhat stateful with 5 different parsing states. + local decodeHead, decodeEmpty, decodeRaw, decodeChunked, decodeCounted + local mode -- state variable that points to various decoders + local bytesLeft -- For counted decoder + + -- This state is for decoding the status line and headers. + function decodeHead(chunk) + if not chunk then return end + + local _, length = find(chunk, "\r?\n\r?\n", 1) + -- First make sure we have all the head before continuing + if not length then + if #chunk < 8 * 1024 then return end + -- But protect against evil clients by refusing heads over 8K long. + error("entity too large") + end + + -- Parse the status/request line + local head = {} + local _, offset + local version + _, offset, version, head.code, head.reason = + find(chunk, "^HTTP/(%d%.%d) (%d+) ([^\r\n]+)\r?\n") + if offset then + head.code = tonumber(head.code) + else + _, offset, head.method, head.path, version = + find(chunk, "^(%u+) ([^ ]+) HTTP/(%d%.%d)\r?\n") + if not offset then + error("expected HTTP data") + end + end + version = tonumber(version) + head.version = version + head.keepAlive = version > 1.0 + + -- We need to inspect some headers to know how to parse the body. + local contentLength + local chunkedEncoding + + -- Parse the header lines + while true do + local key, value + _, offset, key, value = find(chunk, "^([^:\r\n]+): *([^\r\n]+)\r?\n", offset + 1) + if not offset then break end + local lowerKey = lower(key) + + -- Inspect a few headers and remember the values + if lowerKey == "content-length" then + contentLength = tonumber(value) + elseif lowerKey == "transfer-encoding" then + chunkedEncoding = lower(value) == "chunked" + elseif lowerKey == "connection" then + head.keepAlive = lower(value) == "keep-alive" + end + head[#head + 1] = {key, value} + end + + if head.keepAlive and (not (chunkedEncoding or (contentLength and contentLength > 0))) + or (head.method == "GET" or head.method == "HEAD") then + mode = decodeEmpty + elseif chunkedEncoding then + mode = decodeChunked + elseif contentLength then + bytesLeft = contentLength + mode = decodeCounted + elseif not head.keepAlive then + mode = decodeRaw + end + + return head, sub(chunk, length + 1) + + end + + -- This is used for inserting a single empty string into the output string for known empty bodies + function decodeEmpty(chunk) + mode = decodeHead + return "", chunk or "" + end + + function decodeRaw(chunk) + if not chunk then return "", "" end + if #chunk == 0 then return end + return chunk, "" + end + + function decodeChunked(chunk) + local len, term + len, term = match(chunk, "^(%x+)(..)") + if not len then return end + assert(term == "\r\n") + local length = tonumber(len, 16) + if #chunk < length + 4 + #len then return end + if length == 0 then + mode = decodeHead + end + chunk = sub(chunk, #len + 3) + assert(sub(chunk, length + 1, length + 2) == "\r\n") + return sub(chunk, 1, length), sub(chunk, length + 3) + end + + function decodeCounted(chunk) + if bytesLeft == 0 then + mode = decodeEmpty + return mode(chunk) + end + local length = #chunk + -- Make sure we have at least one byte to process + if length == 0 then return end + + if length >= bytesLeft then + mode = decodeEmpty + end + + -- If the entire chunk fits, pass it all through + if length <= bytesLeft then + bytesLeft = bytesLeft - length + return chunk, "" + end + + return sub(chunk, 1, bytesLeft), sub(chunk, bytesLeft + 1) + end + + -- Switch between states by changing which decoder mode points to + mode = decodeHead + return function (chunk) + return mode(chunk) + end + +end + +return exports diff --git a/plugins/json/LICENSE b/plugins/json/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/json/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/json/README.md b/plugins/json/README.md new file mode 100644 index 00000000000..71965e3d0f7 --- /dev/null +++ b/plugins/json/README.md @@ -0,0 +1,2 @@ +# luv-json +A luv port of luvit's json module diff --git a/plugins/json/init.lua b/plugins/json/init.lua new file mode 100644 index 00000000000..d49352cc6ff --- /dev/null +++ b/plugins/json/init.lua @@ -0,0 +1,732 @@ +local exports = {} +exports.name = "luvit/json" +exports.version = "2.5.0" +exports.homepage = "http://dkolf.de/src/dkjson-lua.fsl" +exports.description = "David Kolf's JSON library repackaged for lit." +exports.tags = {"json", "codec"} +exports.license = "MIT" +exports.author = { + name = "David Kolf", + homepage = "http://dkolf.de/", +} +exports.contributors = { + "Tim Caswell", +} + +-- Module options: +local always_try_using_lpeg = false +local register_global_module_table = false +local global_module_name = 'json' + +--[==[ + +David Kolf's JSON module for Lua 5.1/5.2 + +Version 2.5 + + +For the documentation see the corresponding readme.txt or visit +. + +You can contact the author by sending an e-mail to 'david' at the +domain 'dkolf.de'. + + +Copyright (C) 2010-2013 David Heiko Kolf + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--]==] + +-- global dependencies: +local pairs, type, tostring, tonumber, getmetatable, setmetatable = + pairs, type, tostring, tonumber, getmetatable, setmetatable +local error, require, pcall, select = error, require, pcall, select +local floor, huge = math.floor, math.huge +local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = + string.rep, string.gsub, string.sub, string.byte, string.char, + string.find, string.len, string.format +local strmatch = string.match +local concat = table.concat + +local json = exports +json.original_version = "dkjson 2.5" + +if register_global_module_table then + _G[global_module_name] = json +end + +_ENV = nil -- blocking globals in Lua 5.2 + +pcall (function() + -- Enable access to blocked metatables. + -- Don't worry, this module doesn't change anything in them. + local debmeta = require "debug".getmetatable + if debmeta then getmetatable = debmeta end +end) + +json.null = setmetatable ({}, { + __tojson = function () return "null" end +}) + +local function isarray (tbl) + local max, n, arraylen = 0, 0, 0 + for k,v in pairs (tbl) do + if k == 'n' and type(v) == 'number' then + arraylen = v + if v > max then + max = v + end + else + if type(k) ~= 'number' or k < 1 or floor(k) ~= k then + return false + end + if k > max then + max = k + end + n = n + 1 + end + end + if max > 10 and max > arraylen and max > n * 2 then + return false -- don't create an array with too many holes + end + return true, max +end + +local escapecodes = { + ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", + ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" +} + +local function escapeutf8 (uchar) + local value = escapecodes[uchar] + if value then + return value + end + local a, b, c, d = strbyte (uchar, 1, 4) + a, b, c, d = a or 0, b or 0, c or 0, d or 0 + if a <= 0x7f then + value = a + elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then + value = (a - 0xc0) * 0x40 + b - 0x80 + elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then + value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 + elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then + value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 + else + return "" + end + if value <= 0xffff then + return strformat ("\\u%.4x", value) + elseif value <= 0x10ffff then + -- encode as UTF-16 surrogate pair + value = value - 0x10000 + local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) + return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) + else + return "" + end +end + +local function fsub (str, pattern, repl) + -- gsub always builds a new string in a buffer, even when no match + -- exists. First using find should be more efficient when most strings + -- don't contain the pattern. + if strfind (str, pattern) then + return gsub (str, pattern, repl) + else + return str + end +end + +local function quotestring (value) + -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js + value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) + if strfind (value, "[\194\216\220\225\226\239]") then + value = fsub (value, "\194[\128-\159\173]", escapeutf8) + value = fsub (value, "\216[\128-\132]", escapeutf8) + value = fsub (value, "\220\143", escapeutf8) + value = fsub (value, "\225\158[\180\181]", escapeutf8) + value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) + value = fsub (value, "\226\129[\160-\175]", escapeutf8) + value = fsub (value, "\239\187\191", escapeutf8) + value = fsub (value, "\239\191[\176-\191]", escapeutf8) + end + return "\"" .. value .. "\"" +end +json.quotestring = quotestring + +local function replace(str, o, n) + local i, j = strfind (str, o, 1, true) + if i then + return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) + else + return str + end +end + +-- locale independent num2str and str2num functions +local decpoint, numfilter + +local function updatedecpoint () + decpoint = strmatch(tostring(0.5), "([^05+])") + -- build a filter that can be used to remove group separators + numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" +end + +updatedecpoint() + +local function num2str (num) + return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") +end + +local function str2num (str) + local num = tonumber(replace(str, ".", decpoint)) + if not num then + updatedecpoint() + num = tonumber(replace(str, ".", decpoint)) + end + return num +end + +local function addnewline2 (level, buffer, buflen) + buffer[buflen+1] = "\n" + buffer[buflen+2] = strrep (" ", level) + buflen = buflen + 2 + return buflen +end + +function json.addnewline (state) + if state.indent then + state.bufferlen = addnewline2 (state.level or 0, + state.buffer, state.bufferlen or #(state.buffer)) + end +end + +local encode2 -- forward declaration + +local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) + local kt = type (key) + if kt ~= 'string' and kt ~= 'number' then + return nil, "type '" .. kt .. "' is not supported as a key by JSON." + end + if prev then + buflen = buflen + 1 + buffer[buflen] = "," + end + if indent then + buflen = addnewline2 (level, buffer, buflen) + end + buffer[buflen+1] = quotestring (key) + buffer[buflen+2] = ":" + return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) +end + +local function appendcustom(res, buffer, state) + local buflen = state.bufferlen + if type (res) == 'string' then + buflen = buflen + 1 + buffer[buflen] = res + end + return buflen +end + +local function exception(reason, value, state, buffer, buflen, defaultmessage) + defaultmessage = defaultmessage or reason + local handler = state.exception + if not handler then + return nil, defaultmessage + else + state.bufferlen = buflen + local ret, msg = handler (reason, value, state, defaultmessage) + if not ret then return nil, msg or defaultmessage end + return appendcustom(ret, buffer, state) + end +end + +function json.encodeexception(reason, value, state, defaultmessage) + return quotestring("<" .. defaultmessage .. ">") +end + +encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) + local valtype = type (value) + local valmeta = getmetatable (value) + valmeta = type (valmeta) == 'table' and valmeta -- only tables + local valtojson = valmeta and valmeta.__tojson + if valtojson then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + state.bufferlen = buflen + local ret, msg = valtojson (value, state) + if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end + tables[value] = nil + buflen = appendcustom(ret, buffer, state) + elseif value == nil then + buflen = buflen + 1 + buffer[buflen] = "null" + elseif valtype == 'number' then + local s + if value ~= value or value >= huge or -value >= huge then + -- This is the behaviour of the original JSON implementation. + s = "null" + else + s = num2str (value) + end + buflen = buflen + 1 + buffer[buflen] = s + elseif valtype == 'boolean' then + buflen = buflen + 1 + buffer[buflen] = value and "true" or "false" + elseif valtype == 'string' then + buflen = buflen + 1 + buffer[buflen] = quotestring (value) + elseif valtype == 'table' then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + level = level + 1 + local isa, n = isarray (value) + if n == 0 and valmeta and valmeta.__jsontype == 'object' then + isa = false + end + local msg + if isa then -- JSON array + buflen = buflen + 1 + buffer[buflen] = "[" + for i = 1, n do + buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + if i < n then + buflen = buflen + 1 + buffer[buflen] = "," + end + end + buflen = buflen + 1 + buffer[buflen] = "]" + else -- JSON object + local prev = false + buflen = buflen + 1 + buffer[buflen] = "{" + local order = valmeta and valmeta.__jsonorder or globalorder + if order then + local used = {} + n = #order + for i = 1, n do + local k = order[i] + local v = value[k] + local _ + if v then + used[k] = true + buflen, _ = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + prev = true -- add a seperator before the next element + end + end + for k,v in pairs (value) do + if not used[k] then + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + else -- unordered + for k,v in pairs (value) do + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + if indent then + buflen = addnewline2 (level - 1, buffer, buflen) + end + buflen = buflen + 1 + buffer[buflen] = "}" + end + tables[value] = nil + else + return exception ('unsupported type', value, state, buffer, buflen, + "type '" .. valtype .. "' is not supported by JSON.") + end + return buflen +end + +function json.encode (value, state) + state = state or {} + local oldbuffer = state.buffer + local buffer = oldbuffer or {} + state.buffer = buffer + updatedecpoint() + local ret, msg = encode2 (value, state.indent, state.level or 0, + buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) + if not ret then + error (msg, 2) + elseif oldbuffer == buffer then + state.bufferlen = ret + return true + else + state.bufferlen = nil + state.buffer = nil + return concat (buffer) + end +end + +local function loc (str, where) + local line, pos, linepos = 1, 1, 0 + while true do + pos = strfind (str, "\n", pos, true) + if pos and pos < where then + line = line + 1 + linepos = pos + pos = pos + 1 + else + break + end + end + return "line " .. line .. ", column " .. (where - linepos) +end + +local function unterminated (str, what, where) + return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) +end + +local function scanwhite (str, pos) + while true do + pos = strfind (str, "%S", pos) + if not pos then return nil end + local sub2 = strsub (str, pos, pos + 1) + if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then + -- UTF-8 Byte Order Mark + pos = pos + 3 + elseif sub2 == "//" then + pos = strfind (str, "[\n\r]", pos + 2) + if not pos then return nil end + elseif sub2 == "/*" then + pos = strfind (str, "*/", pos + 2) + if not pos then return nil end + pos = pos + 2 + else + return pos + end + end +end + +local escapechars = { + ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", + ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" +} + +local function unichar (value) + if value < 0 then + return nil + elseif value <= 0x007f then + return strchar (value) + elseif value <= 0x07ff then + return strchar (0xc0 + floor(value/0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0xffff then + return strchar (0xe0 + floor(value/0x1000), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0x10ffff then + return strchar (0xf0 + floor(value/0x40000), + 0x80 + (floor(value/0x1000) % 0x40), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + else + return nil + end +end + +local function scanstring (str, pos) + local lastpos = pos + 1 + local buffer, n = {}, 0 + while true do + local nextpos = strfind (str, "[\"\\]", lastpos) + if not nextpos then + return unterminated (str, "string", pos) + end + if nextpos > lastpos then + n = n + 1 + buffer[n] = strsub (str, lastpos, nextpos - 1) + end + if strsub (str, nextpos, nextpos) == "\"" then + lastpos = nextpos + 1 + break + else + local escchar = strsub (str, nextpos + 1, nextpos + 1) + local value + if escchar == "u" then + value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) + if value then + local value2 + if 0xD800 <= value and value <= 0xDBff then + -- we have the high surrogate of UTF-16. Check if there is a + -- low surrogate escaped nearby to combine them. + if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then + value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) + if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then + value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 + else + value2 = nil -- in case it was out of range for a low surrogate + end + end + end + value = value and unichar (value) + if value then + if value2 then + lastpos = nextpos + 12 + else + lastpos = nextpos + 6 + end + end + end + end + if not value then + value = escapechars[escchar] or escchar + lastpos = nextpos + 2 + end + n = n + 1 + buffer[n] = value + end + end + if n == 1 then + return buffer[1], lastpos + elseif n > 1 then + return concat (buffer), lastpos + else + return "", lastpos + end +end + +local scanvalue -- forward declaration + +local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) + local tbl, n = {}, 0 + local pos = startpos + 1 + if what == 'object' then + setmetatable (tbl, objectmeta) + else + setmetatable (tbl, arraymeta) + end + while true do + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + local char = strsub (str, pos, pos) + if char == closechar then + return tbl, pos + 1 + end + local val1, err + val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + if char == ":" then + if val1 == nil then + return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" + end + pos = scanwhite (str, pos + 1) + if not pos then return unterminated (str, what, startpos) end + local val2 + val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + tbl[val1] = val2 + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + else + n = n + 1 + tbl[n] = val1 + end + if char == "," then + pos = pos + 1 + end + end +end + +scanvalue = function (str, pos, nullval, objectmeta, arraymeta) + pos = pos or 1 + pos = scanwhite (str, pos) + if not pos then + return nil, strlen (str) + 1, "no valid JSON value (reached the end)" + end + local char = strsub (str, pos, pos) + if char == "{" then + return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) + elseif char == "[" then + return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) + elseif char == "\"" then + return scanstring (str, pos) + else + local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) + if pstart then + local number = str2num (strsub (str, pstart, pend)) + if number then + return number, pend + 1 + end + end + pstart, pend = strfind (str, "^%a%w*", pos) + if pstart then + local name = strsub (str, pstart, pend) + if name == "true" then + return true, pend + 1 + elseif name == "false" then + return false, pend + 1 + elseif name == "null" then + return nullval, pend + 1 + end + end + return nil, pos, "no valid JSON value at " .. loc (str, pos) + end +end + +local function optionalmetatables(...) + if select("#", ...) > 0 then + return ... + else + return {__jsontype = 'object'}, {__jsontype = 'array'} + end +end + +function json.decode (str, pos, nullval, ...) + local objectmeta, arraymeta = optionalmetatables(...) + return scanvalue (str, pos, nullval, objectmeta, arraymeta) +end + +function json.use_lpeg () + local g = require ("lpeg") + + if g.version() == "0.11" then + error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" + end + + local pegmatch = g.match + local P, S, R = g.P, g.S, g.R + + local function ErrorCall (str, pos, msg, state) + if not state.msg then + state.msg = msg .. " at " .. loc (str, pos) + state.pos = pos + end + return false + end + + local function Err (msg) + return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) + end + + local SingleLineComment = P"//" * (1 - S"\n\r")^0 + local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" + local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 + + local PlainChar = 1 - S"\"\\\n\r" + local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars + local HexDigit = R("09", "af", "AF") + local function UTF16Surrogate (match, pos, high, low) + high, low = tonumber (high, 16), tonumber (low, 16) + if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then + return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) + else + return false + end + end + local function UTF16BMP (hex) + return unichar (tonumber (hex, 16)) + end + local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) + local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP + local Char = UnicodeEscape + EscapeSequence + PlainChar + local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string") + local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) + local Fractal = P"." * R"09"^0 + local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 + local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num + local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) + local SimpleValue = Number + String + Constant + local ArrayContent, ObjectContent + + -- The functions parsearray and parseobject parse only a single value/pair + -- at a time and store them directly to avoid hitting the LPeg limits. + local function parsearray (str, pos, nullval, state) + local obj, cont + local npos + local t, nt = {}, 0 + repeat + obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) + if not npos then break end + pos = npos + nt = nt + 1 + t[nt] = obj + until cont == 'last' + return pos, setmetatable (t, state.arraymeta) + end + + local function parseobject (str, pos, nullval, state) + local obj, key, cont + local npos + local t = {} + repeat + key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) + if not npos then break end + pos = npos + t[key] = obj + until cont == 'last' + return pos, setmetatable (t, state.objectmeta) + end + + local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected") + local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected") + local Value = Space * (Array + Object + SimpleValue) + local ExpectedValue = Value + Space * Err "value expected" + ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() + local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue) + ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() + local DecodeValue = ExpectedValue * g.Cp () + + function json.decode (str, pos, nullval, ...) + local state = {} + state.objectmeta, state.arraymeta = optionalmetatables(...) + local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) + if state.msg then + return nil, state.pos, state.msg + else + return obj, retpos + end + end + + -- use this function only once: + json.use_lpeg = function () return json end + + json.using_lpeg = true + + return json -- so you can get the module using json = require "dkjson".use_lpeg() +end + +if always_try_using_lpeg then + pcall (json.use_lpeg) +end + +json.parse = json.decode +json.stringify = json.encode + +return exports diff --git a/plugins/mime/LICENSE b/plugins/mime/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/mime/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/mime/README.md b/plugins/mime/README.md new file mode 100644 index 00000000000..550ba582b69 --- /dev/null +++ b/plugins/mime/README.md @@ -0,0 +1,2 @@ +# luv-mime +A luv port of weblit's mime module diff --git a/plugins/mime/init.lua b/plugins/mime/init.lua new file mode 100644 index 00000000000..ac41dad1cfc --- /dev/null +++ b/plugins/mime/init.lua @@ -0,0 +1,194 @@ +local exports = {} +exports.name = "creationix/mime" +exports.version = "0.1.2-1" +exports.description = "A simple mime type database useful for serving static files over http." +exports.tags = {"mime", "static"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } +exports.homepage = "https://github.com/creationix/weblit/blob/master/libs/mime.lua" + +local mime = exports +local table = { + ["3gp"] = "video/3gpp", + a = "application/octet-stream", + ai = "application/postscript", + aif = "audio/x-aiff", + aiff = "audio/x-aiff", + asc = "application/pgp-signature", + asf = "video/x-ms-asf", + asm = "text/x-asm", + asx = "video/x-ms-asf", + atom = "application/atom+xml", + au = "audio/basic", + avi = "video/x-msvideo", + bat = "application/x-msdownload", + bin = "application/octet-stream", + bmp = "image/bmp", + bz2 = "application/x-bzip2", + c = "text/x-c", + cab = "application/vnd.ms-cab-compressed", + cc = "text/x-c", + chm = "application/vnd.ms-htmlhelp", + class = "application/octet-stream", + com = "application/x-msdownload", + conf = "text/plain", + cpp = "text/x-c", + crt = "application/x-x509-ca-cert", + css = "text/css", + csv = "text/csv", + cxx = "text/x-c", + deb = "application/x-debian-package", + der = "application/x-x509-ca-cert", + diff = "text/x-diff", + djv = "image/vnd.djvu", + djvu = "image/vnd.djvu", + dll = "application/x-msdownload", + dmg = "application/octet-stream", + doc = "application/msword", + dot = "application/msword", + dtd = "application/xml-dtd", + dvi = "application/x-dvi", + ear = "application/java-archive", + eml = "message/rfc822", + eps = "application/postscript", + exe = "application/x-msdownload", + f = "text/x-fortran", + f77 = "text/x-fortran", + f90 = "text/x-fortran", + flv = "video/x-flv", + ["for"] = "text/x-fortran", + gem = "application/octet-stream", + gemspec = "text/x-script.ruby", + gif = "image/gif", + gz = "application/x-gzip", + h = "text/x-c", + hh = "text/x-c", + htm = "text/html", + html = "text/html", + ico = "image/vnd.microsoft.icon", + ics = "text/calendar", + ifb = "text/calendar", + iso = "application/octet-stream", + jar = "application/java-archive", + java = "text/x-java-source", + jnlp = "application/x-java-jnlp-file", + jpeg = "image/jpeg", + jpg = "image/jpeg", + js = "application/javascript", + json = "application/json", + less = "text/css", + log = "text/plain", + lua = "text/x-lua", + luac = "application/x-lua-bytecode", + m3u = "audio/x-mpegurl", + m4v = "video/mp4", + man = "text/troff", + manifest = "text/cache-manifest", + markdown = "text/markdown", + mathml = "application/mathml+xml", + mbox = "application/mbox", + mdoc = "text/troff", + md = "text/markdown", + me = "text/troff", + mid = "audio/midi", + midi = "audio/midi", + mime = "message/rfc822", + mml = "application/mathml+xml", + mng = "video/x-mng", + mov = "video/quicktime", + mp3 = "audio/mpeg", + mp4 = "video/mp4", + mp4v = "video/mp4", + mpeg = "video/mpeg", + mpg = "video/mpeg", + ms = "text/troff", + msi = "application/x-msdownload", + odp = "application/vnd.oasis.opendocument.presentation", + ods = "application/vnd.oasis.opendocument.spreadsheet", + odt = "application/vnd.oasis.opendocument.text", + ogg = "application/ogg", + p = "text/x-pascal", + pas = "text/x-pascal", + pbm = "image/x-portable-bitmap", + pdf = "application/pdf", + pem = "application/x-x509-ca-cert", + pgm = "image/x-portable-graymap", + pgp = "application/pgp-encrypted", + pkg = "application/octet-stream", + pl = "text/x-script.perl", + pm = "text/x-script.perl-module", + png = "image/png", + pnm = "image/x-portable-anymap", + ppm = "image/x-portable-pixmap", + pps = "application/vnd.ms-powerpoint", + ppt = "application/vnd.ms-powerpoint", + ps = "application/postscript", + psd = "image/vnd.adobe.photoshop", + py = "text/x-script.python", + qt = "video/quicktime", + ra = "audio/x-pn-realaudio", + rake = "text/x-script.ruby", + ram = "audio/x-pn-realaudio", + rar = "application/x-rar-compressed", + rb = "text/x-script.ruby", + rdf = "application/rdf+xml", + roff = "text/troff", + rpm = "application/x-redhat-package-manager", + rss = "application/rss+xml", + rtf = "application/rtf", + ru = "text/x-script.ruby", + s = "text/x-asm", + sgm = "text/sgml", + sgml = "text/sgml", + sh = "application/x-sh", + sig = "application/pgp-signature", + snd = "audio/basic", + so = "application/octet-stream", + svg = "image/svg+xml", + svgz = "image/svg+xml", + swf = "application/x-shockwave-flash", + t = "text/troff", + tar = "application/x-tar", + tbz = "application/x-bzip-compressed-tar", + tci = "application/x-topcloud", + tcl = "application/x-tcl", + tex = "application/x-tex", + texi = "application/x-texinfo", + texinfo = "application/x-texinfo", + text = "text/plain", + tif = "image/tiff", + tiff = "image/tiff", + torrent = "application/x-bittorrent", + tr = "text/troff", + ttf = "application/x-font-ttf", + txt = "text/plain", + vcf = "text/x-vcard", + vcs = "text/x-vcalendar", + vrml = "model/vrml", + war = "application/java-archive", + wav = "audio/x-wav", + webm = "video/webm", + wma = "audio/x-ms-wma", + wmv = "video/x-ms-wmv", + wmx = "video/x-ms-wmx", + wrl = "model/vrml", + wsdl = "application/wsdl+xml", + xbm = "image/x-xbitmap", + xhtml = "application/xhtml+xml", + xls = "application/vnd.ms-excel", + xml = "application/xml", + xpm = "image/x-xpixmap", + xsl = "application/xml", + xslt = "application/xslt+xml", + yaml = "text/yaml", + yml = "text/yaml", + zip = "application/zip", +} +mime.table = table +mime.default = "application/octet-stream" + +function mime.getType(path) + return mime.table[path:lower():match("[^.]*$")] or mime.default +end + +return mime diff --git a/plugins/path/LICENSE b/plugins/path/LICENSE new file mode 100644 index 00000000000..8f71f43fee3 --- /dev/null +++ b/plugins/path/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/path/README.md b/plugins/path/README.md new file mode 100644 index 00000000000..75d881e35e3 --- /dev/null +++ b/plugins/path/README.md @@ -0,0 +1,2 @@ +# luv-path +A luv port of luvi's path module diff --git a/plugins/path/init.lua b/plugins/path/init.lua new file mode 100644 index 00000000000..d0e427d02ee --- /dev/null +++ b/plugins/path/init.lua @@ -0,0 +1,139 @@ +--[[ + +Copyright 2014 The Luvit Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--]] + +-- Extracted from https://github.com/luvit/luvi/blob/master/src/lua/init.lua + +local isWindows +if jit and jit.os then + -- Luajit provides explicit platform detection + isWindows = jit.os == "Windows" +else + -- Normal lua will only have \ for path separator on windows. + isWindows = package.config:find("\\") and true or false +end + +local uv = require('luv') + +local getPrefix, splitPath, joinParts + +if isWindows then + -- Windows aware path utilities + function getPrefix(path) + return path:match("^%a:\\") or + path:match("^/") or + path:match("^\\+") + end + function splitPath(path) + local parts = {} + for part in string.gmatch(path, '([^/\\]+)') do + table.insert(parts, part) + end + return parts + end + function joinParts(prefix, parts, i, j) + if not prefix then + return table.concat(parts, '/', i, j) + elseif prefix ~= '/' then + return prefix .. table.concat(parts, '\\', i, j) + else + return prefix .. table.concat(parts, '/', i, j) + end + end +else + -- Simple optimized versions for UNIX systems + function getPrefix(path) + return path:match("^/") + end + function splitPath(path) + local parts = {} + for part in string.gmatch(path, '([^/]+)') do + table.insert(parts, part) + end + return parts + end + function joinParts(prefix, parts, i, j) + if prefix then + return prefix .. table.concat(parts, '/', i, j) + end + return table.concat(parts, '/', i, j) + end +end + +local function pathJoin(...) + local inputs = {...} + local l = #inputs + + -- Find the last segment that is an absolute path + -- Or if all are relative, prefix will be nil + local i = l + local prefix + while true do + prefix = getPrefix(inputs[i]) + if prefix or i <= 1 then break end + i = i - 1 + end + + -- If there was one, remove its prefix from its segment + if prefix then + inputs[i] = inputs[i]:sub(#prefix) + end + + -- Split all the paths segments into one large list + local parts = {} + while i <= l do + local sub = splitPath(inputs[i]) + for j = 1, #sub do + parts[#parts + 1] = sub[j] + end + i = i + 1 + end + + -- Evaluate special segments in reverse order. + local skip = 0 + local reversed = {} + for idx = #parts, 1, -1 do + local part = parts[idx] + if part == '.' then + -- Ignore + elseif part == '..' then + skip = skip + 1 + elseif skip > 0 then + skip = skip - 1 + else + reversed[#reversed + 1] = part + end + end + + -- Reverse the list again to get the correct order + parts = reversed + for idx = 1, #parts / 2 do + local j = #parts - idx + 1 + parts[idx], parts[j] = parts[j], parts[idx] + end + + local path = joinParts(prefix, parts) + return path +end + +return { + isWindows = isWindows, + join = pathJoin, + getPrefix = getPrefix, + splitPath = splitPath, + joinparts = joinParts, +} diff --git a/plugins/pretty-print/LICENSE b/plugins/pretty-print/LICENSE new file mode 100644 index 00000000000..8f71f43fee3 --- /dev/null +++ b/plugins/pretty-print/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/pretty-print/README.md b/plugins/pretty-print/README.md new file mode 100644 index 00000000000..82372160e09 --- /dev/null +++ b/plugins/pretty-print/README.md @@ -0,0 +1,2 @@ +# luv-pretty-print +luvit's pretty-print module re-packaged as a luv submodule. diff --git a/plugins/pretty-print/init.lua b/plugins/pretty-print/init.lua new file mode 100644 index 00000000000..f1c112d6e4f --- /dev/null +++ b/plugins/pretty-print/init.lua @@ -0,0 +1,362 @@ +--[[ + +Copyright 2014-2015 The Luvit Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--]] + +-- Luv port by Tim Caswell + +local exports = {} +exports.name = "luvit/pretty-print" +exports.version = "1.0.3" +exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/pretty-print.lua" +exports.description = "A lua value pretty printer and colorizer for terminals." +exports.tags = {"colors", "tty"} +exports.license = "Apache 2" +exports.author = { name = "Tim Caswell" } + +local uv = require('luv') + +local prettyPrint, dump, strip, color, colorize, loadColors +local theme = {} +local useColors = false +local defaultTheme + +local stdout, stdin, stderr, width + +local quote, quote2, dquote, dquote2, obracket, cbracket, obrace, cbrace, comma, equals, controls + +local themes = { + -- nice color theme using 16 ansi colors + [16] = { + property = "0;37", -- white + sep = "1;30", -- bright-black + braces = "1;30", -- bright-black + + ["nil"] = "1;30", -- bright-black + boolean = "0;33", -- yellow + number = "1;33", -- bright-yellow + string = "0;32", -- green + quotes = "1;32", -- bright-green + escape = "1;32", -- bright-green + ["function"] = "0;35", -- purple + thread = "1;35", -- bright-purple + + table = "1;34", -- bright blue + userdata = "1;36", -- bright cyan + cdata = "0;36", -- cyan + + err = "1;31", -- bright red + success = "1;33;42", -- bright-yellow on green + failure = "1;33;41", -- bright-yellow on red + highlight = "1;36;44", -- bright-cyan on blue + }, + -- nice color theme using ansi 256-mode colors + [256] = { + property = "38;5;253", + braces = "38;5;247", + sep = "38;5;240", + + ["nil"] = "38;5;244", + boolean = "38;5;220", -- yellow-orange + number = "38;5;202", -- orange + string = "38;5;34", -- darker green + quotes = "38;5;40", -- green + escape = "38;5;46", -- bright green + ["function"] = "38;5;129", -- purple + thread = "38;5;199", -- pink + + table = "38;5;27", -- blue + userdata = "38;5;39", -- blue2 + cdata = "38;5;69", -- teal + + err = "38;5;196", -- bright red + success = "38;5;120;48;5;22", -- bright green on dark green + failure = "38;5;215;48;5;52", -- bright red on dark red + highlight = "38;5;45;48;5;236", -- bright teal on dark grey + }, +} + +local special = { + [7] = 'a', + [8] = 'b', + [9] = 't', + [10] = 'n', + [11] = 'v', + [12] = 'f', + [13] = 'r' +} + +function strip(str) + return string.gsub(str, '\027%[[^m]*m', '') +end + + +function loadColors(index) + if index == nil then index = defaultTheme end + + -- Remove the old theme + for key in pairs(theme) do + theme[key] = nil + end + + if index then + local new = themes[index] + if not new then error("Invalid theme index: " .. tostring(index)) end + -- Add the new theme + for key in pairs(new) do + theme[key] = new[key] + end + useColors = true + else + useColors = false + end + + quote = colorize('quotes', "'", 'string') + quote2 = colorize('quotes', "'") + dquote = colorize('quotes', '"', 'string') + dquote2 = colorize('quotes', '"') + obrace = colorize('braces', '{ ') + cbrace = colorize('braces', '}') + obracket = colorize('property', '[') + cbracket = colorize('property', ']') + comma = colorize('sep', ', ') + equals = colorize('sep', ' = ') + + controls = {} + for i = 0, 31 do + local c = special[i] + if not c then + if i < 10 then + c = "00" .. tostring(i) + else + c = "0" .. tostring(i) + end + end + controls[i] = colorize('escape', '\\' .. c, 'string') + end + controls[92] = colorize('escape', '\\\\', 'string') + controls[34] = colorize('escape', '\\"', 'string') + controls[39] = colorize('escape', "\\'", 'string') + for i = 128, 255 do + local c + if i < 100 then + c = "0" .. tostring(i) + else + c = tostring(i) + end + controls[i] = colorize('escape', '\\' .. c, 'string') + end + +end + +function color(colorName) + return '\27[' .. (theme[colorName] or '0') .. 'm' +end + +function colorize(colorName, string, resetName) + return useColors and + (color(colorName) .. tostring(string) .. color(resetName)) or + tostring(string) +end + +local function stringEscape(c) + return controls[string.byte(c, 1)] +end + +function dump(value, recurse, nocolor) + local seen = {} + local output = {} + local offset = 0 + local stack = {} + + local function recalcOffset(index) + for i = index + 1, #output do + local m = string.match(output[i], "\n([^\n]*)$") + if m then + offset = #(strip(m)) + else + offset = offset + #(strip(output[i])) + end + end + end + + local function write(text, length) + if not length then length = #(strip(text)) end + -- Create room for data by opening parent blocks + -- Start at the root and go down. + local i = 1 + while offset + length > width and stack[i] do + local entry = stack[i] + if not entry.opened then + entry.opened = true + table.insert(output, entry.index + 1, "\n" .. string.rep(" ", i)) + -- Recalculate the offset + recalcOffset(entry.index) + -- Bump the index of all deeper entries + for j = i + 1, #stack do + stack[j].index = stack[j].index + 1 + end + end + i = i + 1 + end + output[#output + 1] = text + offset = offset + length + if offset > width then + return dump(stack) + end + end + + local function indent() + stack[#stack + 1] = { + index = #output, + opened = false, + } + end + + local function unindent() + stack[#stack] = nil + end + + local function process(value) + local typ = type(value) + if typ == 'string' then + if string.match(value, "'") and not string.match(value, '"') then + write(dquote .. string.gsub(value, '[%c\\\128-\255]', stringEscape) .. dquote2) + else + write(quote .. string.gsub(value, "[%c\\'\128-\255]", stringEscape) .. quote2) + end + elseif typ == 'table' and not seen[value] then + if not recurse then seen[value] = true end + write(obrace) + local i = 1 + -- Count the number of keys so we know when to stop adding commas + local total = 0 + for _ in pairs(value) do total = total + 1 end + + local nextIndex = 1 + for k, v in pairs(value) do + indent() + if k == nextIndex then + -- if the key matches the last numerical index + 1 + -- This is how lists print without keys + nextIndex = k + 1 + process(v) + else + if type(k) == "string" and string.find(k,"^[%a_][%a%d_]*$") then + write(colorize("property", k) .. equals) + else + write(obracket) + process(k) + write(cbracket .. equals) + end + if type(v) == "table" then + process(v) + else + indent() + process(v) + unindent() + end + end + if i < total then + write(comma) + else + write(" ") + end + i = i + 1 + unindent() + end + write(cbrace) + else + write(colorize(typ, tostring(value))) + end + end + + process(value) + local s = table.concat(output, "") + return nocolor and strip(s) or s +end + +-- Print replacement that goes through libuv. This is useful on windows +-- to use libuv's code to translate ansi escape codes to windows API calls. +function _G.print(...) + local n = select('#', ...) + local arguments = {...} + for i = 1, n do + arguments[i] = tostring(arguments[i]) + end + uv.write(stdout, table.concat(arguments, "\t") .. "\n") +end + +function prettyPrint(...) + local n = select('#', ...) + local arguments = { ... } + + for i = 1, n do + arguments[i] = dump(arguments[i]) + end + + print(table.concat(arguments, "\t")) +end + +function strip(str) + return string.gsub(str, '\027%[[^m]*m', '') +end + +if uv.guess_handle(0) == 'tty' then + stdin = assert(uv.new_tty(0, true)) +else + stdin = uv.new_pipe(false) + uv.pipe_open(stdin, 0) +end + +if uv.guess_handle(1) == 'tty' then + stdout = assert(uv.new_tty(1, false)) + width = uv.tty_get_winsize(stdout) + if width == 0 then width = 80 end + -- auto-detect when 16 color mode should be used + local term = os.getenv("TERM") + if term == 'xterm' or term == 'xterm-256color' then + defaultTheme = 256 + else + defaultTheme = 16 + end +else + stdout = uv.new_pipe(false) + uv.pipe_open(stdout, 1) + width = 80 +end +loadColors() + +if uv.guess_handle(2) == 'tty' then + stderr = assert(uv.new_tty(2, false)) +else + stderr = uv.new_pipe(false) + uv.pipe_open(stderr, 2) +end + +exports.loadColors = loadColors +exports.theme = theme +exports.print = print +exports.prettyPrint = prettyPrint +exports.dump = dump +exports.color = color +exports.colorize = colorize +exports.stdin = stdin +exports.stdout = stdout +exports.stderr = stderr +exports.strip = strip + +return exports diff --git a/plugins/querystring/LICENSE b/plugins/querystring/LICENSE new file mode 100644 index 00000000000..8f71f43fee3 --- /dev/null +++ b/plugins/querystring/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/querystring/README.md b/plugins/querystring/README.md new file mode 100644 index 00000000000..7f3437312bb --- /dev/null +++ b/plugins/querystring/README.md @@ -0,0 +1,2 @@ +# luv-querystring +A luv port of luvit's querystring diff --git a/plugins/querystring/init.lua b/plugins/querystring/init.lua new file mode 100644 index 00000000000..0e2675f9252 --- /dev/null +++ b/plugins/querystring/init.lua @@ -0,0 +1,105 @@ +--[[ + +Copyright 2015 The Luvit Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--]] + +local exports = {} +exports.name = "luvit/querystring" +exports.version = "1.0.2" +exports.license = "Apache 2" +exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/querystring.lua" +exports.description = "Node-style query-string codec for luvit" +exports.tags = {"luvit", "url", "codec"} + +local find = string.find +local gsub = string.gsub +local char = string.char +local byte = string.byte +local format = string.format +local match = string.match +local gmatch = string.gmatch + +function exports.urldecode(str) + str = gsub(str, '+', ' ') + str = gsub(str, '%%(%x%x)', function(h) + return char(tonumber(h, 16)) + end) + str = gsub(str, '\r\n', '\n') + return str +end + +function exports.urlencode(str) + if str then + str = gsub(str, '\n', '\r\n') + str = gsub(str, '([^%w])', function(c) + return format('%%%02X', byte(c)) + end) + end + return str +end + +local function stringifyPrimitive(v) + return tostring(v) +end + +function exports.stringify(params, sep, eq) + if not sep then sep = '&' end + if not eq then eq = '=' end + if type(params) == "table" then + local fields = {} + for key,value in pairs(params) do + local keyString = exports.urlencode(stringifyPrimitive(key)) .. eq + if type(value) == "table" then + for _, v in ipairs(value) do + table.insert(fields, keyString .. exports.urlencode(stringifyPrimitive(v))) + end + else + table.insert(fields, keyString .. exports.urlencode(stringifyPrimitive(value))) + end + end + return table.concat(fields, sep) + end + return '' +end + +-- parse querystring into table. urldecode tokens +function exports.parse(str, sep, eq) + if not sep then sep = '&' end + if not eq then eq = '=' end + local vars = {} + for pair in gmatch(tostring(str), '[^' .. sep .. ']+') do + if not find(pair, eq) then + vars[exports.urldecode(pair)] = '' + else + local key, value = match(pair, '([^' .. eq .. ']*)' .. eq .. '(.*)') + if key then + key = exports.urldecode(key) + value = exports.urldecode(value) + local type = type(vars[key]) + if type=='nil' then + vars[key] = value + elseif type=='table' then + table.insert(vars[key], value) + else + vars[key] = {vars[key],value} + end + end + end + end + return vars +end + +return exports diff --git a/plugins/weblit/LICENSE b/plugins/weblit/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/weblit/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/weblit/README.md b/plugins/weblit/README.md new file mode 100644 index 00000000000..55d6085d22b --- /dev/null +++ b/plugins/weblit/README.md @@ -0,0 +1,239 @@ +# weblit + +A web framework for luv (ported from luvit/lit) + +Weblit is a collection of lit packages that together form a nice web framework. + +## weblit/app + +This is the core of the framework. It's export value is the app itself. The +config functions can be chained off this config for super terse syntax. + +```lua +require('weblit/app') + + .bind({ + host = "0.0.0.0", + port = 8080 + }) + + .use(require('weblit/logger')) + .use(require('weblit/auto-headers')) + .use(require('weblit/etag-cache')) + + .route({ + method = "GET", + path = "/do/:user/:action", + domain = "*.myapp.io" + }, function (req, res, go) + -- Handle route + end) + + .start() + +``` + +### bind(options) + +Use this to configure your server. You can bind to multiple addresses and +ports. For example, the same server can listen on port `8080` using normal HTTP +while also listening on port `8443` using HTTPS. + +```lua +-- Listen on port 8080 internally using plain HTTP. +.bind({ + host = "127.0.0.1", + port = 8080 +}) + +-- Also listen on port 8443 externally using encrypted HTTPS. +.bind({ + host = "0.0.0.0", + port = 8443, + tls = { + cert = module:load("cert.pem"), + key = module:load("key.pem", + } +}) +``` + +The `host` option defaults to `"127.0.0.1"`. The default port depends on if +you're running as root and if the connection is TLS encrypted. + + | Root | User +------|-----:|------: +HTTP | 80 | 8080 +HTTPS | 442 | 8443 + + +### use(middleware) + +This adds a raw middleware to the chain. It's signature is: + +```lua +.use(function (req, res, go) + -- Log the request table + p("request", req) + -- Hand off to the next layer. + return go() +end) +``` + +The `req` table will contain information about the HTTP request. This includes +several fields: + + - `socket` - The raw libuv `uv_tty_t` socket. + - `method` - The HTTP request method verb like `GET` or `POST`. + - `path` - The raw HTTP request path (including query string). + - `headers` - A list of headers. Each header is a table with two entries for + key and value. For convenience, there are special `__index` and + `__newindex` metamethods that let you treat this like a case insensitive + key/value store. + - `version` - The HTTP version (Usually either `1.0` or `1.1`). + - `keepAlive` - A flag telling you if this should be a keepalive connection. + - `body` - The request body as a string. In the future, this may also be a stream. + +The `res` table also has some conventions used to form the response a piece at a +time. Initially it contains: + + - `code` - The response status code. Initially contains `404`. + - `headers` - Another special headers table like in `req`. + - `body` - The response body to send. Initially contains `"Not Found\n"`. + +The `go` function is to be called if you wish to not handle a request. This +allows other middleware layers to get a chance to respond to the request. Use a +tail call if there is nothing more to do. + +Otherwise do further processing after `go` returns. At this point, all inner +layers have finished and a response is ready in `res`. + +### route(options, middleware) + +Route is like use, but allows you to pre-filter the requests before the middleware +is called. + +```lua +.route({ + method = "PUT", + path = "/upload/:username" +}, function (req, res, go) + local url = saveFile(req.params.username, req.body) + res.code = 201 + res.headers.Location = url +end) +``` + +The route options accept several parameters: + + - `method` - This is a simple filter on a specific HTTP method verb. + - `path` - This is either an exact match or can contain patterns. Segments + looking like `:name` will match single path segments while `:name:` will + match multiple segments. The matches will go into `req.params`. Also any + query string will be stripped off, parsed out, and stored in `req.query`. + - `host` - Will filter against the `Host` header. This can be an exact match + or a glob match like `*.mydomain.org`. + - `filter` - Filter is a custom lua function that accepts `req` and returns + `true` or `false`. + +If the request matches all the requirements, then the middleware is called the +same as with `use`. + +### start + +Bind to the port(s), listen on the socket(s) and start accepting connections. + +## weblit/logger + +This is a simple middleware that logs the request method, url and user agent. +It also includes the response status code. + +Make sure to use it at the top of your middleware chain so that it's able to see +the final response code sent to the client. + +```lua +.use(require('weblit/logger')) +``` + +## weblit/auto-headers + +This implements lots of conventions and useful defaults that help your app +implement a proper HTTP server. + +You should always use this near the top of the list. The only middleware that +goes before this is the logger. + + +```lua +.use(require('weblit/auto-headers')) +``` + +## weblit/etag-cache + +This caches responses in memory keyed by etag. If there is no etag, but there +is a response body, it will use the body to generate an etag. + +Put this in your list after auto-headers, but before custom server logic. + +```lua +.use(require('weblit/etag-cache')) +``` + +## weblit/static + +This middleware serves static files to the user. Use this to serve your client- +side web assets. + +Usage is pretty simplistic for now. + +```lua +local static = require('weblit/static') +app.use(static("path/to/static/assets")) +``` + +If you want to only match a sub-path, use the router. + +```lua +app.route({ + path = "/blog/:path:" +}, static(pathJoin(module.dir, "articles"))) +``` + +The `path` param will be used if it exists and the full path will be used +otherwise. + +## weblit/websocket + +This implements a websocket upgrade handler. You can choose the subprotocol and +other routing information. + +```lua +app.websocket({ + path = "/v2/socket", -- Prefix for matching + protocol = "virgo/2.0", -- Restrict to a websocket sub-protocol +}, function (req, read, write) + -- Log the request headers + p(req) + -- Log and echo all messages + for message in read do + write(message) + end + -- End the stream + write() +end) +``` + + +## weblit + +This is the metapackage that simply includes the other modules. + +It exposes the other modules as a single exports table. + +```lua +exports.app = require('weblit/app') +exports.autoHeaders = require('weblit/auto-headers') +exports.etagCache = require('weblit/etag-cache') +exports.logger = require('weblit/logger') +exports.static = require('weblit/static') +exports.websocket = require('weblit/websocket') +``` diff --git a/plugins/weblit/app.lua b/plugins/weblit/app.lua new file mode 100644 index 00000000000..d3839c1df8e --- /dev/null +++ b/plugins/weblit/app.lua @@ -0,0 +1,261 @@ + + +local createServer = require('coro-net').createServer +local wrapper = require('coro-wrapper') +local readWrap, writeWrap = wrapper.reader, wrapper.writer +local httpCodec = require('http-codec') +--local tlsWrap = require('coro-tls').wrap +local parseQuery = require('querystring').parse + +-- Ignore SIGPIPE if it exists on platform +local uv = require('luv') +if uv.constants.SIGPIPE then + uv.new_signal():start("sigpipe") +end + +local server = {} +local handlers = {} +local bindings = {} + +-- Provide a nice case insensitive interface to headers. +local headerMeta = { + __index = function (list, name) + if type(name) ~= "string" then + return rawget(list, name) + end + name = name:lower() + for i = 1, #list do + local key, value = unpack(list[i]) + if key:lower() == name then return value end + end + end, + __newindex = function (list, name, value) + -- non-string keys go through as-is. + if type(name) ~= "string" then + return rawset(list, name, value) + end + -- First remove any existing pairs with matching key + local lowerName = name:lower() + for i = #list, 1, -1 do + if list[i][1]:lower() == lowerName then + table.remove(list, i) + end + end + -- If value is nil, we're done + if value == nil then return end + -- Otherwise, set the key(s) + if (type(value) == "table") then + -- We accept a table of strings + for i = 1, #value do + rawset(list, #list + 1, {name, tostring(value[i])}) + end + else + -- Or a single value interperted as string + rawset(list, #list + 1, {name, tostring(value)}) + end + end, +} + +local function handleRequest(head, input, socket) + local req = { + socket = socket, + method = head.method, + path = head.path, + headers = setmetatable({}, headerMeta), + version = head.version, + keepAlive = head.keepAlive, + body = input + } + for i = 1, #head do + req.headers[i] = head[i] + end + + local res = { + code = 404, + headers = setmetatable({}, headerMeta), + body = "Not Found\n", + } + + local function run(i) + local success, err = pcall(function () + i = i or 1 + local go = i < #handlers + and function () + return run(i + 1) + end + or function () end + return handlers[i](req, res, go) + end) + if not success then + res.code = 500 + res.headers = setmetatable({}, headerMeta) + res.body = err + print(err) + end + end + run(1) + + local out = { + code = res.code, + keepAlive = res.keepAlive, + } + for i = 1, #res.headers do + out[i] = res.headers[i] + end + return out, res.body, res.upgrade +end + +local function handleConnection(rawRead, rawWrite, socket) + + -- Speak in HTTP events + local read, updateDecoder = readWrap(rawRead, httpCodec.decoder()) + local write, updateEncoder = writeWrap(rawWrite, httpCodec.encoder()) + + for head in read do + local parts = {} + for chunk in read do + if #chunk > 0 then + parts[#parts + 1] = chunk + else + break + end + end + local res, body, upgrade = handleRequest(head, #parts > 0 and table.concat(parts) or nil, socket) + write(res) + if upgrade then + return upgrade(read, write, updateDecoder, updateEncoder, socket) + end + write(body) + if not (res.keepAlive and head.keepAlive) then + break + end + end + write() + +end + +function server.bind(options) + if not options.host then + options.host = "127.0.0.1" + end + if not options.port then + options.port = require('uv').getuid() == 0 and + (options.tls and 443 or 80) or + (options.tls and 8443 or 8080) + end + bindings[#bindings + 1] = options + return server +end + +function server.use(handler) + handlers[#handlers + 1] = handler + return server +end + + +function server.start() + if #bindings == 0 then + server.bind({}) + end + for i = 1, #bindings do + local options = bindings[i] + createServer(options, function (rawRead, rawWrite, socket) + --local tls = options.tls + --if tls then + --rawRead, rawWrite = tlsWrap(rawRead, rawWrite, { + -- server = true, + --key = assert(tls.key, "tls key required"), + --cert = assert(tls.cert, "tls cert required"), + --}) + --end + return handleConnection(rawRead, rawWrite, socket) + end) + print("HTTP server listening at http" .. (options.tls and "s" or "") .. "://" .. options.host .. (options.port == (options.tls and 443 or 80) and "" or ":" .. options.port) .. "/") + end + return server +end + +local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' +local function escape(str) + return str:gsub(quotepattern, "%%%1") +end + +local function compileGlob(glob) + local parts = {"^"} + for a, b in glob:gmatch("([^*]*)(%**)") do + if #a > 0 then + parts[#parts + 1] = escape(a) + end + if #b > 0 then + parts[#parts + 1] = "(.*)" + end + end + parts[#parts + 1] = "$" + local pattern = table.concat(parts) + return function (string) + return string and string:match(pattern) + end +end + +local function compileRoute(route) + local parts = {"^"} + local names = {} + for a, b, c, d in route:gmatch("([^:]*):([_%a][_%w]*)(:?)([^:]*)") do + if #a > 0 then + parts[#parts + 1] = escape(a) + end + if #c > 0 then + parts[#parts + 1] = "(.*)" + else + parts[#parts + 1] = "([^/]*)" + end + names[#names + 1] = b + if #d > 0 then + parts[#parts + 1] = escape(d) + end + end + if #parts == 1 then + return function (string) + if string == route then return {} end + end + end + parts[#parts + 1] = "$" + local pattern = table.concat(parts) + return function (string) + local matches = {string:match(pattern)} + if #matches > 0 then + local results = {} + for i = 1, #matches do + results[i] = matches[i] + results[names[i]] = matches[i] + end + return results + end + end +end + +function server.route(options, handler) + local method = options.method + local path = options.path and compileRoute(options.path) + local host = options.host and compileGlob(options.host) + local filter = options.filter + server.use(function (req, res, go) + if method and req.method ~= method then return go() end + if host and not host(req.headers.host) then return go() end + if filter and not filter(req) then return go() end + local params + if path then + local pathname, query = req.path:match("^([^?]*)%??(.*)") + params = path(pathname) + if not params then return go() end + if #query > 0 then + req.query = parseQuery(query) + end + end + req.params = params or {} + return handler(req, res, go) + end) + return server +end + +return server diff --git a/plugins/weblit/auto-headers.lua b/plugins/weblit/auto-headers.lua new file mode 100644 index 00000000000..44ad65779e9 --- /dev/null +++ b/plugins/weblit/auto-headers.lua @@ -0,0 +1,92 @@ + + +--[[ + +Response automatic values: + - Auto Server header + - Auto Date Header + - code defaults to 404 with body "Not Found\n" + - if there is a string body add Content-Length and ETag if missing + - if string body and no Content-Type, use text/plain for valid utf-8, application/octet-stream otherwise + - Auto add "; charset=utf-8" to Content-Type when body is known to be valid utf-8 + - Auto 304 responses for if-none-match requests + - Auto strip body with HEAD requests + - Auto chunked encoding if body with unknown length + - if Connection header set and not keep-alive, set res.keepAlive to false + - Add Connection Keep-Alive/Close if not found based on res.keepAlive + +--TODO: utf8 scanning + +]] + +--local digest = require('openssl').digest.digest +local date = require('os').date + +return function (req, res, go) + local isHead = false + if req.method == "HEAD" then + req.method = "GET" + isHead = true + end + + local requested = req.headers["if-none-match"] + + go() + + -- We could use the fancy metatable, but this is much faster + local lowerHeaders = {} + local headers = res.headers + for i = 1, #headers do + local key, value = unpack(headers[i]) + lowerHeaders[key:lower()] = value + end + + + if not lowerHeaders.server then + headers[#headers + 1] = {"Server", serverName} + end + if not lowerHeaders.date then + headers[#headers + 1] = {"Date", date("!%a, %d %b %Y %H:%M:%S GMT")} + end + + if not lowerHeaders.connection then + if req.keepAlive then + lowerHeaders.connection = "Keep-Alive" + headers[#headers + 1] = {"Connection", "Keep-Alive"} + else + headers[#headers + 1] = {"Connection", "Close"} + end + end + res.keepAlive = lowerHeaders.connection and lowerHeaders.connection:lower() == "keep-alive" + + local body = res.body + if body then + local needLength = not lowerHeaders["content-length"] and not lowerHeaders["transfer-encoding"] + if type(body) == "string" then + if needLength then + headers[#headers + 1] = {"Content-Length", #body} + end + -- if not lowerHeaders.etag then + -- local etag = '"' .. digest("sha1", body) .. '"' + -- lowerHeaders.etag = etag + --headers[#headers + 1] = {"ETag", etag} + -- end + else + if needLength then + headers[#headers + 1] = {"Transfer-Encoding", "chunked"} + end + end + if not lowerHeaders["content-type"] then + headers[#headers + 1] = {"Content-Type", "text/plain"} + end + end + + local etag = lowerHeaders.etag + if requested and res.code >= 200 and res.code < 300 and requested == etag then + res.code = 304 + body = nil + end + + if isHead then body = nil end + res.body = body +end diff --git a/plugins/weblit/etag-cache.lua b/plugins/weblit/etag-cache.lua new file mode 100644 index 00000000000..e8c5d149b35 --- /dev/null +++ b/plugins/weblit/etag-cache.lua @@ -0,0 +1,39 @@ + +local function clone(headers) + local copy = setmetatable({}, getmetatable(headers)) + for i = 1, #headers do + copy[i] = headers[i] + end + return copy +end + +local cache = {} +return function (req, res, go) + local requested = req.headers["If-None-Match"] + local host = req.headers.Host + local key = host and host .. "|" .. req.path or req.path + local cached = cache[key] + if not requested and cached then + req.headers["If-None-Match"] = cached.etag + end + go() + local etag = res.headers.ETag + if not etag then return end + if res.code >= 200 and res.code < 300 then + local body = res.body + if not body or type(body) == "string" then + cache[key] = { + etag = etag, + code = res.code, + headers = clone(res.headers), + body = body + } + end + elseif res.code == 304 then + if not requested and cached and etag == cached.etag then + res.code = cached.code + res.headers = clone(cached.headers) + res.body = cached.body + end + end +end diff --git a/plugins/weblit/init.lua b/plugins/weblit/init.lua new file mode 100644 index 00000000000..f9224b7880c --- /dev/null +++ b/plugins/weblit/init.lua @@ -0,0 +1,8 @@ +local exports = {} +exports.app = require('weblit/app') +exports.autoHeaders = require('weblit/auto-headers') +exports.etagCache = require('weblit/etag-cache') +exports.logger = require('weblit/logger') +exports.static = require('weblit/static') +exports.websocket = require('weblit/websocket') +return exports diff --git a/plugins/weblit/logger.lua b/plugins/weblit/logger.lua new file mode 100644 index 00000000000..912b4eed768 --- /dev/null +++ b/plugins/weblit/logger.lua @@ -0,0 +1,10 @@ + +return function (req, res, go) + -- Skip this layer for clients who don't send User-Agent headers. + local userAgent = req.headers["user-agent"] + if not userAgent then return go() end + -- Run all inner layers first. + go() + -- And then log after everything is done + --print(string.format("%s %s %s %s", req.method, req.path, userAgent, res.code)) +end diff --git a/plugins/weblit/static.lua b/plugins/weblit/static.lua new file mode 100644 index 00000000000..b34ea638fa1 --- /dev/null +++ b/plugins/weblit/static.lua @@ -0,0 +1,62 @@ + +local getType = require("mime").getType +local jsonStringify = require('json').stringify + +local makeChroot = require('coro-fs').chroot + +return function (rootPath) + + local fs = makeChroot(rootPath) + + return function (req, res, go) + if req.method ~= "GET" then return go() end + local path = (req.params and req.params.path) or req.path + path = path:match("^[^?#]*") + if path:byte(1) == 47 then + path = path:sub(2) + end + local stat = fs.stat(path) + if not stat then return go() end + + local function renderFile() + local body = assert(fs.readFile(path)) + res.code = 200 + res.headers["Content-Type"] = getType(path) + res.body = body + return + end + + local function renderDirectory() + if req.path:byte(-1) ~= 47 then + res.code = 301 + res.headers.Location = req.path .. '/' + return + end + local files = {} + for entry in fs.scandir(path) do + if entry.name == "index.html" and entry.type == "file" then + path = (#path > 0 and path .. "/" or "") .. "index.html" + return renderFile() + end + files[#files + 1] = entry + entry.url = "http://" .. req.headers.host .. req.path .. entry.name + end + local body = jsonStringify(files) .. "\n" + res.code = 200 + res.headers["Content-Type"] = "application/json" + res.body = body + return + end + + if stat.type == "directory" then + return renderDirectory() + elseif stat.type == "file" then + if req.path:byte(-1) == 47 then + res.code = 301 + res.headers.Location = req.path:match("^(.*[^/])/+$") + return + end + return renderFile() + end + end +end diff --git a/plugins/weblit/websocket.lua b/plugins/weblit/websocket.lua new file mode 100644 index 00000000000..d5dfe572ea6 --- /dev/null +++ b/plugins/weblit/websocket.lua @@ -0,0 +1,82 @@ + +local websocketCodec = require('websocket-codec') + +local function websocketHandler(options, handler) + return function (req, res, go) + -- Websocket connections must be GET requests + -- with 'Upgrade: websocket' + -- and 'Connection: Upgrade' headers + local headers = req.headers + local connection = headers.connection + local upgrade = headers.upgrade + if not ( + req.method == "GET" and + upgrade and upgrade:lower():find("websocket", 1, true) and + connection and connection:lower():find("upgrade", 1, true) + ) then + return go() + end + + if options.filter and not options.filter(req) then + return go() + end + + -- If there is a sub-protocol specified, filter on it. + local protocol = options.protocol + if protocol then + local list = headers["sec-websocket-protocol"] + local foundProtocol + if list then + for item in list:gmatch("[^, ]+") do + if item == protocol then + foundProtocol = true + break + end + end + end + if not foundProtocol then + return go() + end + end + + -- Make sure it's a new client speaking v13 of the protocol + assert(tonumber(headers["sec-websocket-version"]) >= 13, "only websocket protocol v13 supported") + + -- Get the security key + local key = assert(headers["sec-websocket-key"], "websocket security required") + + res.code = 101 + headers = res.headers + headers.Upgrade = "websocket" + headers.Connection = "Upgrade" + headers["Sec-WebSocket-Accept"] = websocketCodec.acceptKey(key) + if protocol then + headers["Sec-WebSocket-Protocol"] = protocol + end + function res.upgrade(read, write, updateDecoder, updateEncoder) + updateDecoder(websocketCodec.decode) + updateEncoder(websocketCodec.encode) + local success, err = pcall(handler, req, read, write) + if not success then + print(err) + write({ + opcode = 1, + payload = err, + }) + return write() + end + end + end +end + +local server = require('weblit-app') +function server.websocket(options, handler) + server.route({ + method = "GET", + path = options.path, + host = options.host, + }, websocketHandler(options, handler)) + return server +end + +return websocketHandler diff --git a/plugins/websocket-codec/LICENSE b/plugins/websocket-codec/LICENSE new file mode 100644 index 00000000000..5789b767285 --- /dev/null +++ b/plugins/websocket-codec/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Tim Caswell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/websocket-codec/README.md b/plugins/websocket-codec/README.md new file mode 100644 index 00000000000..50f41340dce --- /dev/null +++ b/plugins/websocket-codec/README.md @@ -0,0 +1,2 @@ +# luv-websocket-codec +A luv port of lit's websocket codec diff --git a/plugins/websocket-codec/init.lua b/plugins/websocket-codec/init.lua new file mode 100644 index 00000000000..8a7da7f9190 --- /dev/null +++ b/plugins/websocket-codec/init.lua @@ -0,0 +1,261 @@ +local exports = {} + +exports.name = "creationix/websocket-codec" +exports.version = "1.0.7" +exports.homepage = "https://github.com/luvit/lit/blob/master/deps/websocket-codec.lua" +exports.description = "A codec implementing websocket framing and helpers for handshakeing" +exports.tags = {"http", "websocket", "codec"} +exports.license = "MIT" +exports.author = { name = "Tim Caswell" } + +local digest = require('openssl').digest.digest +local base64 = require('openssl').base64 +local random = require('openssl').random +local bit = require('bit') + +local band = bit.band +local bor = bit.bor +local bxor = bit.bxor +local rshift = bit.rshift +local lshift = bit.lshift +local char = string.char +local byte = string.byte +local sub = string.sub +local gmatch = string.gmatch +local lower = string.lower +local gsub = string.gsub +local concat = table.concat + +local function applyMask(data, mask) + local bytes = { + [0] = byte(mask, 1), + [1] = byte(mask, 2), + [2] = byte(mask, 3), + [3] = byte(mask, 4) + } + local out = {} + for i = 1, #data do + out[i] = char( + bxor(byte(data, i), bytes[(i - 1) % 4]) + ) + end + return concat(out) +end + +function exports.decode(chunk) + if #chunk < 2 then return end + local second = byte(chunk, 2) + local len = band(second, 0x7f) + local offset + if len == 126 then + if #chunk < 4 then return end + len = bor( + lshift(byte(chunk, 3), 8), + byte(chunk, 4)) + offset = 4 + elseif len == 127 then + if #chunk < 10 then return end + len = bor( + lshift(byte(chunk, 3), 56), + lshift(byte(chunk, 4), 48), + lshift(byte(chunk, 5), 40), + lshift(byte(chunk, 6), 32), + lshift(byte(chunk, 7), 24), + lshift(byte(chunk, 8), 16), + lshift(byte(chunk, 9), 8), + byte(chunk, 10)) + offset = 10 + else + offset = 2 + end + local mask = band(second, 0x80) > 0 + if mask then + offset = offset + 4 + end + if #chunk < offset + len then return end + + local first = byte(chunk, 1) + local payload = sub(chunk, offset + 1, offset + len) + assert(#payload == len, "Length mismatch") + if mask then + payload = applyMask(payload, sub(chunk, offset - 3, offset)) + end + local extra = sub(chunk, offset + len + 1) + return { + fin = band(first, 0x80) > 0, + rsv1 = band(first, 0x40) > 0, + rsv2 = band(first, 0x20) > 0, + rsv3 = band(first, 0x10) > 0, + opcode = band(first, 0xf), + mask = mask, + len = len, + payload = payload + }, extra +end + +function exports.encode(item) + if type(item) == "string" then + item = { + opcode = 2, + payload = item + } + end + local payload = item.payload + assert(type(payload) == "string", "payload must be string") + local len = #payload + local fin = item.fin + if fin == nil then fin = true end + local rsv1 = item.rsv1 + local rsv2 = item.rsv2 + local rsv3 = item.rsv3 + local opcode = item.opcode or 2 + local mask = item.mask + local chars = { + char(bor( + fin and 0x80 or 0, + rsv1 and 0x40 or 0, + rsv2 and 0x20 or 0, + rsv3 and 0x10 or 0, + opcode + )), + char(bor( + mask and 0x80 or 0, + len < 126 and len or (len < 0x10000) and 126 or 127 + )) + } + if len >= 0x10000 then + chars[3] = char(band(rshift(len, 56), 0xff)) + chars[4] = char(band(rshift(len, 48), 0xff)) + chars[5] = char(band(rshift(len, 40), 0xff)) + chars[6] = char(band(rshift(len, 32), 0xff)) + chars[7] = char(band(rshift(len, 24), 0xff)) + chars[8] = char(band(rshift(len, 16), 0xff)) + chars[9] = char(band(rshift(len, 8), 0xff)) + chars[10] = char(band(len, 0xff)) + elseif len >= 126 then + chars[3] = char(band(rshift(len, 8), 0xff)) + chars[4] = char(band(len, 0xff)) + end + if mask then + local key = random(4) + return concat(chars) .. key .. applyMask(payload, key) + end + return concat(chars) .. payload +end + +local websocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +function exports.acceptKey(key) + return gsub(base64(digest("sha1", key .. websocketGuid, true)), "\n", "") +end +local acceptKey = exports.acceptKey + +-- Make a client handshake connection +function exports.handshake(options, request) + local key = gsub(base64(random(20)), "\n", "") + local host = options.host + local path = options.path or "/" + local protocol = options.protocol + local req = { + method = "GET", + path = path, + {"Connection", "Upgrade"}, + {"Upgrade", "websocket"}, + {"Sec-WebSocket-Version", "13"}, + {"Sec-WebSocket-Key", key}, + } + for i = 1, #options do + req[#req + 1] = options[i] + end + if host then + req[#req + 1] = {"Host", host} + end + if protocol then + req[#req + 1] = {"Sec-WebSocket-Protocol", protocol} + end + local res = request(req) + if not res then + return nil, "Missing response from server" + end + -- Parse the headers for quick reading + if res.code ~= 101 then + return nil, "response must be code 101" + end + + local headers = {} + for i = 1, #res do + local name, value = unpack(res[i]) + headers[lower(name)] = value + end + + if not headers.connection or lower(headers.connection) ~= "upgrade" then + return nil, "Invalid or missing connection upgrade header in response" + end + if headers["sec-websocket-accept"] ~= acceptKey(key) then + return nil, "challenge key missing or mismatched" + end + if protocol and headers["sec-websocket-protocol"] ~= protocol then + return nil, "protocol missing or mistmatched" + end + return true +end + +function exports.handleHandshake(head, protocol) + + -- WebSocket connections must be GET requests + if not head.method == "GET" then return end + + -- Parse the headers for quick reading + local headers = {} + for i = 1, #head do + local name, value = unpack(head[i]) + headers[lower(name)] = value + end + + -- Must have 'Upgrade: websocket' and 'Connection: Upgrade' headers + if not (headers.connection and headers.upgrade and + headers.connection:lower():find("upgrade", 1, true) and + headers.upgrade:lower():find("websocket", 1, true)) then return end + + -- Make sure it's a new client speaking v13 of the protocol + if tonumber(headers["sec-websocket-version"]) < 13 then + return nil, "only websocket protocol v13 supported" + end + + local key = headers["sec-websocket-key"] + if not key then + return nil, "websocket security key missing" + end + + -- If the server wants a specified protocol, check for it. + if protocol then + local foundProtocol = false + local list = headers["sec-websocket-protocol"] + if list then + for item in gmatch(list, "[^, ]+") do + if item == protocol then + foundProtocol = true + break + end + end + end + if not foundProtocol then + return nil, "specified protocol missing in request" + end + end + + local accept = acceptKey(key) + + local res = { + code = 101, + {"Upgrade", "websocket"}, + {"Connection", "Upgrade"}, + {"Sec-WebSocket-Accept", accept}, + } + if protocol then + res[#res + 1] = {"Sec-WebSocket-Protocol", protocol} + end + + return res +end +return exports diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index 293657b1f45..c4686c5566f 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -19,6 +19,7 @@ #include "uiinput.h" #include "xmlfile.h" #include "coreutil.h" +#include "luaengine.h" #include @@ -1928,6 +1929,8 @@ void device_debug::instruction_hook(offs_t curpc) // flush any pending updates before waiting again machine.debug_view().flush_osd_updates(); + machine.manager().lua()->periodic_check(); + // clear the memory modified flag and wait global->memory_modified = false; if (machine.debug_flags & DEBUG_FLAG_OSD_ENABLED) diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp index f5d014750b2..97e107ebb49 100644 --- a/src/emu/emuopts.cpp +++ b/src/emu/emuopts.cpp @@ -43,6 +43,7 @@ const options_entry emu_options::s_option_entries[] = { OPTION_FONTPATH, ".", OPTION_STRING, "path to font files" }, { OPTION_CHEATPATH, "cheat", OPTION_STRING, "path to cheat files" }, { OPTION_CROSSHAIRPATH, "crosshair", OPTION_STRING, "path to crosshair files" }, + { OPTION_PLUGINSPATH, "plugins", OPTION_STRING, "path to plugin files" }, // output directory options { nullptr, nullptr, OPTION_HEADER, "CORE OUTPUT DIRECTORY OPTIONS" }, diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index 916a0f263f5..c8bbc58435a 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -56,6 +56,7 @@ enum #define OPTION_FONTPATH "fontpath" #define OPTION_CHEATPATH "cheatpath" #define OPTION_CROSSHAIRPATH "crosshairpath" +#define OPTION_PLUGINSPATH "pluginspath" // core directory options #define OPTION_CFG_DIRECTORY "cfg_directory" @@ -231,6 +232,7 @@ public: const char *font_path() const { return value(OPTION_FONTPATH); } const char *cheat_path() const { return value(OPTION_CHEATPATH); } const char *crosshair_path() const { return value(OPTION_CROSSHAIRPATH); } + const char *plugins_path() const { return value(OPTION_PLUGINSPATH); } // core directory options const char *cfg_directory() const { return value(OPTION_CFG_DIRECTORY); } diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp index 7c59ef69a2d..5119dec397c 100644 --- a/src/emu/machine.cpp +++ b/src/emu/machine.cpp @@ -387,8 +387,10 @@ int running_machine::run(bool firstrun) // execute CPUs if not paused if (!m_paused) + { m_scheduler.timeslice(); - + manager().lua()->periodic_check(); + } // otherwise, just pump video updates through else m_video->frame_update(); diff --git a/src/emu/mame.cpp b/src/emu/mame.cpp index f17c04e3b68..55fc78928f8 100644 --- a/src/emu/mame.cpp +++ b/src/emu/mame.cpp @@ -167,6 +167,15 @@ int machine_manager::execute() int error = MAMERR_NONE; m_lua->initialize(); + { + emu_file file(options().plugins_path(), OPEN_FLAG_READ); + file_error filerr = file.open("boot.lua"); + if (filerr == FILERR_NONE) + { + m_lua->load_script(file.fullpath()); + } + } + if (m_options.console()) { m_lua->start_console(); } -- cgit v1.2.3-70-g09d2