├── tests ├── res_test.json ├── res_test.txt ├── mod_test.lua ├── mod_test_2.lua ├── full │ ├── Lock.lua │ ├── HttpServer.lua │ ├── UdpSocket.lua │ ├── http_filters.lua │ ├── SerialWorker.lua │ ├── RingBuffer.lua │ ├── event.lua │ ├── Channel.lua │ ├── Pipe.lua │ ├── mqtt.lua │ ├── thread_memory.lua │ ├── tar.lua │ ├── gzip.lua │ ├── ZipFile.lua │ ├── thread_stream.lua │ ├── Buffer.lua │ ├── dns.lua │ ├── system_execute.lua │ ├── http_headers.lua │ ├── serial.lua │ ├── WebSocket.lua │ ├── zip.lua │ ├── http_form.lua │ ├── secure.lua │ ├── fd.lua │ └── FileStreamHandler.lua ├── getWriteReverseRead.lua ├── time.lua ├── randomChars.lua ├── runtests.sh ├── base │ ├── CoroutineScheduler.lua │ ├── shiftArguments.lua │ ├── ast.lua │ ├── FileDescriptor.lua │ ├── MessageDigest.lua │ ├── Map.lua │ ├── xml.lua │ ├── PromiseAsync.lua │ └── EventPublisher.lua └── genCertificateAndPKey.lua ├── jls ├── util │ ├── json-dkjson.lua │ ├── md │ │ ├── md5-openssl.lua │ │ ├── sha1-openssl.lua │ │ ├── crc32.lua │ │ ├── md5.lua │ │ ├── sha1.lua │ │ └── crc32-zlib.lua │ ├── Gpio.lua │ ├── Worker.lua │ ├── json-lunajson.lua │ ├── XmlParser-XmlParser.lua │ ├── json-cjson.lua │ ├── XmlParser-lxp.lua │ ├── cd │ │ ├── gzip.lua │ │ ├── deflate.lua │ │ └── hex.lua │ ├── ShareableQueue.lua │ ├── BlockingQueue.lua │ ├── Queue.lua │ ├── zip │ │ ├── Deflater.lua │ │ └── Inflater.lua │ ├── Worker-.lua │ ├── Struct.lua │ └── RingBuffer.lua ├── lang │ ├── BufferLocal.lua │ ├── BufferShared.lua │ ├── ProcessHandle-.lua │ ├── Lock.lua │ ├── Thread.lua │ ├── sys.lua │ ├── BufferGlobal.lua │ ├── ProcessHandle.lua │ ├── setenv.lua │ ├── sys-.lua │ ├── sys-luv.lua │ ├── sys-socket.lua │ ├── event.lua │ ├── formatCommandLine.lua │ ├── Thread-luv.lua │ ├── signal.lua │ ├── luv_async.lua │ ├── loopWithTimeout.lua │ ├── BufferView.lua │ ├── shiftArguments.lua │ ├── ProcessHandle-linux.lua │ ├── Buffer-buffer.lua │ ├── BufferFile.lua │ ├── Lock-buffer.lua │ ├── Thread-llthreads.lua │ ├── luv_stream.lua │ ├── ProcessHandle-win32.lua │ ├── event-luv.lua │ ├── Lock-.lua │ ├── ProcessHandle-luachild.lua │ └── Buffer.lua ├── io │ ├── Pipe.lua │ ├── Serial.lua │ ├── fs.lua │ ├── FileDescriptor.lua │ ├── streams │ │ ├── LimitedStreamHandler.lua │ │ ├── BufferedStreamHandler.lua │ │ ├── PromiseStreamHandler.lua │ │ ├── PromisesStreamHandler.lua │ │ ├── BlockStreamHandler.lua │ │ ├── RangeStreamHandler.lua │ │ └── DelayedStreamHandler.lua │ ├── fs-lfs.lua │ ├── fs-luv.lua │ ├── Serial-.lua │ ├── Serial-luv.lua │ ├── fs-.lua │ └── Pipe-luachild.lua ├── net │ ├── http │ │ ├── HttpContext.lua │ │ ├── handler │ │ │ ├── RestHttpHandler.lua │ │ │ ├── ResourceHttpHandler.lua │ │ │ ├── ZipFileHttpHandler.lua │ │ │ └── TableHttpHandler.lua │ │ ├── filter │ │ │ ├── LogHttpFilter.lua │ │ │ ├── PathHttpFilter.lua │ │ │ └── BasicAuthenticationHttpFilter.lua │ │ ├── Attributes.lua │ │ └── HttpSession.lua │ ├── TcpSocket.lua │ ├── UdpSocket.lua │ ├── dns-luv.lua │ └── dns-socket.lua └── init.lua ├── examples ├── favicon.ico ├── README.md ├── wsClient.lua ├── zip.lua ├── browser.lua ├── webviewDoc.lua └── acme.lua ├── LICENSE └── config.ld /tests/res_test.json: -------------------------------------------------------------------------------- 1 | {"a": "Hi", "b": 1} -------------------------------------------------------------------------------- /tests/res_test.txt: -------------------------------------------------------------------------------- 1 | Resource content -------------------------------------------------------------------------------- /jls/util/json-dkjson.lua: -------------------------------------------------------------------------------- 1 | return require('dkjson') 2 | -------------------------------------------------------------------------------- /jls/lang/BufferLocal.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.BufferGlobal') 2 | -------------------------------------------------------------------------------- /jls/lang/BufferShared.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.BufferFile') 2 | -------------------------------------------------------------------------------- /jls/lang/ProcessHandle-.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.ProcessHandleBase') -------------------------------------------------------------------------------- /jls/util/md/md5-openssl.lua: -------------------------------------------------------------------------------- 1 | return require('jls.util.MessageDigest').fromOpenssl('md5') -------------------------------------------------------------------------------- /jls/util/md/sha1-openssl.lua: -------------------------------------------------------------------------------- 1 | return require('jls.util.MessageDigest').fromOpenssl('sha1') -------------------------------------------------------------------------------- /examples/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javalikescript/luajls/HEAD/examples/favicon.ico -------------------------------------------------------------------------------- /jls/io/Pipe.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.io.Pipe-luv', 'jls.io.Pipe-luachild') 2 | -------------------------------------------------------------------------------- /jls/io/Serial.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.io.Serial-luv', 'jls.io.Serial-') 2 | -------------------------------------------------------------------------------- /jls/lang/Lock.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.lang.Lock-buffer', 'jls.lang.Lock-') 2 | -------------------------------------------------------------------------------- /jls/io/fs.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.io.fs-luv', 'jls.io.fs-lfs', 'jls.io.fs-') 2 | -------------------------------------------------------------------------------- /jls/net/http/HttpContext.lua: -------------------------------------------------------------------------------- 1 | -- Deprecated, to remove 2 | return require('jls.net.http.HttpServer').HttpContext 3 | -------------------------------------------------------------------------------- /jls/util/Gpio.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.util.Gpio-periphery', 'jls.util.Gpio-pigpio') 2 | -------------------------------------------------------------------------------- /jls/util/Worker.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.util.Worker-channel', 'jls.util.Worker-') 2 | -------------------------------------------------------------------------------- /jls/util/md/crc32.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.util.md.crc32-zlib', 'jls.util.md.crc32-') 2 | -------------------------------------------------------------------------------- /jls/util/md/md5.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.util.md.md5-openssl', 'jls.util.md.md5-') 2 | -------------------------------------------------------------------------------- /jls/util/md/sha1.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.util.md.sha1-openssl', 'jls.util.md.sha1-') 2 | -------------------------------------------------------------------------------- /jls/lang/Thread.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.lang.Thread-luv', 'jls.lang.Thread-llthreads') 2 | -------------------------------------------------------------------------------- /jls/net/TcpSocket.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.net.TcpSocket-luv', 'jls.net.TcpSocket-socket') 2 | -------------------------------------------------------------------------------- /jls/net/UdpSocket.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.net.UdpSocket-luv', 'jls.net.UdpSocket-socket') 2 | -------------------------------------------------------------------------------- /jls/lang/sys.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.lang.sys-luv', 'jls.lang.sys-socket', 'jls.lang.sys-') 2 | -------------------------------------------------------------------------------- /jls/io/FileDescriptor.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.io.FileDescriptor-luv', 'jls.io.FileDescriptor-') 2 | -------------------------------------------------------------------------------- /jls/net/http/handler/RestHttpHandler.lua: -------------------------------------------------------------------------------- 1 | -- Deprecated, to remove 2 | return require('jls.net.http.handler.RouterHttpHandler') 3 | -------------------------------------------------------------------------------- /jls/lang/BufferGlobal.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').tryRequire('jls.lang.Buffer-buffer') or require('jls.lang.BufferFile') 2 | -------------------------------------------------------------------------------- /tests/mod_test.lua: -------------------------------------------------------------------------------- 1 | return { 2 | a = { 3 | a1 = 1, 4 | a2 = 'A string value', 5 | }, 6 | b = true, 7 | c = 'Hi', 8 | name = 'mod_test', 9 | } -------------------------------------------------------------------------------- /jls/lang/ProcessHandle.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.loader').requireOne('jls.lang.ProcessHandle-luv', 'jls.lang.ProcessHandle-luachild', 'jls.lang.ProcessHandle-') 2 | -------------------------------------------------------------------------------- /tests/mod_test_2.lua: -------------------------------------------------------------------------------- 1 | return { 2 | a = { 3 | a1 = 1.23, 4 | a2 = 'A string value', 5 | }, 6 | b = false, 7 | c = 'Hello', 8 | name = 'mod_test_2', 9 | } -------------------------------------------------------------------------------- /jls/lang/setenv.lua: -------------------------------------------------------------------------------- 1 | local status, lcLib = pcall(require, 'luachild') 2 | if status and lcLib then 3 | return lcLib.setenv 4 | end 5 | return require('jls.lang.class').notImplementedFunction 6 | -------------------------------------------------------------------------------- /tests/full/Lock.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Lock = require('jls.lang.Lock') 4 | 5 | function Test_lock_unlock() 6 | local lock = Lock:new() 7 | lock:lock() 8 | lock:unlock() 9 | lock:finalize() 10 | end 11 | 12 | os.exit(lu.LuaUnit.run()) 13 | -------------------------------------------------------------------------------- /jls/io/streams/LimitedStreamHandler.lua: -------------------------------------------------------------------------------- 1 | return require('jls.lang.class').create('jls.io.streams.RangeStreamHandler', function(limitedStreamHandler, super) 2 | function limitedStreamHandler:initialize(handler, length, offset) 3 | super.initialize(self, handler, offset, length) 4 | end 5 | end) -------------------------------------------------------------------------------- /jls/util/json-lunajson.lua: -------------------------------------------------------------------------------- 1 | local jsonLib = require('lunajson') 2 | 3 | -- https://github.com/grafi-tt/lunajson 4 | 5 | local NULL = {} 6 | 7 | return { 8 | decode = function(value) 9 | return jsonLib.decode(value, nil, NULL) 10 | end, 11 | encode = function(value) 12 | return jsonLib.encode(value, NULL) 13 | end, 14 | null = NULL, 15 | } 16 | -------------------------------------------------------------------------------- /jls/io/fs-lfs.lua: -------------------------------------------------------------------------------- 1 | local fs = require('jls.io.fs-') 2 | local lfsLib = require('lfs') 3 | 4 | return { 5 | utime = lfsLib.touch, 6 | stat = lfsLib.attributes, 7 | currentdir = lfsLib.currentdir, 8 | mkdir = lfsLib.mkdir, 9 | rmdir = lfsLib.rmdir, 10 | unlink = fs.unlink, 11 | rename = fs.rename, 12 | copyfile = fs.copyfile, 13 | dir = lfsLib.dir 14 | } 15 | -------------------------------------------------------------------------------- /jls/net/dns-luv.lua: -------------------------------------------------------------------------------- 1 | local lib = require('luv') 2 | 3 | return { 4 | getaddrinfo = function(node, callback) 5 | lib.getaddrinfo(node, nil, {family = 'unspec', socktype = 'stream'}, callback) 6 | end, 7 | getnameinfo = function(addr, callback) 8 | lib.getnameinfo({ip = addr}, callback) 9 | end, 10 | interface_addresses = lib.interface_addresses, 11 | } 12 | -------------------------------------------------------------------------------- /jls/lang/sys-.lua: -------------------------------------------------------------------------------- 1 | local os_time = os.time 2 | 3 | return { 4 | timems = function() 5 | return os_time() * 1000 6 | end, 7 | gettimeofday = function() 8 | return os_time(), 0 9 | end, 10 | sleep = function(millis) 11 | local t = os_time() + (millis / 1000) 12 | while os_time() < t do end 13 | end, 14 | -- TODO move getenv/setenv to process or to env 15 | getenv = os.getenv, 16 | setenv = require('jls.lang.setenv'), 17 | } 18 | -------------------------------------------------------------------------------- /jls/net/dns-socket.lua: -------------------------------------------------------------------------------- 1 | local lib = require('socket').dns 2 | local class = require('jls.lang.class') 3 | 4 | return { 5 | getaddrinfo = function(node, callback) 6 | local addrinfo, err = lib.getaddrinfo(node) 7 | callback(err, addrinfo) 8 | end, 9 | getnameinfo = function(addr, callback) 10 | local names, err = lib.getnameinfo(addr) 11 | callback(err, names and names[1]) 12 | end, 13 | interface_addresses = class.notImplementedFunction, 14 | } 15 | -------------------------------------------------------------------------------- /tests/getWriteReverseRead.lua: -------------------------------------------------------------------------------- 1 | local List = require('jls.util.List') 2 | return function() 3 | local t = {} 4 | local function write(item) 5 | table.insert(t, item == nil and t or item) 6 | end 7 | local function reverse() 8 | List.reverse(t) 9 | end 10 | local function read() 11 | local item = table.remove(t) 12 | if item == t then 13 | return nil 14 | end 15 | return item 16 | end 17 | return write, reverse, read, t 18 | end 19 | -------------------------------------------------------------------------------- /jls/lang/sys-luv.lua: -------------------------------------------------------------------------------- 1 | local luvLib = require('luv') 2 | 3 | if type(luvLib.gettimeofday) ~= 'function' or type(luvLib.sleep) ~= 'function' then 4 | error('functions not available') 5 | end 6 | 7 | return { 8 | timems = function() 9 | local sec, usec = luvLib.gettimeofday() 10 | return math.floor(sec) * 1000 + usec // 1000 11 | end, 12 | gettimeofday = luvLib.gettimeofday, 13 | sleep = luvLib.sleep, 14 | getenv = luvLib.os_getenv, 15 | setenv = luvLib.os_setenv, 16 | } 17 | -------------------------------------------------------------------------------- /jls/init.lua: -------------------------------------------------------------------------------- 1 | -- Provide a jls table that load modules 2 | 3 | local function newIndexTable(name) 4 | local t = {} 5 | setmetatable(t, { 6 | __index = function(tt, key) 7 | local path = name..'.'..key 8 | local m = package.loaded[path] 9 | if m then 10 | return m 11 | end 12 | local status 13 | status, m = pcall(require, path) 14 | if status then 15 | return m 16 | end 17 | m = newIndexTable(path) 18 | tt[key] = m 19 | return m 20 | end 21 | }) 22 | return t 23 | end 24 | 25 | return newIndexTable('jls') 26 | -------------------------------------------------------------------------------- /tests/full/HttpServer.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local HttpServer = require('jls.net.http.HttpServer') 4 | 5 | function Test_computeIndex() 6 | local computeIndex = HttpServer.HttpContext.computeIndex 7 | lu.assertEquals(computeIndex(''), 0) 8 | lu.assertEquals(computeIndex('/'), 1) 9 | lu.assertEquals(computeIndex('/(.*)'), 1) 10 | lu.assertEquals(computeIndex('/(%.*)%...'), 4) 11 | lu.assertEquals(computeIndex('/?(.*)'), 0) 12 | lu.assertEquals(computeIndex('/static/(.*)'), 8) 13 | lu.assertEquals(computeIndex('/addon/([^/]*)/?(.*)'), 7) 14 | end 15 | 16 | os.exit(lu.LuaUnit.run()) 17 | -------------------------------------------------------------------------------- /jls/util/md/crc32-zlib.lua: -------------------------------------------------------------------------------- 1 | local zLib = require('zlib') 2 | 3 | return require('jls.lang.class').create('jls.util.MessageDigest', function(crc32) 4 | 5 | function crc32:initialize() 6 | self:reset() 7 | end 8 | 9 | function crc32:update(s) 10 | self.value = self.compute(s) 11 | return self 12 | end 13 | 14 | function crc32:digest() 15 | return self.value >> 0 16 | end 17 | 18 | function crc32:reset() 19 | self.value = 0 20 | self.compute = zLib.crc32() 21 | return self 22 | end 23 | 24 | function crc32:getAlgorithm() 25 | return 'CRC32' 26 | end 27 | 28 | end) 29 | -------------------------------------------------------------------------------- /tests/time.lua: -------------------------------------------------------------------------------- 1 | return function(fn, ...) 2 | local system = require('jls.lang.system') 3 | collectgarbage('collect') 4 | collectgarbage('stop') 5 | local gcCountBefore = collectgarbage('count') 6 | local startClock = os.clock() 7 | local startMillis = system.currentTimeMillis() 8 | fn(...) 9 | local endMillis = system.currentTimeMillis() 10 | local endClock = os.clock() 11 | local gcCountAfter = collectgarbage('count') 12 | collectgarbage('restart') 13 | return endMillis - startMillis, math.floor((endClock - startClock) * 1000), math.floor((gcCountAfter - gcCountBefore) * 1024) 14 | end 15 | -------------------------------------------------------------------------------- /jls/lang/sys-socket.lua: -------------------------------------------------------------------------------- 1 | local luaSocketLib = require('socket') 2 | 3 | local m = { 4 | timems = function() 5 | return math.floor(luaSocketLib.gettime() * 1000) 6 | end, 7 | gettimeofday = function() 8 | local t = luaSocketLib.gettime() 9 | local sec = math.floor(t) 10 | local usec = math.floor((t - sec) * 1000000) 11 | return sec, usec 12 | end, 13 | sleep = function(millis) 14 | luaSocketLib.sleep(millis / 1000) 15 | end, 16 | } 17 | 18 | -- inherit default implementation values 19 | for k, v in pairs(require('jls.lang.sys-')) do 20 | if m[k] == nil then 21 | m[k] = v 22 | end 23 | end 24 | 25 | return m 26 | -------------------------------------------------------------------------------- /tests/randomChars.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable-next-line: deprecated 2 | local table_unpack = table.unpack or _G.unpack 3 | 4 | local function randomChars(len, from, to) 5 | from = from or 0 6 | to = to or 255 7 | if len <= 10 then 8 | local bytes = {} 9 | for _ = 1, len do 10 | table.insert(bytes, math.random(from, to)) 11 | end 12 | return string.char(table_unpack(bytes)) 13 | end 14 | local parts = {} 15 | for _ = 1, len / 10 do 16 | table.insert(parts, randomChars(10, from, to)) 17 | end 18 | table.insert(parts, randomChars(len % 10, from, to)) 19 | return table.concat(parts) 20 | end 21 | 22 | return randomChars 23 | -------------------------------------------------------------------------------- /jls/util/XmlParser-XmlParser.lua: -------------------------------------------------------------------------------- 1 | local XmlParser = require('XmlParser') -- xml2lua parser 2 | 3 | local function errorHandler(errMsg, pos) 4 | error(string.format("%s [char=%d]\n", errMsg or "Parse Error", pos)) 5 | end 6 | 7 | local function starttag(handler, tag) 8 | handler:startElement(tag.name, tag.attrs) 9 | end 10 | 11 | local function endtag(handler, tag) 12 | handler:endElement(tag.name) 13 | end 14 | 15 | return { 16 | new = function(_, handler) 17 | handler.starttag = starttag 18 | handler.endtag = endtag 19 | return XmlParser.new(handler, { 20 | stripWS = true, -- Indicates if whitespaces should be striped or not 21 | expandEntities = false, 22 | errorHandler = errorHandler 23 | }) 24 | end 25 | } -------------------------------------------------------------------------------- /jls/lang/event.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.loader').requireOne('jls.lang.event-luv', 'jls.lang.event-') 2 | 3 | local noLoopCheck = os.getenv('JLS_EVENT_NO_LOOP_CHECK') 4 | if not noLoopCheck then 5 | local logger = require('jls.lang.logger') 6 | if logger:isLoggable(logger.INFO) then 7 | -- registering a global object to check if the event loop has been called and has processed all the events. 8 | JLS_EVENT_GLOBAL_OBJECT = setmetatable({}, { 9 | __gc = function() 10 | if event:loopAlive() then 11 | logger:info('event loop is alive, make sure you ran the event loop!') 12 | else 13 | logger:fine('event loop is not alive') 14 | end 15 | end 16 | }) 17 | end 18 | end 19 | 20 | return event 21 | -------------------------------------------------------------------------------- /jls/util/json-cjson.lua: -------------------------------------------------------------------------------- 1 | local jsonLib = require('cjson') 2 | 3 | --[[ 4 | On the first load, the cjson library determines the locale decimal point. 5 | If the locale decimal point changes after loading then the library will not be able to encode numbers. 6 | This issue can be quickly determined as json.encode(0.5) gives '0,5'. 7 | 8 | The cjson library encodes sparse Lua arrays as JSON arrays using JSON null for the missing entries. 9 | The cjson library accepts number keys and encodes them as string. 10 | ]] 11 | 12 | -- no sparse array 13 | jsonLib.encode_sparse_array(false, 1, 0) 14 | 15 | --jsonLib.encode_keep_buffer(false) 16 | 17 | return { 18 | decode = jsonLib.decode, 19 | encode = jsonLib.encode, 20 | null = jsonLib.null or jsonLib.util.null, 21 | } 22 | -------------------------------------------------------------------------------- /jls/net/http/filter/LogHttpFilter.lua: -------------------------------------------------------------------------------- 1 | --[[-- Provide a simple HTTP filter for logging. 2 | 3 | After adding this filter, any request containing the header *jls-logger-level* will increase the global log level. 4 | 5 | @module jls.net.http.filter.LogHttpFilter 6 | @pragma nostrip 7 | ]] 8 | 9 | local rootLogger = require('jls.lang.logger') 10 | 11 | --- A LogHttpFilter class. 12 | -- @type LogHttpFilter 13 | return require('jls.lang.class').create('jls.net.http.HttpFilter', function(filter) 14 | 15 | function filter:doFilter(exchange) 16 | local ll = exchange:getRequest():getHeader('jls-logger-level') 17 | if ll then 18 | local config = rootLogger:getConfig() 19 | rootLogger:setConfig(ll) 20 | exchange:onClose():next(function() 21 | rootLogger:setConfig(config) 22 | end) 23 | end 24 | end 25 | 26 | end) 27 | -------------------------------------------------------------------------------- /jls/lang/formatCommandLine.lua: -------------------------------------------------------------------------------- 1 | local isWindowsOS = string.sub(package.config, 1, 1) == '\\' 2 | 3 | return function(args) 4 | local pargs = {} 5 | for _, arg in ipairs(args) do 6 | local earg = arg 7 | if isWindowsOS then 8 | -- see https://ss64.com/nt/syntax-esc.html 9 | if string.find(arg, '[%s"&\\<>%^|%%]') and not string.match(arg, '^[<>|&]+$') then 10 | earg = '"'..string.gsub(arg, '"', '""')..'"' 11 | end 12 | else 13 | -- see https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html 14 | if string.find(arg, '[%s"~`#$&%*%(%)\\|%[%]{};\'"<>/%?!]') and not string.match(arg, '^[<>|&]+$') then 15 | earg = '"'..string.gsub(arg, '["\\]', '\\%&1')..'"' 16 | end 17 | end 18 | table.insert(pargs, earg) 19 | end 20 | if isWindowsOS then 21 | return '"'..table.concat(pargs, ' ')..'"' 22 | end 23 | return table.concat(pargs, ' ') 24 | end -------------------------------------------------------------------------------- /jls/util/XmlParser-lxp.lua: -------------------------------------------------------------------------------- 1 | local lxpLib = require('lxp') 2 | 3 | local strings = require('jls.util.strings') 4 | 5 | local function startElement(p, name, attrs) 6 | local handler = p:getcallbacks().handler 7 | handler:startElement(name, attrs) 8 | end 9 | 10 | local function endElement(p, name) 11 | local handler = p:getcallbacks().handler 12 | handler:endElement(name) 13 | end 14 | 15 | local function cdata(p, text) 16 | text = strings.strip(text) 17 | if text ~= '' then 18 | local handler = p:getcallbacks().handler 19 | handler:cdata(text) 20 | end 21 | end 22 | 23 | return { 24 | new = function(_, handler) 25 | -- see https://lunarmodules.github.io/luaexpat/manual.html 26 | -- lxp.new(callbacks [, separator[, merge_character_data]]) 27 | return lxpLib.new({ 28 | StartElement = startElement, 29 | EndElement = endElement, 30 | CharacterData = cdata, 31 | _nonstrict = true, 32 | handler = handler 33 | }, nil, true) 34 | end 35 | } -------------------------------------------------------------------------------- /jls/util/cd/gzip.lua: -------------------------------------------------------------------------------- 1 | local StreamHandler = require('jls.io.StreamHandler') 2 | local BufferedStreamHandler = require('jls.io.streams.BufferedStreamHandler') 3 | local gzip = require('jls.util.zip.gzip') 4 | 5 | return require('jls.lang.class').create('jls.util.Codec', function(codec) 6 | 7 | function codec:decode(value) 8 | local bufferedStream = BufferedStreamHandler:new() 9 | StreamHandler.fill(self:decodeStream(bufferedStream), value) 10 | return bufferedStream:getBuffer() 11 | end 12 | 13 | function codec:encode(value) 14 | local bufferedStream = BufferedStreamHandler:new() 15 | StreamHandler.fill(self:encodeStream(bufferedStream), value) 16 | return bufferedStream:getBuffer() 17 | end 18 | 19 | function codec:decodeStream(sh) 20 | return gzip.decompressStream(sh) 21 | end 22 | 23 | function codec:encodeStream(sh) 24 | return gzip.compressStream(sh) 25 | end 26 | 27 | function codec:getName() 28 | return 'gzip' 29 | end 30 | 31 | end) 32 | -------------------------------------------------------------------------------- /jls/lang/Thread-luv.lua: -------------------------------------------------------------------------------- 1 | local luvLib = require('luv') 2 | 3 | local class = require('jls.lang.class') 4 | local Promise = require('jls.lang.Promise') 5 | local ThreadBase = require('jls.lang.ThreadBase') 6 | 7 | return class.create(ThreadBase, function(thread) 8 | 9 | function thread:start(...) 10 | if self.t then 11 | return self 12 | end 13 | self._endPromise = Promise:new(function(resolve, reject) 14 | self._async = luvLib.new_async(function(status, value) 15 | ThreadBase._apply(resolve, reject, status, value) 16 | local async, t = self._async, self.t 17 | self._async = nil 18 | self.t = nil 19 | if async then 20 | async:close() 21 | end 22 | if t then 23 | t:join() 24 | end 25 | end) 26 | end) 27 | self.t = luvLib.new_thread(self:_arg(self._async, ...)) 28 | return self 29 | end 30 | 31 | end, function(Thread) 32 | 33 | function Thread._main(chunk, async, ...) 34 | async:send(ThreadBase._main(chunk, ...)) 35 | end 36 | 37 | end) -------------------------------------------------------------------------------- /jls/lang/signal.lua: -------------------------------------------------------------------------------- 1 | local function parseFlags(n) 2 | local flags, s = string.match(n, '^([%?!]*)(%w*)$') 3 | if not flags then 4 | error('invalid name') 5 | end 6 | return flags, s 7 | end 8 | 9 | local function hasFlag(flags, flag) 10 | return not not string.find(flags, flag, 1, true) 11 | end 12 | 13 | local hasLuv, luvLib = pcall(require, 'luv') 14 | if hasLuv then 15 | return function(n, cb) 16 | local flags, s = parseFlags(n) 17 | local signal = luvLib.new_signal() 18 | luvLib.ref(signal) 19 | if hasFlag(flags, '!') then 20 | luvLib.signal_start_oneshot(signal, s, cb) 21 | else 22 | luvLib.signal_start(signal, s, cb) 23 | end 24 | return function() 25 | luvLib.signal_stop(signal) 26 | luvLib.unref(signal) 27 | end 28 | end 29 | end 30 | 31 | local class = require('jls.lang.class') 32 | return function(n) 33 | local flags = parseFlags(n) 34 | if hasFlag(flags, '?') then 35 | return class.emptyFunction 36 | end 37 | error('not available') 38 | end -------------------------------------------------------------------------------- /jls/util/ShareableQueue.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | local Lock = require('jls.lang.Lock') 3 | 4 | return class.create('jls.util.Queue', function(queue) 5 | 6 | function queue:initialize(q) 7 | self.queue = q 8 | self.lock = Lock:new() 9 | end 10 | 11 | function queue:enqueue(data, id) 12 | local q, lock = self.queue, self.lock 13 | lock:lock() 14 | local status, result = pcall(q.enqueue, q, data, id) 15 | lock:unlock() 16 | if status then 17 | return result 18 | end 19 | error(result) 20 | end 21 | 22 | function queue:dequeue() 23 | local q, lock = self.queue, self.lock 24 | lock:lock() 25 | local status, data, id = pcall(q.dequeue, q) 26 | lock:unlock() 27 | if status then 28 | return data, id 29 | end 30 | error(data) 31 | end 32 | 33 | function queue:serialize(write) 34 | write(self.queue) 35 | write(self.lock) 36 | end 37 | 38 | function queue:deserialize(read) 39 | self.queue = read('jls.util.Queue') 40 | self.lock = read('jls.lang.Lock') 41 | end 42 | 43 | end) 44 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | This folder contains, command line base, single file, examples, use "--help" argument to get help. 2 | 3 | * A tiny web browser 4 | Allows open a web page with no tabs. 5 | * A cipher utility 6 | Allows to encode, decode a file. 7 | * A discover utility 8 | Allows to discover network services using mDNS or SSDP. 9 | * An HTTP client 10 | Allows to get or post requests to an HTTP server. 11 | * An HTTP proxy 12 | Could be used as an explicit proxy, supports connect. 13 | Allows filtering and HTTP traffic monitoring. 14 | * A MQTT client and server 15 | Allows to publish or subscribe to an MQTT server. 16 | * A WebSocket client 17 | Allows to send or receive message to a WebSocket server. 18 | * A web server 19 | Allows to serve static files. 20 | Supports file upload and WebDAV. 21 | * An ACME client 22 | Allows to get a certificate for a local HTTP server. 23 | * A WebView with documentation 24 | Allows to browse the luajls documentation, including Lua reference, LDoc, LuaUnit and LuaCov. 25 | * A ZIP utility 26 | Allows to create, extract or list ZIP file content. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2024 SPYL, javalikescript@free.fr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 4 | associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 6 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -z ${lua+x} 4 | then 5 | lua=lua 6 | if which lua5.4 >/dev/null 7 | then 8 | lua=lua5.4 9 | fi 10 | fi 11 | 12 | #LUA_CPATH=none 13 | 14 | #JLS_REQUIRES=\!luv 15 | #JLS_REQUIRES=\!lfs,\!cjson,\!socket,\!linux,\!luachild,\!winapi 16 | 17 | folders="base full" 18 | if test "$LUA_CPATH" = "none" 19 | then 20 | folders="base" 21 | fi 22 | 23 | $lua -v 24 | 25 | #lua="$lua -lluacov" 26 | 27 | ### To generate coverage stats then report 28 | ## lua -lluacov tests/base/class.lua 29 | ## lua ../luaclibs/luacov/src/bin/luacov jls 30 | 31 | errorcount=0 32 | testcount=0 33 | for d in $folders 34 | do 35 | for f in tests/$d/*.lua 36 | do 37 | echo testing $f ... 38 | $lua $f 1>/dev/null 2>/dev/null 39 | if test $? -ne 0 40 | then 41 | echo test $f in error: 42 | $lua $f 43 | errorcount=`expr $errorcount + 1` 44 | fi 45 | testcount=`expr $testcount + 1` 46 | done 47 | echo test dir $d completed $errorcount/$testcount files in error 48 | done 49 | if test $errorcount -ne 0 50 | then 51 | echo $errorcount/$testcount files in error 52 | else 53 | echo $testcount files pass 54 | fi 55 | rm .jls.*.tmp 56 | -------------------------------------------------------------------------------- /tests/base/CoroutineScheduler.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local CoroutineScheduler = require("jls.util.CoroutineScheduler") 4 | 5 | function Test_schedule() 6 | local v = 0 7 | local scheduler = CoroutineScheduler:new() 8 | scheduler:schedule(function() 9 | for i = 1, 3 do 10 | v = v + 1 11 | coroutine.yield(-1) 12 | end 13 | end, false) 14 | scheduler:run() 15 | lu.assertEquals(v, 3) 16 | end 17 | 18 | function Test_schedule_daemon() 19 | local v = 0 20 | local vd = 0 21 | local scheduler = CoroutineScheduler:new() 22 | scheduler:schedule(function() 23 | for i = 1, 3 do 24 | v = v + 1 25 | coroutine.yield(-1) 26 | end 27 | end, false) 28 | scheduler:schedule(function() 29 | while true do 30 | vd = vd + 1 31 | coroutine.yield(-1) 32 | end 33 | end, true) 34 | scheduler:run() 35 | lu.assertEquals(v, 3) 36 | lu.assertEquals(vd, 4) 37 | scheduler:run() 38 | lu.assertEquals(v, 3) 39 | lu.assertEquals(vd, 5) 40 | end 41 | 42 | -- TODO test resume/yield arguments 43 | -- TODO test wait/timeout schedule 44 | 45 | os.exit(lu.LuaUnit.run()) 46 | -------------------------------------------------------------------------------- /tests/full/UdpSocket.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local UdpSocket = require('jls.net.UdpSocket') 4 | local logger = require('jls.lang.logger') 5 | 6 | local loop = require('jls.lang.loopWithTimeout') 7 | 8 | function Test_UdpSocket() 9 | local host, port = '225.0.0.37', 12345 10 | local receivedData 11 | local receiver = UdpSocket:new() 12 | local sender = UdpSocket:new() 13 | receiver:bind('0.0.0.0', port, {reuseaddr = true}) 14 | receiver:joinGroup(host, '0.0.0.0') 15 | receiver:receiveStart(function(err, data) 16 | if err then 17 | logger:warn('receive error: "%s"', err) 18 | elseif data then 19 | logger:fine('received data: "%s"', data) 20 | receivedData = data 21 | end 22 | receiver:receiveStop() 23 | receiver:close() 24 | end) 25 | sender:send('Hello', host, port):finally(function(value) 26 | logger:warn('send value: "%s"', value) 27 | logger:fine('closing sender') 28 | sender:close() 29 | end) 30 | if not loop(function() 31 | sender:close() 32 | receiver:close() 33 | end) then 34 | lu.fail('Timeout reached') 35 | end 36 | lu.assertEquals(receivedData, 'Hello') 37 | end 38 | 39 | os.exit(lu.LuaUnit.run()) 40 | -------------------------------------------------------------------------------- /tests/base/shiftArguments.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local shiftArguments = require('jls.lang.shiftArguments') 4 | 5 | function Test_lua() 6 | lu.assertEquals(shiftArguments({'lua'}), {[-1]='lua'}) 7 | lu.assertEquals(shiftArguments({'lua', 'script.lua'}), {[-1]='lua', [0]='script.lua'}) 8 | lu.assertEquals(shiftArguments({'lua', 'script.lua', '-h'}), {[-1]='lua', [0]='script.lua', '-h'}) 9 | lu.assertEquals(shiftArguments({'lua', '-e', 'print("Hi")'}), {[-3]='lua', [-2]='-e', [-1]='print("Hi")'}) 10 | lu.assertEquals(shiftArguments({'lua', '-'}), {[-2]='lua', [-1]='-'}) 11 | lu.assertEquals(shiftArguments({'lua', '--', 'script.lua'}), {[-2]='lua', [-1]='--', [0]='script.lua'}) 12 | end 13 | 14 | function Test_bad() 15 | lu.assertEquals(shiftArguments({}), {}) 16 | lu.assertEquals(shiftArguments(), {}) 17 | end 18 | 19 | function Test_luvit() 20 | lu.assertEquals(shiftArguments({[0]='luvit'}, 1), {[-1]='luvit'}) 21 | lu.assertEquals(shiftArguments({[0]='luvit', 'script.lua'}, 1), {[-1]='luvit', [0]='script.lua'}) 22 | lu.assertEquals(shiftArguments({[0]='luvit', '-l', 'mod', 'script.lua'}, 1), {[-3]='luvit', [-2]='-l', [-1]='mod', [0]='script.lua'}) 23 | end 24 | 25 | os.exit(lu.LuaUnit.run()) 26 | -------------------------------------------------------------------------------- /jls/lang/luv_async.lua: -------------------------------------------------------------------------------- 1 | local ASYNC_MT = { 2 | __index = function(self, key) 3 | if not self._cleanmt and self._metatable and not debug.getmetatable(self._data) then 4 | self._cleanmt = true 5 | debug.setmetatable(self._data, self._metatable) 6 | end 7 | local value = self._data[key] 8 | if type(value) == 'function' then 9 | local fn = function(w, ...) 10 | return value(w._data, ...) 11 | end 12 | self[key] = fn 13 | return fn 14 | end 15 | return value 16 | end, 17 | __gc = function(self) 18 | if self._cleanmt then 19 | debug.setmetatable(self._data, nil) 20 | end 21 | end 22 | } 23 | -- luv removes the metatable from the userdata async arguments, to disable the GC. 24 | -- This function captures the metatable then restores it until GC. 25 | return function(value) 26 | if type(value) == 'userdata' then 27 | local mt = debug.getmetatable(value) 28 | if mt then 29 | return setmetatable({ 30 | _cleanmt = false, 31 | _metatable = mt, 32 | _data = value, 33 | close = function() 34 | error('cannot close async argument') 35 | end 36 | }, ASYNC_MT) 37 | end 38 | end 39 | return value 40 | end 41 | -------------------------------------------------------------------------------- /jls/lang/loopWithTimeout.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.event') 2 | 3 | --- Runs the event loop until there is no more registered event or the timeout occurs. 4 | -- This function allows to use asynchronous functions in a blocking way. 5 | -- When called with a function, the event loop is not stopped on timeout, 6 | -- it is the responsability of the function to close pending events. 7 | -- The function fails if the timeout has been reached. 8 | -- @function jls.lang.loopWithTimeout 9 | -- @tparam[opt] number timeout The timeout in milliseconds, default is 5000. 10 | -- @tparam[opt] function onTimeout A function that will be called when the timeout occurs. 11 | -- @treturn boolean false if the timeout has been reached. 12 | return function(timeout, onTimeout) 13 | local timer 14 | if type(timeout) == 'function' then 15 | onTimeout = timeout 16 | timeout = nil 17 | end 18 | timer = event:setTimeout(function() 19 | timer = nil 20 | if type(onTimeout) == 'function' and pcall(onTimeout) then 21 | return 22 | end 23 | event:stop() 24 | end, timeout or 5000) 25 | event:daemon(timer, true) 26 | event:loop() 27 | if timer then 28 | event:clearTimeout(timer) 29 | return true 30 | end 31 | return false 32 | end 33 | -------------------------------------------------------------------------------- /jls/lang/BufferView.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | 3 | return class.create('jls.lang.Buffer', function(buffer) 4 | 5 | function buffer:initialize(value, from, to) 6 | self.buffer = value 7 | self.offset = (from or 1) - 1 8 | self.size = (to or value:length()) - self.offset 9 | end 10 | 11 | function buffer:length() 12 | return self.size 13 | end 14 | 15 | function buffer:get(from, to) 16 | return self.buffer:get(self.offset + (from or 1), self.offset + (to or self.size)) 17 | end 18 | 19 | function buffer:set(value, offset, from, to) 20 | self.buffer:set(value, self.offset + (offset or 1), from, to) 21 | end 22 | 23 | function buffer:getBytes(from, to) 24 | return self.buffer:getBytes(self.offset + (from or 1), self.offset + (to or from or 1)) 25 | end 26 | 27 | function buffer:setBytes(at, ...) 28 | self.buffer:setBytes(self.offset + (at or 1), ...) 29 | end 30 | 31 | function buffer:serialize(write) 32 | write(self.buffer) 33 | write(self.offset) 34 | write(self.size) 35 | end 36 | 37 | function buffer:deserialize(read) 38 | self.buffer = read('jls.lang.Buffer') 39 | self.offset = read('number') 40 | self.size = read('number') 41 | end 42 | 43 | end) 44 | -------------------------------------------------------------------------------- /jls/io/fs-luv.lua: -------------------------------------------------------------------------------- 1 | 2 | local luvLib = require('luv') 3 | 4 | local NEW_DIR_MODE = tonumber('777', 8) 5 | 6 | -- https://docs.oracle.com/javase/7/docs/api/java/nio/file/attribute/BasicFileAttributes.html 7 | local function adaptStat(st) 8 | -- make a stat table compatible with lfs 9 | if st then 10 | --st.moden = st.mode -- not used 11 | st.mode = st.type 12 | if st.mtime then 13 | st.modification = st.mtime.sec 14 | end 15 | if st.atime then 16 | st.access = st.atime.sec 17 | end 18 | if st.ctime then 19 | st.change = st.ctime.sec 20 | end 21 | end 22 | return st 23 | end 24 | 25 | return { 26 | utime = function(path, atime, mtime) 27 | return luvLib.fs_utime(path, atime, mtime) 28 | end, 29 | stat = function(path) 30 | return adaptStat(luvLib.fs_stat(path)) 31 | end, 32 | currentdir = luvLib.cwd, 33 | mkdir = function(path) 34 | return luvLib.fs_mkdir(path, NEW_DIR_MODE) 35 | end, 36 | rmdir = luvLib.fs_rmdir, 37 | unlink = luvLib.fs_unlink, 38 | rename = luvLib.fs_rename, 39 | copyfile = luvLib.fs_copyfile, 40 | dir = function(path) 41 | local req = luvLib.fs_scandir(path) 42 | return function() 43 | return luvLib.fs_scandir_next(req) 44 | end 45 | end 46 | } 47 | -------------------------------------------------------------------------------- /jls/util/BlockingQueue.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | local logger = require('jls.lang.logger'):get(...) 3 | local system = require('jls.lang.system') 4 | 5 | return class.create('jls.util.Queue', function(queue) 6 | 7 | function queue:initialize(q, timeout) 8 | self.queue = q 9 | self.timeout = timeout or 15000 10 | end 11 | 12 | function queue:enqueue(data) 13 | local endTime, delay 14 | while true do 15 | if self.queue:enqueue(data) then 16 | return true 17 | end 18 | local t = system.currentTimeMillis() 19 | if endTime == nil then 20 | endTime = t + self.timeout 21 | delay = 100 22 | end 23 | if t >= endTime then 24 | break 25 | end 26 | logger:finer('no space on queue, sleeping %d ms', delay) 27 | system.sleep(delay) 28 | if delay < 3000 then 29 | delay = delay * 2 30 | end 31 | end 32 | return false 33 | end 34 | 35 | function queue:dequeue() 36 | return self.queue:dequeue() 37 | end 38 | 39 | function queue:serialize(write) 40 | write(self.queue) 41 | write(self.timeout) 42 | end 43 | 44 | function queue:deserialize(read) 45 | self.queue = read('jls.util.Queue') 46 | self.timeout = read('number') 47 | end 48 | 49 | end) 50 | -------------------------------------------------------------------------------- /jls/lang/shiftArguments.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | shift an argument table to be compatible with standalone lua or luvit 3 | Before running any code, lua collects all command-line arguments in a global table called arg. 4 | The script name goes to index 0, the first argument after the script name goes to index 1, and so on. 5 | When there is no script, the executable goes to index 0, Lua options goes to index 1, and so on. 6 | In order to pass arguments, a script has to be provided, it could be the null device `/dev/null` or `NUL`. 7 | ]] 8 | return function(args, init) 9 | if type(args) ~= 'table' then 10 | return {} 11 | end 12 | local scriptIndex = init or 2 13 | while true do 14 | local v = args[scriptIndex] 15 | if not v then 16 | break 17 | elseif v == '-' or v == '--' then -- stop handling options 18 | scriptIndex = scriptIndex + 1 19 | break 20 | elseif v == '-e' or v == '-l' then -- options with value 21 | scriptIndex = scriptIndex + 2 22 | elseif string.find(v, '^%-%w$') or string.find(v, '^%-%-[%w%-]+$') then -- options without value 23 | scriptIndex = scriptIndex + 1 24 | else 25 | break 26 | end 27 | end 28 | local sarg = {}; 29 | for i = #args, -99, -1 do 30 | local v = args[i] 31 | if not v then 32 | break 33 | end 34 | sarg[i - scriptIndex] = v; 35 | end 36 | return sarg 37 | end 38 | -------------------------------------------------------------------------------- /jls/lang/ProcessHandle-linux.lua: -------------------------------------------------------------------------------- 1 | local linuxLib = require('linux') 2 | local event = require('jls.lang.loader').requireOne('jls.lang.event-') 3 | local Promise = require('jls.lang.Promise') 4 | 5 | return require('jls.lang.class').create('jls.lang.ProcessHandleBase', function(processHandle) 6 | 7 | function processHandle:waitExitCode(timeoutMs) 8 | local id, kind, code = linuxLib.waitpid(self.pid, 0, timeoutMs or -1) 9 | if id then 10 | if kind == 'timeout' then 11 | return true 12 | elseif kind == 'exit' then 13 | elseif kind == 'signal' then 14 | code = code + 128 15 | end 16 | self.code = code 17 | end 18 | return false 19 | end 20 | 21 | function processHandle:isAlive() 22 | return self:waitExitCode(0) 23 | end 24 | 25 | function processHandle:destroy() 26 | return linuxLib.kill(self.pid, linuxLib.constants.SIGINT) 27 | end 28 | 29 | function processHandle:ended() 30 | if not self.endPromise then 31 | self.endPromise = Promise:new(function(resolve, reject) 32 | event:setTask(function(timeoutMs) 33 | if self:waitExitCode(timeoutMs) then 34 | return true 35 | end 36 | resolve(self.code) 37 | return false 38 | end, -1) 39 | end) 40 | end 41 | return self.endPromise 42 | end 43 | 44 | end, function(ProcessHandle) 45 | 46 | ProcessHandle.getCurrentPid = linuxLib.getpid 47 | 48 | end) 49 | -------------------------------------------------------------------------------- /tests/full/http_filters.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local SessionHttpFilter = require('jls.net.http.filter.SessionHttpFilter') 4 | local HttpExchange = require('jls.net.http.HttpExchange') 5 | 6 | local function getCookie(cookies, name) 7 | for _, cookie in ipairs(cookies) do 8 | local key, value = string.match(cookie, '^([^=]+)=([^;]*)') 9 | if key == name then 10 | return value 11 | end 12 | end 13 | end 14 | 15 | function Test_HttpSession() 16 | local filter = SessionHttpFilter:new() 17 | local exchange = HttpExchange:new() 18 | filter:doFilter(exchange) 19 | local session = exchange:getSession() 20 | lu.assertNotIsNil(session) 21 | local cookies = exchange:getResponse():getHeader('set-cookie') 22 | lu.assertNotIsNil(cookies) 23 | local sessionId = getCookie(cookies, filter.name) 24 | lu.assertNotIsNil(sessionId) 25 | lu.assertEquals(session:getId(), sessionId) 26 | exchange = HttpExchange:new() 27 | exchange:getRequest():setHeader('cookie', filter.name..'='..sessionId) 28 | lu.assertEquals(exchange:getRequest():getCookie(filter.name), sessionId) 29 | filter:doFilter(exchange) 30 | session = exchange:getSession() 31 | lu.assertNotIsNil(session) 32 | lu.assertEquals(session:getId(), sessionId) 33 | session:invalidate() 34 | filter:doFilter(exchange) 35 | session = exchange:getSession() 36 | lu.assertNotIsNil(session) 37 | lu.assertNotEquals(session:getId(), sessionId) 38 | end 39 | 40 | os.exit(lu.LuaUnit.run()) 41 | -------------------------------------------------------------------------------- /jls/io/Serial-.lua: -------------------------------------------------------------------------------- 1 | local serialLib = require('serial') 2 | local loader = require('jls.lang.loader') 3 | local logger = require('jls.lang.logger'):get(...) 4 | local event = loader.requireOne('jls.lang.event-') 5 | local StreamHandler = require('jls.io.StreamHandler') 6 | 7 | return require('jls.lang.class').create('jls.io.SerialBase', function(serial, super) 8 | 9 | function serial:initialize(...) 10 | super.initialize(self, ...) 11 | end 12 | 13 | function serial:readStart(stream) 14 | logger:finer('readStart()') 15 | if self.readTaskId then 16 | error('read already started') 17 | end 18 | local cb = StreamHandler.ensureCallback(stream) 19 | self.readTaskId = event:setTask(function(timeoutMs) 20 | local status, err = serialLib.waitDataAvailable(self.fileDesc.fd, timeoutMs) 21 | if status then 22 | serial:readAvailable(cb) 23 | return true 24 | end 25 | if err == 'timeout' then 26 | return true 27 | end 28 | if err == 'close' then 29 | cb() 30 | else 31 | logger:fine('Error while waiting serial data %s', err) 32 | cb(err or 'Error while waiting serial data') 33 | end 34 | self.readTaskId = nil 35 | return false 36 | end, -1) 37 | end 38 | 39 | function serial:readStop() 40 | logger:finer('readStop()') 41 | if self.readTaskId then 42 | event:clearInterval(self.readTaskId) 43 | self.readTaskId = nil 44 | end 45 | end 46 | 47 | end) 48 | -------------------------------------------------------------------------------- /tests/full/SerialWorker.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local logger = require('jls.lang.logger') 4 | local SerialWorker = require('jls.util.SerialWorker') 5 | local loop = require('jls.lang.loopWithTimeout') 6 | 7 | function Test_default() 8 | local responses = {} 9 | local sw = SerialWorker:new() 10 | sw:call(function(d) 11 | return 'Hi '..tostring(d) 12 | end, 'John'):next(function(d) 13 | table.insert(responses, d or '') 14 | end) 15 | sw:call(function(d) 16 | return 'Hello '..tostring(d) 17 | end, 'Mary'):next(function(d) 18 | table.insert(responses, d or '') 19 | end):finally(function() 20 | sw:close() 21 | end) 22 | logger:info('looping') 23 | if not loop(function() 24 | sw:close() 25 | end) then 26 | lu.fail('Timeout reached') 27 | end 28 | lu.assertEquals(responses, {'Hi John', 'Hello Mary'}) 29 | end 30 | 31 | function Test_reject() 32 | local rejects = {} 33 | local sw = SerialWorker:new() 34 | sw:call(function(d) 35 | return nil, 'Sorry '..tostring(d) 36 | end, 'John'):catch(function(d) 37 | table.insert(rejects, d or '') 38 | end) 39 | sw:call(function(d) 40 | error('Ouch '..tostring(d)) 41 | end, 'Mary'):catch(function(d) 42 | table.insert(rejects, d or '') 43 | end):finally(function() 44 | sw:close() 45 | end) 46 | logger:info('looping') 47 | if not loop(function() 48 | sw:close() 49 | end) then 50 | lu.fail('Timeout reached') 51 | end 52 | lu.assertEquals(#rejects, 2) 53 | lu.assertEquals(rejects[1], 'Sorry John') 54 | lu.assertNotNil(string.match(rejects[2], 'Ouch Mary')) 55 | end 56 | 57 | os.exit(lu.LuaUnit.run()) 58 | -------------------------------------------------------------------------------- /jls/lang/Buffer-buffer.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | 3 | local bufferLib = require('buffer') 4 | assert(type(bufferLib.toreference) == 'function', 'bad buffer lib version '..tostring(bufferLib._VERSION)) 5 | 6 | return class.create('jls.lang.Buffer', function(buffer) 7 | 8 | function buffer:initialize(value, size) 9 | self.buffer = value 10 | self.size = size 11 | end 12 | 13 | function buffer:length() 14 | return self.size 15 | end 16 | 17 | function buffer:get(from, to) 18 | return bufferLib.sub(self.buffer, from, to or self.size) 19 | end 20 | 21 | function buffer:set(value, offset, from, to) 22 | offset = offset or 1 23 | from = from or 1 24 | to = to or #value 25 | assert(offset > 0 and from > 0 and to > 0 and to <= #value, 'invalid argument') 26 | local l = 1 + to - from 27 | assert(offset - 1 + l <= self.size, 'not enough space') 28 | if l > 0 then 29 | bufferLib.memcpy(self.buffer, value, offset, from, to) 30 | end 31 | end 32 | 33 | function buffer:getBytes(from, to) 34 | return bufferLib.byte(self.buffer, from, to) 35 | end 36 | 37 | function buffer:setBytes(at, ...) 38 | bufferLib.byteset(self.buffer, at, ...) 39 | end 40 | 41 | function buffer:serialize(write) 42 | write(bufferLib.toreference(self.buffer, nil, 'jls.lang.Buffer')) 43 | end 44 | 45 | function buffer:deserialize(read) 46 | self.buffer, self.size = bufferLib.fromreference(read('string'), nil, 'jls.lang.Buffer') 47 | end 48 | 49 | end, function(Buffer) 50 | 51 | function Buffer.allocate(size) 52 | return Buffer:new(bufferLib.new(size), size) 53 | end 54 | 55 | end) -------------------------------------------------------------------------------- /examples/wsClient.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.event') 2 | local system = require('jls.lang.system') 3 | local tables = require('jls.util.tables') 4 | local WebSocket = require('jls.net.http.WebSocket') 5 | 6 | local options = tables.createArgumentTable(system.getArguments(), { 7 | helpPath = 'help', 8 | emptyPath = 'url', 9 | logPath = 'log-level', 10 | aliases = { 11 | h = 'help', 12 | ll = 'log-level', 13 | }, 14 | schema = { 15 | title = 'Open a WebSocket', 16 | type = 'object', 17 | additionalProperties = false, 18 | required = {'url'}, 19 | properties = { 20 | help = { 21 | title = 'Show the help', 22 | type = 'boolean', 23 | default = false 24 | }, 25 | url = { 26 | title = 'The WebSocket URL', 27 | type = 'string', 28 | pattern = '^wss?://.+$', 29 | }, 30 | read = { 31 | title = 'Read and print incoming text messages', 32 | type = 'boolean', 33 | default = true 34 | }, 35 | message = { 36 | title = 'The message to send', 37 | type = 'string' 38 | }, 39 | } 40 | } 41 | }) 42 | 43 | local webSocket = WebSocket:new(options.url) 44 | webSocket:open():next(function() 45 | print('opened') 46 | function webSocket:onTextMessage(payload) 47 | print(payload) 48 | end 49 | if options.message then 50 | print('message sent') 51 | webSocket:sendTextMessage(options.message) 52 | end 53 | if options.read then 54 | webSocket:readStart() 55 | else 56 | webSocket:close() 57 | end 58 | end, function(reason) 59 | print('cannot open', reason) 60 | end) 61 | 62 | event:loop() 63 | -------------------------------------------------------------------------------- /jls/net/http/Attributes.lua: -------------------------------------------------------------------------------- 1 | --- A class that holds attributes as key-value pairs. 2 | -- The attributes are stored in the table field "attributes". 3 | -- @module jls.net.http.Attributes 4 | -- @pragma nostrip 5 | 6 | --- A class that holds attributes. 7 | -- @type Attributes 8 | return require('jls.lang.class').create(function(attributes) 9 | 10 | --- Creates a new Attributes. 11 | -- @function Attributes:new 12 | function attributes:initialize() 13 | self.attributes = {} 14 | end 15 | 16 | --- Sets the specified value for the specified name. 17 | -- @tparam string name the attribute name 18 | -- @param value the attribute value 19 | function attributes:setAttribute(name, value) 20 | self.attributes[name] = value 21 | return self 22 | end 23 | 24 | --- Returns the value for the specified name. 25 | -- @tparam string name the attribute name 26 | -- @return the attribute value 27 | function attributes:getAttribute(name) 28 | return self.attributes[name] 29 | end 30 | 31 | --- Removes the value for the specified name. 32 | -- @tparam string name the attribute name 33 | -- @return the attribute value 34 | function attributes:removeAttribute(name) 35 | self.attributes[name] = nil 36 | return self 37 | end 38 | 39 | function attributes:getAttributes() 40 | return self.attributes 41 | end 42 | 43 | function attributes:setAttributes(attrs) 44 | for name, value in pairs(attrs) do 45 | self:setAttribute(name, value) 46 | end 47 | return self 48 | end 49 | 50 | function attributes:cleanAttributes() 51 | self.attributes = {} 52 | end 53 | 54 | end) -------------------------------------------------------------------------------- /tests/full/RingBuffer.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local RingBuffer = require('jls.util.RingBuffer') 4 | local Buffer = require('jls.lang.Buffer') 5 | local serialization = require('jls.lang.serialization') 6 | 7 | local function newRingBuffer(size) 8 | return RingBuffer:new(Buffer.allocate(size)) 9 | end 10 | 11 | function Test_enqueue() 12 | local queue = newRingBuffer(64) 13 | queue:enqueue('a') 14 | lu.assertEquals(queue:dequeue(), 'a') 15 | queue:enqueue('b') 16 | queue:enqueue('c') 17 | lu.assertEquals(queue:dequeue(), 'b') 18 | lu.assertEquals(queue:dequeue(), 'c') 19 | end 20 | 21 | function Test_enqueue_ring() 22 | local nextSize = string.packsize('I4I4') 23 | local headerSize = string.packsize('I1I2') 24 | local queue = newRingBuffer(nextSize + 20 + headerSize * 3) 25 | local data10 = '1234567890' 26 | local data = '1234' 27 | lu.assertTrue(queue:enqueue(data)) 28 | lu.assertTrue(queue:enqueue(data10)) 29 | lu.assertFalse(queue:enqueue(data10)) 30 | lu.assertEquals(queue:dequeue(), data) 31 | lu.assertTrue(queue:enqueue(data10)) 32 | lu.assertEquals(queue:dequeue(), data10) 33 | lu.assertEquals(queue:dequeue(), data10) 34 | end 35 | 36 | function Test_enqueue_ring_multiple() 37 | local queue = newRingBuffer(64) 38 | local data = 'Hello !' 39 | queue:enqueue(data) 40 | for i = 1, 100 do 41 | queue:enqueue(data) 42 | lu.assertEquals(queue:dequeue(), data) 43 | end 44 | lu.assertEquals(queue:dequeue(), data) 45 | end 46 | 47 | function Test_serialization() 48 | local queue = newRingBuffer(64) 49 | queue:enqueue('a') 50 | local q = serialization.deserialize(serialization.serialize(queue)) 51 | lu.assertEquals(q:dequeue(), 'a') 52 | end 53 | 54 | os.exit(lu.LuaUnit.run()) 55 | -------------------------------------------------------------------------------- /tests/full/event.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local event = require('jls.lang.event') 4 | 5 | function Test_setTimeout() 6 | local called = false 7 | event:setTimeout(function() 8 | called = true 9 | end, 100) 10 | event:loop() 11 | lu.assertEquals(called, true) 12 | end 13 | 14 | function Test_setTimeout_order() 15 | local value = 1 16 | event:setTimeout(function() 17 | value = value * 2 18 | end, 300) 19 | event:setTimeout(function() 20 | value = value + 1 21 | end, 100) 22 | event:loop() 23 | lu.assertEquals(value, 4) 24 | end 25 | 26 | function Test_setTimeout_no_delay() 27 | local value = 1 28 | event:setTimeout(function() 29 | value = value * 2 30 | end, 300) 31 | event:setTimeout(function() 32 | value = value + 1 33 | end) 34 | event:setTimeout(function() 35 | value = value + 1 36 | end) 37 | event:loop() 38 | lu.assertEquals(value, 6) 39 | end 40 | 41 | function Test_setInterval() 42 | local count = 0 43 | local eventId = event:setInterval(function() 44 | count = count + 1 45 | end, 100) 46 | event:setTimeout(function() 47 | event:clearInterval(eventId) 48 | end, 500) 49 | event:loop() 50 | -- this test will fail on pure Lua depending on time resolution 51 | lu.assertAlmostEquals(count, 5, 2) 52 | end 53 | 54 | function Test_setTask() 55 | if event ~= package.loaded['jls.lang.event-'] then 56 | print('/!\\ skipping setTask test') 57 | lu.success() 58 | end 59 | local count = 0 60 | event:setTask(function() 61 | count = count + 1 62 | if count >= 4 then 63 | return false 64 | end 65 | return true 66 | end, 100) 67 | event:loop() 68 | lu.assertEquals(count, 4) 69 | end 70 | 71 | os.exit(lu.LuaUnit.run()) 72 | -------------------------------------------------------------------------------- /jls/lang/BufferFile.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | 3 | return class.create('jls.lang.Buffer', function(buffer) 4 | 5 | function buffer:initialize(size, name, preserve) 6 | self.size = size 7 | self.name = name or string.format('.%s-%p.tmp', 'jls.lang.Buffer', self) 8 | self.file = assert(io.open(self.name, preserve and 'r+b' or 'w+b')) 9 | if not preserve then 10 | self.file:write(string.rep('\0', self.size)) 11 | self.file:flush() 12 | self.initialized = true 13 | end 14 | end 15 | 16 | function buffer:finalize() 17 | if self.initialized then 18 | self.initialized = nil 19 | self.file:close() 20 | os.remove(self.name) 21 | end 22 | end 23 | 24 | function buffer:length() 25 | return self.size 26 | end 27 | 28 | function buffer:get(from, to) 29 | self.file:seek('set') 30 | local s = self.file:read(self.size) 31 | if from then 32 | return string.sub(s, from, to or #s) 33 | end 34 | return s 35 | end 36 | 37 | function buffer:set(value, offset, from, to) 38 | self.file:seek('set', (offset or 1) - 1) 39 | if from then 40 | self.file:write(string.sub(value, from, to)) 41 | else 42 | self.file:write(value) 43 | end 44 | self.file:flush() 45 | end 46 | 47 | function buffer:serialize(write) 48 | write(self.size) 49 | write(self.name) 50 | end 51 | 52 | function buffer:deserialize(read) 53 | local size, name = read('number'), read('string') 54 | assert(string.match(name, '^%.[%w%.]+-%x+%.tmp$'), 'invalid name') 55 | self:initialize(size, name, true) 56 | end 57 | 58 | end, function(Buffer) 59 | 60 | function Buffer.allocate(size) 61 | return Buffer:new(size) 62 | end 63 | 64 | end) -------------------------------------------------------------------------------- /tests/full/Channel.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local loader = require('jls.lang.loader') 4 | local event = require('jls.lang.event') 5 | local loop = require('jls.lang.loopWithTimeout') 6 | local Channel = require('jls.util.Channel') 7 | 8 | local function test_channel(scheme) 9 | local receivedMessage 10 | local channelServer = Channel:new() 11 | local channel = Channel:new() 12 | local acceptedChannel 13 | local function closeAll(reason) 14 | print('Close all '..(reason or '')) 15 | channelServer:close(false) 16 | channel:close(false) 17 | if acceptedChannel then 18 | acceptedChannel:close(false) 19 | end 20 | if reason then 21 | lu.fail(reason) 22 | end 23 | end 24 | channelServer:acceptAndClose():next(function(c) 25 | acceptedChannel = c 26 | acceptedChannel:receiveStart(function(message) 27 | receivedMessage = message 28 | acceptedChannel:receiveStop() 29 | acceptedChannel:close(false) 30 | end) 31 | end) 32 | channelServer:bind(false, scheme):next(function() 33 | local name = channelServer:getName() 34 | return channel:connect(name) 35 | end):next(function() 36 | channel:writeMessage('Hello') 37 | end):catch(function(r) 38 | closeAll('Listen or connect error: '..tostring(r)) 39 | end) 40 | if not loop(closeAll) then 41 | lu.fail('Timeout reached') 42 | end 43 | lu.assertEquals(receivedMessage, 'Hello') 44 | end 45 | 46 | function Test_channel_pipe() 47 | if event ~= loader.getRequired('jls.lang.event-luv') then 48 | print('/!\\ skipping pipe test') 49 | lu.success() 50 | end 51 | test_channel('pipe') 52 | end 53 | 54 | function Test_channel_tcp() 55 | test_channel('tcp') 56 | end 57 | 58 | os.exit(lu.LuaUnit.run()) 59 | -------------------------------------------------------------------------------- /jls/lang/Lock-buffer.lua: -------------------------------------------------------------------------------- 1 | --- Provides mutual exclusion for the threads in the current process. 2 | -- @module jls.lang.Lock 3 | -- @pragma nostrip 4 | 5 | local class = require('jls.lang.class') 6 | 7 | local bufferLib = require('buffer') 8 | assert(type(bufferLib.initmutex) == 'function', 'bad buffer lib version '..tostring(bufferLib._VERSION)) 9 | 10 | --- The Lock class. 11 | -- @type Lock 12 | return class.create(function(lock) 13 | 14 | --- Creates a new Lock. 15 | -- @function Lock:new 16 | function lock:initialize() 17 | self.mutex = bufferLib.newmutex() 18 | bufferLib.initmutex(self.mutex) 19 | self.initialized = true 20 | end 21 | 22 | function lock:finalize() 23 | -- type(self.mutex) == 'userdata' and bufferLib.len(self.mutex) == bufferLib.MUTEX_SIZE 24 | if self.initialized then 25 | self.initialized = false 26 | bufferLib.destroymutex(self.mutex) 27 | end 28 | end 29 | 30 | --- Acquires the lock, blocking if necessary. 31 | function lock:lock() 32 | bufferLib.lock(self.mutex) 33 | end 34 | 35 | --- Releases the lock. 36 | function lock:unlock() 37 | bufferLib.unlock(self.mutex) 38 | end 39 | 40 | --- Returns true if the lock has been acquired without blocking. 41 | -- @treturn boolean true if the lock has been acquired 42 | function lock:tryLock() 43 | return bufferLib.trylock(self.mutex) 44 | end 45 | 46 | function lock:serialize(write) 47 | write(bufferLib.toreference(self.mutex, nil, 'jls.lang.Lock')) 48 | end 49 | 50 | function lock:deserialize(read) 51 | local m = bufferLib.fromreference(read('string'), nil, 'jls.lang.Lock') 52 | if type(m) ~= 'userdata' then 53 | error('invalid serialization value') 54 | end 55 | self.mutex = m 56 | end 57 | 58 | end) -------------------------------------------------------------------------------- /tests/full/Pipe.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local loader = require('jls.lang.loader') 4 | local event = require('jls.lang.event') 5 | local loop = require('jls.lang.loopWithTimeout') 6 | local logger = require('jls.lang.logger') 7 | local Pipe = loader.tryRequire('jls.io.Pipe') 8 | 9 | function Test_default() 10 | if event ~= loader.getRequired('jls.lang.event-luv') then 11 | print('/!\\ skipping default test') 12 | lu.success() 13 | return 14 | end 15 | local received 16 | local pipeName = 'pipe.test' 17 | pipeName = Pipe.normalizePipeName(pipeName) 18 | local p = Pipe:new() 19 | function p:onAccept(pb) 20 | local status, err = pb:readStart(function(err, data) 21 | logger:fine('pb:read "%s", "%s"', err, data) 22 | if data then 23 | pb:write('Hi '..tostring(data)) 24 | received = data 25 | else 26 | pb:close() 27 | p:close() 28 | end 29 | end) 30 | logger:fine('pb:readStart() => %s, %s', status, err) 31 | end 32 | p:bind(pipeName):next(function() 33 | local pc = Pipe:new() 34 | logger:fine('client connect(%s)', pipeName) 35 | pc:connect(pipeName):next(function() 36 | logger:fine('client connected') 37 | local status, err = pc:readStart(function(err, data) 38 | logger:fine('pc:read "%s", "%s"', err, data) 39 | pc:close() 40 | end) 41 | logger:fine('pc:readStart() => %s, %s', status, err) 42 | pc:write('John') 43 | end):catch(function(err) 44 | logger:warn('client pipe error %s', err) 45 | pc:close() 46 | end) 47 | end) 48 | logger:fine('looping') 49 | if not loop(function() 50 | p:close() 51 | end) then 52 | lu.fail('Timeout reached') 53 | end 54 | lu.assertEquals(received, 'John') 55 | end 56 | 57 | os.exit(lu.LuaUnit.run()) 58 | -------------------------------------------------------------------------------- /jls/util/Queue.lua: -------------------------------------------------------------------------------- 1 | --- Represents a list of data in first-in first-out order. 2 | -- @module jls.util.Queue 3 | -- @pragma nostrip 4 | 5 | local class = require('jls.lang.class') 6 | 7 | --- The Queue class. 8 | -- @type Queue 9 | return class.create(function(queue) 10 | 11 | --- Adds the specified data to the queue. 12 | -- @tparam string data The data to add at the end of the queue 13 | -- @treturn boolean true if the data has been added 14 | -- @function queue:enqueue 15 | queue.enqueue = class.notImplementedFunction 16 | 17 | --- Removes and returns the first data of the queue. 18 | -- @treturn string The first data or nil if there is nothing in the queue 19 | -- @function queue:dequeue 20 | queue.dequeue = class.notImplementedFunction 21 | 22 | end, function(Queue) 23 | 24 | --- Returns a queue that is thread safe. 25 | -- @tparam jls.util.Queue queue The queue 26 | -- @treturn jls.util.Queue the thread safe queue 27 | function Queue.share(queue) 28 | local ShareableQueue = require('jls.util.ShareableQueue') 29 | if ShareableQueue:isInstance(queue) then 30 | return queue 31 | end 32 | return ShareableQueue:new(queue) 33 | end 34 | 35 | --- Returns a queue that blocks until data could be queued. 36 | -- @tparam jls.util.Queue queue The queue 37 | -- @tparam number timeout The max duration in milliseconds to wait for enqueue 38 | -- @treturn jls.util.Queue the blocking queue 39 | function Queue.block(queue, timeout) 40 | return require('jls.util.BlockingQueue'):new(queue, timeout) 41 | end 42 | 43 | --- Returns a queue using a circular buffer. 44 | -- @tparam jls.lang.Buffer buffer The buffer 45 | -- @treturn jls.util.Queue the ring buffer 46 | function Queue.ringBuffer(buffer) 47 | return require('jls.util.RingBuffer'):new(buffer) 48 | end 49 | 50 | end) 51 | -------------------------------------------------------------------------------- /jls/io/streams/BufferedStreamHandler.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple buffered stream handler. 2 | -- @module jls.io.streams.BufferedStreamHandler 3 | -- @pragma nostrip 4 | 5 | local logger = require('jls.lang.logger'):get(...) 6 | local StringBuffer = require('jls.lang.StringBuffer') 7 | local StreamHandler = require('jls.io.StreamHandler') 8 | 9 | --- A BufferedStreamHandler class. 10 | -- This class allows to buffer the stream to pass to the wrapped handler. 11 | -- @type BufferedStreamHandler 12 | return require('jls.lang.class').create(StreamHandler.WrappedStreamHandler, function(bufferedStreamHandler, super) 13 | 14 | --- Creates a buffered @{StreamHandler}. 15 | -- The data will be pass to the wrapped handler once. 16 | -- @tparam[opt] StreamHandler handler the handler to wrap 17 | -- @function BufferedStreamHandler:new 18 | function bufferedStreamHandler:initialize(handler) 19 | logger:finest('initialize()') 20 | super.initialize(self, handler) 21 | self.buffer = StringBuffer:new() 22 | end 23 | 24 | function bufferedStreamHandler:getStringBuffer() 25 | return self.buffer 26 | end 27 | 28 | function bufferedStreamHandler:getBuffer() 29 | if self.err then 30 | return nil, self.err 31 | end 32 | return self.buffer:toString() 33 | end 34 | 35 | function bufferedStreamHandler:onData(data) 36 | logger:finer('onData(#%l)', data) 37 | if data then 38 | self.buffer:append(data) 39 | else 40 | if self.buffer:length() > 0 then 41 | return StreamHandler.fill(self.handler, self.buffer:toString()) 42 | end 43 | return self.handler:onData(nil) 44 | end 45 | end 46 | 47 | function bufferedStreamHandler:onError(err) 48 | logger:fine('buffered stream handler on error due to %s', err) 49 | if not self.err then 50 | self.err = err 51 | end 52 | self.handler:onError(err) 53 | end 54 | 55 | end) 56 | -------------------------------------------------------------------------------- /jls/io/Serial-luv.lua: -------------------------------------------------------------------------------- 1 | local luvLib = require('luv') 2 | local logger = require('jls.lang.logger'):get(...) 3 | local StreamHandler = require('jls.io.StreamHandler') 4 | 5 | return require('jls.lang.class').create('jls.io.SerialBase', function(serial) 6 | 7 | function serial:readStart(stream) 8 | logger:finer('readStart()') 9 | if self.waitThread then 10 | error('read already started') 11 | end 12 | self.streamCallback = StreamHandler.ensureCallback(stream) 13 | local errAsync = luvLib.new_async(function(err) 14 | if err == 'close' then 15 | self.streamCallback() 16 | else 17 | logger:fine('Error while waiting serial data %s', err) 18 | self.streamCallback(err or 'unknown error') 19 | end 20 | end) 21 | local waitAsync = luvLib.new_async(function() 22 | self:readAvailable(self.streamCallback) 23 | end) 24 | self.waitThread = luvLib.new_thread(function(fd, async, asyncErr, path, cpath) 25 | package.path = path 26 | package.cpath = cpath 27 | local serialLib = require('serial') 28 | local luv = require('luv') 29 | while true do 30 | local status, err = serialLib.waitDataAvailable(fd, 5000) -- will block 31 | if not status then 32 | if err ~= 'timeout' then 33 | asyncErr:send(err or 'unknown error') 34 | break 35 | end 36 | else 37 | async:send() 38 | luv.sleep(1) -- let async event be handled 39 | end 40 | end 41 | async:close() 42 | asyncErr:close() 43 | end, self.fileDesc.fd, waitAsync, errAsync, package.path, package.cpath) 44 | end 45 | 46 | function serial:readStop() 47 | logger:finer('readStop()') 48 | if self.waitThread then 49 | self.waitThread:join() 50 | self.waitThread = nil 51 | self.streamCallback = nil 52 | end 53 | end 54 | 55 | end) 56 | -------------------------------------------------------------------------------- /tests/base/ast.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local ast = require('jls.util.ast') 4 | 5 | function Test_parse_generate() 6 | lu.assertEquals(ast.generate(ast.parse("local a = 2 // 2")), "local a=2//2;") 7 | end 8 | 9 | function Test_parseExpression_generate() 10 | lu.assertEquals(ast.generate(ast.parseExpression("2 // 2")), "2//2") 11 | end 12 | 13 | function Test_parse_generate_51() 14 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = 1 // 2"), ast.toLua51)), "local a=compat.fdiv(1,2);") 15 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = 1 ~ 2"), ast.toLua51)), "local a=compat.bxor(1,2);") 16 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = 1 & 2"), ast.toLua51)), "local a=compat.band(1,2);") 17 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = 1 | 2"), ast.toLua51)), "local a=compat.bor(1,2);") 18 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = ~2"), ast.toLua51)), "local a=compat.bnot(2);") 19 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = 1 & (2 ~ 3)"), ast.toLua51)), "local a=compat.band(1,compat.bxor(2,3));") 20 | lu.assertEquals(ast.generate(ast.traverse(ast.parse("local a = _ENV.b"), ast.toLua51)), "local a=b;") 21 | end 22 | 23 | function Test_traverse() 24 | local tree = ast.parse("local a = 2 // 2") 25 | ast.traverse(tree, function(astNode) 26 | if astNode.type == 'binary' and astNode.operator == '//' then 27 | return { 28 | type = 'call', 29 | callee = { 30 | type = 'lookup', 31 | object = { type = 'identifier', name = 'math' }, 32 | member = { type = 'literal', value = 'floor' }, 33 | }, 34 | arguments = { { type = 'binary', operator = '/', left = astNode.left, right = astNode.right } }, 35 | } 36 | end 37 | end) 38 | lu.assertEquals(ast.generate(tree), "local a=math.floor(2/2);") 39 | end 40 | 41 | os.exit(lu.LuaUnit.run()) 42 | -------------------------------------------------------------------------------- /jls/io/streams/PromiseStreamHandler.lua: -------------------------------------------------------------------------------- 1 | -- This class provides a promise that resolves once the stream is closed. 2 | -- @module jls.io.streams.PromiseStreamHandler 3 | -- @pragma nostrip 4 | 5 | local Promise = require('jls.lang.Promise') 6 | local StreamHandler = require('jls.io.StreamHandler') 7 | 8 | -- A PromiseStreamHandler class. 9 | -- This class provides a promise that resolves once the stream ended. 10 | -- @type PromiseStreamHandler 11 | return require('jls.lang.class').create(StreamHandler.WrappedStreamHandler, function(promiseStreamHandler, super) 12 | 13 | -- Creates a @{StreamHandler} with a promise. 14 | -- @tparam[opt] StreamHandler handler the handler to wrap 15 | -- @function PromiseStreamHandler:new 16 | function promiseStreamHandler:initialize(handler) 17 | super.initialize(self, handler) 18 | self:reset() 19 | end 20 | 21 | function promiseStreamHandler:reset() 22 | self.promise, self.promiseCallback = Promise.withCallback() 23 | self.size = 0 24 | end 25 | 26 | -- Returns a Promise that resolves once the stream ends. 27 | -- @treturn jls.lang.Promise a promise that resolves once the stream ends. 28 | function promiseStreamHandler:getPromise() 29 | return self.promise 30 | end 31 | 32 | function promiseStreamHandler:onData(data) 33 | local result = self.handler:onData(data) 34 | if data then 35 | self.size = self.size + #data 36 | else 37 | if Promise:isInstance(result) then 38 | result:next(function() 39 | self.promiseCallback(nil, self.size) 40 | end) 41 | else 42 | self.promiseCallback(nil, self.size) 43 | end 44 | end 45 | return result 46 | end 47 | 48 | function promiseStreamHandler:onError(err) 49 | self.handler:onError(err) 50 | self.promiseCallback(err or 'Error') 51 | end 52 | 53 | function promiseStreamHandler:close() 54 | self.promiseCallback('Closed') -- if not already resolved 55 | end 56 | 57 | end) 58 | 59 | -------------------------------------------------------------------------------- /tests/full/mqtt.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local logger = require('jls.lang.logger') 4 | local loop = require('jls.lang.loopWithTimeout') 5 | local mqtt = require('jls.net.mqtt') 6 | 7 | function Test_pubsub() 8 | local hostname, port = 'localhost', 0 9 | local topicName, payload = 'test', 'Hello world!' 10 | local topicNameReceived, payloadReceived 11 | 12 | local mqttServer = mqtt.MqttServer:new() 13 | local mqttClientSub = mqtt.MqttClient:new() 14 | function mqttClientSub:onMessage(tn, pl) 15 | logger:info('mqttClientSub:onMessage(%s)', tn) 16 | topicNameReceived = tn 17 | payloadReceived = pl 18 | self:close() 19 | mqttServer:close() 20 | end 21 | local mqttClientPub = mqtt.MqttClient:new() 22 | logger:info('mqttServer:bind()') 23 | mqttServer:bind(nil, port):next(function() 24 | if port == 0 then 25 | port = select(2, mqttServer:getAddress()) 26 | logger:info('mqttServer bound on %s', port) 27 | end 28 | logger:info('mqttClientSub:connect()') 29 | return mqttClientSub:connect(hostname, port) 30 | end):next(function() 31 | logger:info('mqttClientSub:subscribe()') 32 | mqttClientSub:subscribe(topicName, 0) 33 | logger:info('mqttClientPub:connect()') 34 | return mqttClientPub:connect(hostname, port) 35 | end):next(function() 36 | logger:info('mqttClientPub:publish()') 37 | return mqttClientPub:publish(topicName, payload) 38 | end):next(function() 39 | logger:info('mqttClientPub:close()') 40 | mqttClientPub:close() 41 | end):catch(function(reason) 42 | logger:warn('something goes wrong %s', reason) 43 | mqttServer:close() 44 | mqttClientPub:close() 45 | mqttClientSub:close() 46 | end) 47 | if not loop(function() 48 | mqttServer:close() 49 | mqttClientPub:close() 50 | mqttClientSub:close() 51 | end) then 52 | lu.fail('Timeout reached') 53 | end 54 | lu.assertEquals(payloadReceived, payload) 55 | end 56 | 57 | os.exit(lu.LuaUnit.run()) 58 | -------------------------------------------------------------------------------- /jls/lang/Thread-llthreads.lua: -------------------------------------------------------------------------------- 1 | local llthreadsLib = require('llthreads') 2 | 3 | local loader = require('jls.lang.loader') 4 | local event = loader.requireOne('jls.lang.event-') 5 | local logger = require('jls.lang.logger'):get(...) 6 | 7 | local class = require('jls.lang.class') 8 | local Promise = require('jls.lang.Promise') 9 | local ThreadBase = require('jls.lang.ThreadBase') 10 | 11 | return class.create(ThreadBase, function(thread, super) 12 | 13 | function thread:initialize(fn) 14 | super.initialize(self, fn) 15 | self.daemon = false 16 | end 17 | 18 | function thread:start(...) 19 | if self.t then 20 | return self 21 | end 22 | logger:finer('start()') 23 | self.t = llthreadsLib.new(self:_arg(...)) 24 | self.t:start(self.daemon, true) 25 | return self 26 | end 27 | 28 | function thread:ended() 29 | if self.t then 30 | if not self._endPromise then 31 | self._endPromise = Promise:new(function(resolve, reject) 32 | local t = self.t 33 | event:setTask(function() 34 | if t:alive() then 35 | logger:finer('not ended') 36 | return true 37 | end 38 | logger:finer('ended') 39 | local ok, status, value = t:join() 40 | self._endPromise = nil 41 | if not ok then 42 | status, value = false, 'unable to join thread properly' 43 | end 44 | ThreadBase._apply(resolve, reject, status, value) 45 | self.t = nil 46 | return false 47 | end) 48 | end) 49 | end 50 | return self._endPromise 51 | end 52 | return Promise.reject() 53 | end 54 | 55 | function thread:isAlive() 56 | if self.t then 57 | local alive, err = self.t:alive() 58 | if alive then 59 | return true 60 | elseif err then 61 | logger:warn('alive fails due to %s', err) 62 | end 63 | end 64 | return false 65 | end 66 | 67 | end) -------------------------------------------------------------------------------- /tests/full/thread_memory.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Thread = require('jls.lang.Thread') 4 | local Buffer = require('jls.lang.Buffer') 5 | local Lock = require('jls.lang.Lock') 6 | local system = require('jls.lang.system') 7 | local loop = require('jls.lang.loopWithTimeout') 8 | 9 | -- Indicate to a polling thread that it must terminate 10 | function Test_thread_buffer() 11 | local buffer = Buffer.allocate(1, 'global') 12 | buffer:setBytes(1, 0) 13 | lu.assertEquals(buffer:getBytes(), 0) 14 | local result = nil 15 | Thread:new(function(buf) 16 | local sys = require('jls.lang.system') 17 | local n = 0 18 | while true do 19 | local v = buf:getBytes() 20 | n = n + 1 21 | if v ~= 0 then 22 | return n 23 | end 24 | sys.sleep(50) 25 | end 26 | end):start(buffer):ended():next(function(res) 27 | result = res 28 | end) 29 | lu.assertNil(result) 30 | system.sleep(200) 31 | buffer:setBytes(1, 1) 32 | if not loop() then 33 | lu.fail('Timeout reached') 34 | end 35 | lu.assertNotNil(result) 36 | lu.assertTrue(result > 0) 37 | end 38 | 39 | function Test_thread_lock() 40 | local lock = Lock:new() 41 | lock:lock() 42 | local result = nil 43 | Thread:new(function(lck) 44 | local sys = require('jls.lang.system') 45 | local tr = lck:tryLock() 46 | lck:lock() 47 | sys.sleep(200) 48 | lck:unlock() 49 | return string.format('tryLock=%s', tr) 50 | end):start(lock):ended():next(function(res) 51 | result = res 52 | end) 53 | lu.assertNil(result) 54 | local start = system.currentTimeMillis() 55 | system.sleep(200) 56 | lock:unlock() 57 | system.sleep(50) 58 | lock:lock() 59 | lock:unlock() 60 | local ms = system.currentTimeMillis() - start 61 | if not loop() then 62 | lu.fail('Timeout reached') 63 | end 64 | lock:finalize() 65 | lu.assertNotNil(result) 66 | lu.assertEquals(result, 'tryLock=false') 67 | lu.assertTrue(ms >= 400) -- may fail 68 | end 69 | 70 | os.exit(lu.LuaUnit.run()) 71 | -------------------------------------------------------------------------------- /tests/full/tar.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local tar = require('jls.util.zip.tar') 4 | local File = require('jls.io.File') 5 | local Codec = require('jls.util.Codec') 6 | local StreamHandler = require('jls.io.StreamHandler') 7 | local base64 = Codec.getInstance('base64') 8 | 9 | local TEST_PATH = 'tests/full' 10 | local TMP_PATH = TEST_PATH..'/tmp' 11 | local TMP_DIR = File:new(TMP_PATH) 12 | 13 | -- echo -n Hi>a.txt; echo "Hello World !">b.txt; tar -czf - a.txt b.txt | base64 14 | local SAMPLE_TAR_GZ = base64:decode('H4sIAAAAAAAAA+3TMQrCQBCF4a09xXgB2TGb7BVyA+sVUwRWAskGPL5JsBBExCJZhf9rBmameM0Lh3RLZl12Ujk3T/WlfZ4PR6PFtCkL7+Y/VWfViF0512IcUuhFzBCuYxPf/326/6m6zZ0AOZ1/of9avfbf0/8t1E2MnZy6Pl5kv8udBgAAAAAAAAAAAAAAAN+6A+eyywsAKAAA') 15 | 16 | Tests = {} 17 | 18 | function Tests:setUp() 19 | if TMP_DIR:isDirectory() then 20 | if not TMP_DIR:deleteAll() then 21 | error('Cannot delete tmp dir') 22 | end 23 | else 24 | if not TMP_DIR:mkdir() then 25 | error('Cannot create tmp dir') 26 | end 27 | end 28 | end 29 | 30 | function Tests:tearDown() 31 | if not TMP_DIR:deleteRecursive() then 32 | error('Cannot delete tmp dir') 33 | end 34 | end 35 | 36 | local function verifyExtraction(invert) 37 | local aFile = File:new(TMP_DIR, 'a.txt') 38 | if invert then 39 | lu.assertFalse(aFile:isFile()) 40 | return 41 | end 42 | local bFile = File:new(TMP_DIR, 'b.txt') 43 | lu.assertTrue(aFile:isFile()) 44 | lu.assertTrue(bFile:isFile()) 45 | lu.assertEquals(aFile:readAll(), 'Hi') 46 | lu.assertEquals(bFile:readAll(), 'Hello World !\n') 47 | end 48 | 49 | function Tests:test_extractStreamTo() 50 | verifyExtraction(true) 51 | local sh = tar.extractStreamTo(TMP_DIR, true) 52 | StreamHandler.fill(sh, SAMPLE_TAR_GZ) 53 | verifyExtraction() 54 | end 55 | 56 | function Tests:test_extractFileTo() 57 | local tarFile = File:new(TMP_DIR, 'test.tar.gz') 58 | tarFile:write(SAMPLE_TAR_GZ) 59 | verifyExtraction(true) 60 | tar.extractFileTo(tarFile, TMP_DIR) 61 | verifyExtraction() 62 | end 63 | 64 | os.exit(lu.LuaUnit.run()) 65 | -------------------------------------------------------------------------------- /jls/lang/luv_stream.lua: -------------------------------------------------------------------------------- 1 | --local luvLib = require('luv') 2 | local logger = require('jls.lang.logger'):get(...) 3 | local Promise = require('jls.lang.Promise') 4 | local StreamHandler = require('jls.io.StreamHandler') 5 | 6 | return { 7 | close = function(stream, callback) 8 | logger:finest('close(%s)', stream) 9 | local cb, d = Promise.ensureCallback(callback) 10 | if stream and not stream:is_closing() then 11 | stream:close(cb) 12 | elseif cb then 13 | cb() 14 | end 15 | return d 16 | end, 17 | read_start = function(stream, callback) 18 | logger:finest('read_start(%s)', stream) 19 | local cb = StreamHandler.ensureCallback(callback) 20 | local status, err 21 | if stream then 22 | status, err = stream:read_start(cb) 23 | else 24 | err = 'stream not available' 25 | end 26 | if not status then 27 | cb(err or 'unknown error') 28 | end 29 | logger:finer('read_start() => %s, %s', status, err) 30 | return status, err 31 | end, 32 | read_stop = function(stream) 33 | logger:finest('read_stop(%s)', stream) 34 | local status, err 35 | if stream then 36 | status, err = stream:read_stop() 37 | else 38 | err = 'stream not available' 39 | end 40 | logger:finer('read_stop() => %s, %s', status, err) 41 | return status, err 42 | end, 43 | write = function(stream, data, callback) 44 | logger:finest('write(%s, %l)', stream, data) 45 | local cb, d = Promise.ensureCallback(callback) 46 | local req, err 47 | if stream then 48 | --if stream:is_closing() then 49 | -- logger:warn('write(%s) is closing', stream) 50 | --end 51 | -- write returns a cancelable request 52 | req, err = stream:write(data, cb) 53 | else 54 | err = 'stream not available' 55 | end 56 | if not req then 57 | if cb then 58 | cb(err or 'unknown error') 59 | else 60 | logger:warn('write(%s) fail %s', stream, err) 61 | end 62 | end 63 | -- TODO invert request and error? 64 | return d, req, err 65 | end, 66 | } 67 | -------------------------------------------------------------------------------- /jls/lang/ProcessHandle-win32.lua: -------------------------------------------------------------------------------- 1 | local win32Lib = require('win32') 2 | local event = require('jls.lang.loader').requireOne('jls.lang.event-') 3 | local logger = require('jls.lang.logger'):get(...) 4 | local Promise = require('jls.lang.Promise') 5 | 6 | return require('jls.lang.class').create('jls.lang.ProcessHandleBase', function(processHandle) 7 | 8 | function processHandle:isAlive() 9 | local code = win32Lib.GetExitCodeProcess(self.pid) 10 | if code then 11 | if code == win32Lib.constants.STILL_ACTIVE then 12 | return true 13 | end 14 | self.code = code 15 | end 16 | return false 17 | end 18 | 19 | function processHandle:destroy() 20 | return win32Lib.TerminateProcessId(self.pid) 21 | end 22 | 23 | function processHandle:ended() 24 | if not self.endPromise then 25 | self.endPromise = Promise:new(function(resolve, reject) 26 | event:setTask(function(timeoutMs) 27 | logger:fine('waiting process id %d with timeout %s', self.pid, timeoutMs) 28 | local status, code = win32Lib.WaitProcessId(self.pid, timeoutMs, true) 29 | logger:fine('wait ended for process id %d => %s, %s', self.pid, status, code) 30 | if status == win32Lib.constants.WAIT_TIMEOUT then 31 | return true 32 | elseif status == win32Lib.constants.WAIT_OBJECT_0 then 33 | self.code = code 34 | resolve(code) 35 | elseif status == win32Lib.constants.WAIT_ABANDONED then 36 | -- the mutex object that was not released before the owning thread terminated 37 | reject('abandoned') 38 | elseif status == win32Lib.constants.WAIT_FAILED then 39 | reject('unable to wait process due to '..(win32Lib.GetMessageFromSystem() or 'n/a')) 40 | else 41 | reject('unexpected return status '..tostring(status)) 42 | end 43 | return false 44 | end, -1) 45 | end) 46 | end 47 | return self.endPromise 48 | end 49 | 50 | end, function(ProcessHandle) 51 | 52 | ProcessHandle.getCurrentPid = win32Lib.GetCurrentProcessId 53 | 54 | end) 55 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | -- See https://stevedonovan.github.io/ldoc/manual/doc.md.html 2 | file = {'jls', exclude = { 3 | 'jls/io/streams', 4 | 'jls/net/http/filter', 5 | 'jls/net/http/handler', 6 | 'jls/net/http/Hpack.lua', 7 | 'jls/util/CoroutineScheduler.lua', 8 | 'jls/util/Struct.lua', 9 | }} 10 | project = "luajls" 11 | title = "Lua JLS 0.8" 12 | description = "luajls documentation" 13 | full_description = [[ 14 | The luajls library provides abstraction interfaces for general-purpose scripting. 15 | 16 | ## Contents 17 | 18 | * Introduction 19 | * @{manual.md.Audience|Audience} 20 | * @{manual.md.Overview|Overview} 21 | * @{manual.md.General_Considerations|General Considerations} 22 | * Basic Concepts 23 | * @{manual.md.Namespace_and_Modules|Namespace and Modules} 24 | * @{manual.md.Object_Oriented_Programming|Object Oriented Programming} 25 | * @{manual.md.Exception|Exception} 26 | * @{manual.md.Modules_and_Resources|Modules and Resources} 27 | * @{manual.md.Logging|Logging} 28 | * @{manual.md.Concurrent_Programming|Concurrent Programming} 29 | * Data Storage and Transformation 30 | * @{manual.md.File_System|File System} 31 | * @{manual.md.Data_Transformation|Data Transformation} 32 | * Network Programming 33 | * @{manual.md.Network_Socket|Network Socket} 34 | * @{manual.md.Hypertext_Transfer_Protocol__HTTP_|Hypertext Transfer Protocol (HTTP)} 35 | * Process and Thread 36 | * @{manual.md.Process|Process} 37 | * @{manual.md.Thread|Thread} 38 | * @{manual.md.Inter_Process_Communication|Inter-Process Communication} 39 | * Utilities 40 | * @{manual.md.Basic_Classes|Basic Classes} 41 | * @{manual.md.Data_Exchange_Formats|Data Exchange Formats} 42 | * @{manual.md.User_Interface|User Interface} 43 | 44 | ]] 45 | --no_return_or_parms = true 46 | --no_summary = true 47 | --no_space_before_args = true 48 | --sort = true 49 | style = "!fixed" 50 | --no_lua_ref = true 51 | topics = "doc_topics" 52 | package = "jls" 53 | format = "markdown" 54 | custom_tags = { 55 | {'diagnostic', title=''}, 56 | {'experimental', title='Experimental Section'}, 57 | } -------------------------------------------------------------------------------- /tests/base/FileDescriptor.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local FileDescriptor = require('jls.io.FileDescriptor') 4 | local Path = require('jls.io.Path') 5 | 6 | local TMP_FILENAME = Path.cleanPath('tests/test_fd.tmp') 7 | 8 | local function createFile(path, content) 9 | local file = assert(io.open(path, 'wb')) 10 | file:write(content) -- TODO check for errors 11 | file:close() 12 | end 13 | 14 | local function assertFileContent(path, expectedContent) 15 | local file = assert(io.open(path, 'rb')) 16 | lu.assertNotIsNil(file) 17 | local fileContent = file:read('*a') -- TODO check for errors 18 | file:close() 19 | lu.assertEquals(fileContent, expectedContent) 20 | end 21 | 22 | function Test_readSync_no_file() 23 | -- delete tmp file 24 | os.remove(TMP_FILENAME) 25 | 26 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'r') 27 | if fd then 28 | fd:closeSync() -- just in case 29 | end 30 | lu.assertIsNil(fd) 31 | end 32 | 33 | function Test_readSync() 34 | -- prepare tmp file with some content 35 | createFile(TMP_FILENAME, '12345678901234567890Some Content') 36 | 37 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'r') 38 | local content 39 | content = fd:readSync(20) 40 | lu.assertEquals(content, '12345678901234567890') 41 | content = fd:readSync(2048) 42 | lu.assertEquals(content, 'Some Content') 43 | content = fd:readSync(2048) 44 | lu.assertEquals(content, nil) 45 | 46 | content = fd:readSync(2048, 20) 47 | lu.assertEquals(content, 'Some Content') 48 | content = fd:readSync(20, 0) 49 | lu.assertEquals(content, '12345678901234567890') 50 | fd:closeSync() 51 | end 52 | 53 | function Test_writeSync() 54 | local err 55 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'w') 56 | _, err = fd:writeSync('12345678901234567890') 57 | lu.assertIsNil(err) 58 | _, err = fd:writeSync('Some Content') 59 | lu.assertIsNil(err) 60 | fd:flushSync() 61 | fd:closeSync() 62 | 63 | assertFileContent(TMP_FILENAME, '12345678901234567890Some Content') 64 | end 65 | 66 | function Test_z_cleanup() 67 | -- delete tmp file 68 | os.remove(TMP_FILENAME) 69 | end 70 | 71 | os.exit(lu.LuaUnit.run()) 72 | -------------------------------------------------------------------------------- /jls/util/zip/Deflater.lua: -------------------------------------------------------------------------------- 1 | --- Provide compression using the ZLIB library. 2 | -- @module jls.util.zip.Deflater 3 | -- @pragma nostrip 4 | 5 | local zLib = require('zlib') 6 | 7 | --- The Deflater class. 8 | -- A Deflater allows to compress data. 9 | -- @type Deflater 10 | return require('jls.lang.class').create(function(deflater) 11 | 12 | --[[ 13 | If no compression_level is provided uses Z_DEFAULT_COMPRESSION (6), 14 | compression level is a number from 1-9 where zlib.BEST_SPEED is 1 and zlib.BEST_COMPRESSION is 9. 15 | windowBits Default is 15, MAX_WBITS 16 | ]] 17 | 18 | --- Creates a new Deflater with the specified compression level and window bits. 19 | -- @function Deflater:new 20 | -- @tparam number compressionLevel the compression level from 1-9, from `BEST_SPEED` to `BEST_COMPRESSION` 21 | -- @tparam number windowBits the window bits 22 | function deflater:initialize(compressionLevel, windowBits) 23 | self:reset(compressionLevel, windowBits) 24 | end 25 | 26 | function deflater:reset(compressionLevel, windowBits) 27 | self.stream = zLib.deflate(compressionLevel, windowBits) 28 | self.eof = false 29 | self.bytesIn = 0 30 | self.bytesOut = 0 31 | end 32 | 33 | --- Deflates the specified data. 34 | -- @tparam string buffer the data to deflate 35 | -- @tparam string flush the flush mode: sync, full or finish 36 | -- @return the deflated data 37 | function deflater:deflate(buffer, flush) 38 | -- nil, sync, full, finish 39 | local deflated 40 | deflated, self.eof, self.bytesIn, self.bytesOut = self.stream(buffer, flush) 41 | return deflated 42 | end 43 | 44 | function deflater:flushSync(buffer) 45 | return self:deflate(buffer, 'sync') 46 | end 47 | 48 | function deflater:flushFull(buffer) 49 | return self:deflate(buffer, 'full') 50 | end 51 | 52 | function deflater:finish(buffer) 53 | return self:deflate(buffer, 'finish') 54 | end 55 | 56 | function deflater:getBytesRead() 57 | return self.bytesIn 58 | end 59 | 60 | function deflater:getBytesWritten() 61 | return self.bytesOut 62 | end 63 | 64 | function deflater:finished() 65 | return self.eof 66 | end 67 | 68 | end) 69 | -------------------------------------------------------------------------------- /jls/net/http/handler/ResourceHttpHandler.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple HTTP handler for resources. 2 | -- @module jls.net.http.handler.ResourceHttpHandler 3 | -- @pragma nostrip 4 | 5 | local loader = require('jls.lang.loader') 6 | local HTTP_CONST = require('jls.net.http.HttpMessage').CONST 7 | local HttpExchange = require('jls.net.http.HttpExchange') 8 | local FileHttpHandler = require('jls.net.http.handler.FileHttpHandler') 9 | local Date = require('jls.util.Date') 10 | 11 | --- A ResourceHttpHandler class. 12 | -- @type ResourceHttpHandler 13 | return require('jls.lang.class').create('jls.net.http.HttpHandler', function(resourceHttpHandler) 14 | 15 | --- Creates a resource @{HttpHandler}. 16 | -- @function ResourceHttpHandler:new 17 | function resourceHttpHandler:initialize(prefix, filename) 18 | self.prefix = prefix or '' 19 | self.defaultFile = filename or 'index.html' 20 | self.date = Date:new() 21 | end 22 | 23 | function resourceHttpHandler:handle(exchange) 24 | if not HttpExchange.methodAllowed(exchange, {HTTP_CONST.METHOD_GET, HTTP_CONST.METHOD_HEAD}) then 25 | return 26 | end 27 | local response = exchange:getResponse() 28 | local path = exchange:getRequestPath() 29 | if path == '' or string.sub(path, -1) == '/' then 30 | path = path..self.defaultFile 31 | end 32 | local resource = loader.loadResource(self.prefix..path, true) 33 | if resource then 34 | response:setStatusCode(HTTP_CONST.HTTP_OK, 'OK') 35 | response:setContentType(FileHttpHandler.guessContentType(path)) 36 | response:setCacheControl(true) 37 | response:setLastModified(self.date) 38 | response:setContentLength(#resource) 39 | if exchange:getRequestMethod() == HTTP_CONST.METHOD_GET then 40 | local request = exchange:getRequest() 41 | local ifModifiedSince = request:getIfModifiedSince() 42 | if ifModifiedSince and self.date:getTime() <= ifModifiedSince then 43 | response:setStatusCode(HTTP_CONST.HTTP_NOT_MODIFIED, 'Not modified') 44 | return 45 | end 46 | response:setBody(resource) 47 | end 48 | else 49 | HttpExchange.notFound(exchange) 50 | end 51 | end 52 | 53 | end) 54 | -------------------------------------------------------------------------------- /jls/util/zip/Inflater.lua: -------------------------------------------------------------------------------- 1 | --- Provide decompression using the ZLIB library. 2 | -- @module jls.util.zip.Inflater 3 | -- @pragma nostrip 4 | 5 | local zLib = require('zlib') 6 | 7 | --- The Inflater class. 8 | -- A Inflater allows to decompress data. 9 | -- @type Inflater 10 | return require('jls.lang.class').create(function(inflater) 11 | 12 | --[[ 13 | The windowBits parameter is the base two logarithm of the maximum window size. 14 | windowBits can also be -8..-15 for raw inflate. 15 | In this case, -windowBits determines the window size. 16 | inflate() will then process raw deflate data, not looking for a zlib or gzip header, 17 | not generating a check value, and not looking for any check values for comparison at the end of the stream. 18 | 19 | windowBits can also be greater than 15 for optional gzip decoding. 20 | Add 32 to windowBits to enable zlib and gzip decoding with automatic headerdetection, 21 | or add 16 to decode only the gzip format (the zlib format will return a Z_DATA_ERROR). 22 | If a gzip stream is being decoded, strm->adler is a CRC-32 instead of an Adler-32. 23 | 24 | By default, we will do gzip header detection w/ max window size */ 25 | 26 | Default is 15+32, MAX_WBITS+32 27 | ]] 28 | --- Creates a new Inflater with the specified window bits. 29 | -- @function Inflater:new 30 | -- @tparam[opt] number windowBits the window bits 31 | function inflater:initialize(windowBits) 32 | self:reset(windowBits) 33 | end 34 | 35 | function inflater:reset(windowBits) 36 | self.stream = zLib.inflate(windowBits) 37 | self.eof = false 38 | self.bytesIn = 0 39 | self.bytesOut = 0 40 | end 41 | 42 | --- Inflates the specified data. 43 | -- @tparam string buffer the data to inflate 44 | -- @return the inflated data 45 | function inflater:inflate(buffer) 46 | local inflated 47 | inflated, self.eof, self.bytesIn, self.bytesOut = self.stream(buffer) 48 | return inflated 49 | end 50 | 51 | function inflater:getBytesRead() 52 | return self.bytesIn 53 | end 54 | 55 | function inflater:getBytesWritten() 56 | return self.bytesOut 57 | end 58 | 59 | function inflater:finished() 60 | return self.eof 61 | end 62 | 63 | end) 64 | -------------------------------------------------------------------------------- /jls/util/cd/deflate.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | local WrappedStreamHandler = require('jls.io.StreamHandler').WrappedStreamHandler 3 | local Deflater = require('jls.util.zip.Deflater') 4 | local Inflater = require('jls.util.zip.Inflater') 5 | 6 | local DecodeStreamHandler = class.create(WrappedStreamHandler, function(decodeStreamHandler, super) 7 | function decodeStreamHandler:initialize(handler, inflater) 8 | super.initialize(self, handler) 9 | self.inflater = inflater 10 | end 11 | function decodeStreamHandler:onData(data) 12 | if data then 13 | local status, inflated = pcall(self.inflater.inflate, self.inflater, data) 14 | if status then 15 | return self.handler:onData(inflated) 16 | end 17 | self.handler:onError(inflated or 'unknown') 18 | else 19 | return self.handler:onData() 20 | end 21 | end 22 | end) 23 | 24 | local EncodeStreamHandler = class.create(WrappedStreamHandler, function(encodeStreamHandler, super) 25 | function encodeStreamHandler:initialize(handler, deflater) 26 | super.initialize(self, handler) 27 | self.deflater = deflater 28 | end 29 | function encodeStreamHandler:onData(data) 30 | if data then 31 | return self.handler:onData(self.deflater:deflate(data)) 32 | end 33 | self.handler:onData(self.deflater:finish(data)) 34 | self.handler:onData() 35 | end 36 | end) 37 | 38 | return require('jls.lang.class').create('jls.util.Codec', function(deflate) 39 | 40 | function deflate:initialize(windowBits, compressionLevel) 41 | self.windowBits = windowBits 42 | self.compressionLevel = compressionLevel 43 | end 44 | 45 | function deflate:decode(value) 46 | return Inflater:new(self.windowBits):inflate(value) 47 | end 48 | 49 | function deflate:encode(value) 50 | return Deflater:new(self.compressionLevel, self.windowBits):deflate(value, 'finish') 51 | end 52 | 53 | function deflate:decodeStream(sh) 54 | return DecodeStreamHandler:new(sh, Inflater:new(self.windowBits)) 55 | end 56 | 57 | function deflate:encodeStream(sh) 58 | return EncodeStreamHandler:new(sh, Deflater:new(self.compressionLevel, self.windowBits)) 59 | end 60 | 61 | function deflate:getName() 62 | return 'deflate' 63 | end 64 | 65 | end) 66 | -------------------------------------------------------------------------------- /jls/net/http/filter/PathHttpFilter.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple HTTP path filter. 2 | -- @module jls.net.http.filter.PathHttpFilter 3 | -- @pragma nostrip 4 | 5 | local strings = require('jls.util.strings') 6 | 7 | --- A PathHttpFilter class. 8 | -- @type PathHttpFilter 9 | return require('jls.lang.class').create('jls.net.http.HttpFilter', function(pathFilter) 10 | 11 | --- Creates a @{HttpFilter} by path. 12 | -- This filter allows to restrict a filter to a set of allowed or excluded path patterns. 13 | -- @tparam HttpFilter filter the filter to apply depending on the allowed/excluded patterns. 14 | -- @tparam[opt] table patterns a list of allowed patterns. 15 | -- @tparam[opt] table excludedPatterns a list of excluded patterns. 16 | -- @function PathHttpFilter:new 17 | function pathFilter:initialize(filter, patterns, excludedPatterns) 18 | self.filter = filter 19 | self.patterns = patterns or {} 20 | self.excludedPatterns = excludedPatterns or {} 21 | end 22 | 23 | local function addPatterns(list, escape, ...) 24 | for _, pattern in ipairs({...}) do 25 | if escape then 26 | pattern = '^'..strings.escape(pattern)..'$' 27 | end 28 | table.insert(list, pattern) 29 | end 30 | end 31 | 32 | function pathFilter:allow(...) 33 | addPatterns(self.patterns, false, ...) 34 | return self 35 | end 36 | 37 | function pathFilter:allowPath(...) 38 | addPatterns(self.patterns, true, ...) 39 | return self 40 | end 41 | 42 | function pathFilter:exclude(...) 43 | addPatterns(self.excludedPatterns, false, ...) 44 | return self 45 | end 46 | 47 | function pathFilter:excludePath(...) 48 | addPatterns(self.excludedPatterns, true, ...) 49 | return self 50 | end 51 | 52 | function pathFilter:doFilter(exchange) 53 | local request = exchange:getRequest() 54 | local path = request:getTargetPath() 55 | for _, pattern in ipairs(self.excludedPatterns) do 56 | if string.match(path, pattern) then 57 | return 58 | end 59 | end 60 | if #self.patterns == 0 then 61 | return self.filter:doFilter(exchange) 62 | end 63 | for _, pattern in ipairs(self.patterns) do 64 | if string.match(path, pattern) then 65 | return self.filter:doFilter(exchange) 66 | end 67 | end 68 | end 69 | 70 | end) 71 | -------------------------------------------------------------------------------- /tests/full/gzip.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local gzip = require('jls.util.zip.gzip') 4 | local StreamHandler = require('jls.io.StreamHandler') 5 | local BufferedStreamHandler = require('jls.io.streams.BufferedStreamHandler') 6 | local Codec = require('jls.util.Codec') 7 | local base64 = Codec.getInstance('base64') 8 | 9 | local SAMPLE_PLAIN = 'Hello world !' 10 | 11 | -- echo -n "Hello world !" | gzip | base64 12 | local SAMPLE_GZIPPED = base64:decode('H4sIAAAAAAAAA/NIzcnJVyjPL8pJUVAEAEAsDgcNAAAA') 13 | 14 | local function compress_decompress(data, header) 15 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 16 | local resultHeader 17 | local stream = gzip.compressStream(gzip.decompressStream(bufferedStream, function(header) 18 | resultHeader = header 19 | end), header) 20 | StreamHandler.fill(stream, data) 21 | return bufferedStream:getBuffer(), resultHeader 22 | end 23 | 24 | function Test_decompress() 25 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 26 | local stream = gzip.decompressStream(bufferedStream) 27 | StreamHandler.fill(stream, SAMPLE_GZIPPED) 28 | local result = bufferedStream:getBuffer() 29 | lu.assertEquals(result, SAMPLE_PLAIN) 30 | end 31 | 32 | function Test_compress() 33 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 34 | local stream = gzip.compressStream(bufferedStream) 35 | StreamHandler.fill(stream, SAMPLE_PLAIN) 36 | local result = bufferedStream:getBuffer() 37 | lu.assertEquals(base64:encode(result), base64:encode(SAMPLE_GZIPPED)) 38 | end 39 | 40 | function Test_compress_decompress() 41 | local data = 'test' 42 | lu.assertEquals(compress_decompress(data), data) 43 | end 44 | 45 | function Test_compress_decompress_with_header() 46 | local data = [[ 47 | I find it hard to believe you don't know 48 | The beauty that you are 49 | But if you don't let me be your eyes 50 | A hand in your darkness, so you won't be afraid 51 | ]] 52 | local header = { 53 | name = 'test name', 54 | comment = 'comment for test', 55 | modificationTime = 830908800, 56 | os = 11 57 | } 58 | local resultData, resultHeader = compress_decompress(data, header) 59 | lu.assertEquals(resultData, data) 60 | lu.assertEquals(resultHeader, header) 61 | end 62 | 63 | os.exit(lu.LuaUnit.run()) 64 | -------------------------------------------------------------------------------- /tests/full/ZipFile.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local File = require('jls.io.File') 4 | local ZipFile = require('jls.util.zip.ZipFile') 5 | local logger = require('jls.lang.logger') 6 | 7 | local TEST_PATH = 'tests/full' 8 | local TMP_PATH = TEST_PATH..'/tmp' 9 | local TMP_DIR = File:new(TMP_PATH) 10 | 11 | local function setUpTmpDir() 12 | if TMP_DIR:isDirectory() then 13 | if not TMP_DIR:deleteAll() then 14 | error('Cannot delete tmp dir') 15 | end 16 | else 17 | if not TMP_DIR:mkdir() then 18 | error('Cannot create tmp dir') 19 | end 20 | end 21 | end 22 | 23 | Tests = {} 24 | 25 | function Tests:tearDown() 26 | if not TMP_DIR:deleteRecursive() then 27 | error('Cannot delete tmp dir') 28 | end 29 | end 30 | 31 | function Tests:test_exists() 32 | setUpTmpDir() 33 | 34 | logger:info('create directories and files') 35 | local z = File:new(TMP_DIR, 'z') 36 | local a = File:new(TMP_DIR, 'a') 37 | a:write('Test a') 38 | local b = File:new(TMP_DIR, 'b') 39 | b:write('Test b') 40 | b:setLastModified(830908800000) 41 | 42 | logger:info('zip directories and files') 43 | ZipFile.zipTo(z, {a, b}) 44 | 45 | logger:info('delete directories and files') 46 | a:delete() 47 | b:delete() 48 | lu.assertEquals(a:exists(), false) 49 | lu.assertEquals(b:exists(), false) 50 | 51 | logger:info('unzip directories and files') 52 | ZipFile.unzipTo(z, TMP_DIR) 53 | lu.assertEquals(a:isFile(), true) 54 | lu.assertEquals(b:isFile(), true) 55 | lu.assertEquals(b:lastModified(), 830908800000) 56 | lu.assertEquals(a:readAll(), 'Test a') 57 | lu.assertEquals(b:readAll(), 'Test b') 58 | end 59 | 60 | function Tests:test_Struct_to_from() 61 | local Struct = ZipFile._Struct 62 | 63 | --lu.assertEquals(res, exp) 64 | --lu.assertIsNil(res) 65 | local struct = Struct:new({ 66 | {name = 'aUInt8', type = 'B'}, 67 | {name = 'aInt8', type = 'b'}, 68 | {name = 'aUInt16', type = 'H'}, 69 | {name = 'aInt16', type = 'h'}, 70 | {name = 'aUInt32', type = 'I4'}, 71 | {name = 'aInt32', type = 'i4'} 72 | }) 73 | local t = { 74 | aUInt8 = 1, 75 | aInt8 = 2, 76 | aUInt16 = 3, 77 | aInt16 = 4, 78 | aUInt32 = 5, 79 | aInt32 = 6 80 | } 81 | lu.assertEquals(struct:fromString(struct:toString(t)), t) 82 | end 83 | 84 | os.exit(lu.LuaUnit.run()) 85 | -------------------------------------------------------------------------------- /jls/net/http/handler/ZipFileHttpHandler.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple HTTP handler for ZIP file. 2 | -- @module jls.net.http.handler.ZipFileHttpHandler 3 | -- @pragma nostrip 4 | 5 | local ZipFile = require('jls.util.zip.ZipFile') 6 | local HTTP_CONST = require('jls.net.http.HttpMessage').CONST 7 | local HttpExchange = require('jls.net.http.HttpExchange') 8 | local FileHttpHandler = require('jls.net.http.handler.FileHttpHandler') 9 | local Date = require('jls.util.Date') 10 | 11 | --- A ZipFileHttpHandler class. 12 | -- @type ZipFileHttpHandler 13 | return require('jls.lang.class').create('jls.net.http.HttpHandler', function(zipFileHttpHandler) 14 | 15 | --- Creates a ZIP file @{HttpHandler}. 16 | -- @tparam jls.io.File zipFile the ZIP file. 17 | -- @function ZipFileHttpHandler:new 18 | function zipFileHttpHandler:initialize(zipFile) 19 | self.zipFile = ZipFile:new(zipFile, false) 20 | end 21 | 22 | function zipFileHttpHandler:handle(exchange) 23 | if not HttpExchange.methodAllowed(exchange, {HTTP_CONST.METHOD_GET, HTTP_CONST.METHOD_HEAD}) then 24 | return 25 | end 26 | local response = exchange:getResponse() 27 | local zipFile = self.zipFile 28 | local path = exchange:getRequestPath() 29 | local entry = zipFile:getEntry(path) 30 | if entry and not entry:isDirectory() then 31 | response:setStatusCode(HTTP_CONST.HTTP_OK, 'OK') 32 | response:setContentType(FileHttpHandler.guessContentType(path)) 33 | response:setCacheControl(true) 34 | local d = Date.fromLocalDateTime(entry:getDatetime(), true) 35 | if d then 36 | response:setLastModified(d) 37 | end 38 | response:setContentLength(entry:getSize()) 39 | if exchange:getRequestMethod() == HTTP_CONST.METHOD_GET then 40 | local request = exchange:getRequest() 41 | local ifModifiedSince = request:getIfModifiedSince() 42 | if ifModifiedSince and d and d:getTime() <= ifModifiedSince then 43 | response:setStatusCode(HTTP_CONST.HTTP_NOT_MODIFIED, 'Not modified') 44 | return 45 | end 46 | --response:setBody(zipFile:getContent(entry)) 47 | response:onWriteBodyStreamHandler(function() 48 | zipFile:getContent(entry, response:getBodyStreamHandler(), true) 49 | end) 50 | end 51 | else 52 | HttpExchange.notFound(exchange) 53 | end 54 | end 55 | 56 | end) 57 | -------------------------------------------------------------------------------- /tests/full/thread_stream.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Thread = require('jls.lang.Thread') 4 | local event = require('jls.lang.event') 5 | local loader = require('jls.lang.loader') 6 | local BufferStream = loader.tryRequire('jls.util.BufferStream') 7 | local Promise = require('jls.lang.Promise') 8 | local loop = require('jls.lang.loopWithTimeout') 9 | 10 | local function onError(reason) 11 | print('Unexpected error: '..tostring(reason)) 12 | end 13 | 14 | local function waitConnect(bs, delay) 15 | return Promise:new(function(resolve, reject) 16 | local function f() 17 | if bs.outgoingQueue then 18 | resolve(bs) 19 | else 20 | event:setTimeout(f, delay or 100) 21 | end 22 | end 23 | f() 24 | end) 25 | end 26 | 27 | function Test_thread_stream() 28 | if not BufferStream then 29 | local isLuv = package.loaded['jls.lang.event'] == package.loaded['jls.lang.event-luv'] 30 | if isLuv then 31 | lu.fail('fail to require BufferStream') 32 | else 33 | print('/!\\ skipping stream test as BufferStream is not available') 34 | lu.success() 35 | end 36 | return 37 | end 38 | local result = nil 39 | local bs = BufferStream:new(4096) 40 | Thread:new(Thread.resolveUpValues(function(...) 41 | local ts = BufferStream:new(4096, ...) 42 | local n = 0 43 | ts:readStart(function(err, data) 44 | if err or not data or data == 'close' then 45 | ts:close() 46 | else 47 | n = n + 1 48 | end 49 | end) 50 | ts:write('x'):next(function() 51 | return ts:write('y') 52 | end):next(function() 53 | return ts:write('z') 54 | end):catch(print) 55 | event:loop() 56 | ts:close() 57 | return n 58 | end)):start(bs:openAsync()):ended():next(function(res) 59 | result = res 60 | end, onError) 61 | lu.assertNil(result) 62 | local recvCount = 0 63 | bs:readStart(function(err, data) 64 | recvCount = recvCount + 1 65 | end) 66 | waitConnect(bs):next(function() 67 | return bs:write('a') 68 | end):next(function() 69 | return bs:write('b') 70 | end):next(function() 71 | return bs:write('close') 72 | end):catch(onError) 73 | if not loop() then 74 | lu.fail('Timeout reached') 75 | end 76 | bs:close() 77 | lu.assertNotNil(result) 78 | lu.assertEquals(result, 2) 79 | lu.assertEquals(recvCount, 3) 80 | end 81 | 82 | os.exit(lu.LuaUnit.run()) 83 | -------------------------------------------------------------------------------- /tests/base/MessageDigest.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local MessageDigest = require('jls.util.MessageDigest') 4 | local hex = require('jls.util.Codec').getInstance('hex') 5 | 6 | --[[ 7 | MessageDigest.getInstance('MD5') 8 | print('loaded modules:') 9 | for name in pairs(package.loaded) do 10 | print(' '..name) 11 | end 12 | ]] 13 | 14 | local function assertHexEquals(result, expected) 15 | lu.assertEquals(result, expected, string.format('expected: 0x%X, actual: 0x%X', expected, result)) 16 | end 17 | 18 | local function onAlgAndMod(alg, mod, fn) 19 | local Md = MessageDigest.getMessageDigest(alg) 20 | fn(Md:new()) 21 | local Mdp = require(mod) 22 | if Md ~= Mdp then 23 | --print('also testing pure Lua module '..mod) 24 | fn(Mdp:new()) 25 | end 26 | end 27 | 28 | function Test_md5_digest() 29 | onAlgAndMod('MD5', 'jls.util.md.md5-', function(md) 30 | lu.assertEquals(hex:encode(md:update(''):digest()), 'd41d8cd98f00b204e9800998ecf8427e') 31 | md:reset():update('The quick brown fox jumps over the lazy dog') 32 | lu.assertEquals(hex:encode(md:digest()), '9e107d9d372bb6826bd81d3542a419d6') 33 | md:reset():update('The quick brown fox jumps over the lazy dog.') 34 | lu.assertEquals(hex:encode(md:digest()), 'e4d909c290d0fb1ca068ffaddf22cbd0') 35 | end) 36 | end 37 | 38 | function Test_md5_updates() 39 | local md = MessageDigest.getInstance('MD5') 40 | md:update('The quick brown fox'):update(' jumps over the lazy dog') 41 | md:update('.') 42 | lu.assertEquals(hex:encode(md:digest()), 'e4d909c290d0fb1ca068ffaddf22cbd0') 43 | end 44 | 45 | function Test_sha1_digest() 46 | onAlgAndMod('SHA-1', 'jls.util.md.sha1-', function(md) 47 | md:update('The quick brown fox jumps over the lazy dog') 48 | lu.assertEquals(hex:encode(md:digest()), '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12') 49 | end) 50 | end 51 | 52 | function Test_Crc32_updates() 53 | onAlgAndMod('CRC32', 'jls.util.md.crc32-', function(md) 54 | md:update('The quick brown fox') 55 | md:update(' jumps over the lazy dog') 56 | assertHexEquals(md:digest(), 0x414FA339) 57 | end) 58 | end 59 | 60 | function Test_Crc32_digest() 61 | local md = MessageDigest.getInstance('CRC32') 62 | md:update('123456789') 63 | assertHexEquals(md:digest(), 0xCBF43926) 64 | md:reset():update('The quick brown fox jumps over the lazy dog') 65 | assertHexEquals(md:digest(), 0x414FA339) 66 | end 67 | 68 | os.exit(lu.LuaUnit.run()) 69 | -------------------------------------------------------------------------------- /jls/io/streams/PromisesStreamHandler.lua: -------------------------------------------------------------------------------- 1 | local Promise = require('jls.lang.Promise') 2 | local StreamHandler = require('jls.io.StreamHandler') 3 | 4 | local END = Promise.resolve() 5 | 6 | return require('jls.lang.class').create(StreamHandler, function(promisesStreamHandler) 7 | 8 | function promisesStreamHandler:initialize() 9 | self.list = {} 10 | self.promise = nil 11 | self.readIndex = 0 12 | self.writeIndex = 0 13 | end 14 | 15 | local function setPromise(self, value) 16 | self.list = nil 17 | self.promise = value 18 | end 19 | 20 | function promisesStreamHandler:read() 21 | if self.promise then 22 | return self.promise 23 | end 24 | self.size = 0 25 | local index = self.readIndex + 1 26 | self.readIndex = index 27 | local promise = self.list[index] 28 | if promise then 29 | if promise == END then 30 | setPromise(self, Promise.reject('ended')) 31 | else 32 | self.list[index] = nil 33 | end 34 | else 35 | local apply 36 | promise, apply = Promise.withCallback() 37 | promise._apply = apply 38 | self.list[index] = promise 39 | end 40 | return promise 41 | end 42 | 43 | local function write(self, err, data) 44 | local index = self.writeIndex + 1 45 | self.writeIndex = index 46 | local promise = self.list[index] 47 | if promise then 48 | promise._apply(err, data) 49 | self.list[index] = nil 50 | if not data and not err then 51 | setPromise(self, Promise.reject('ended')) 52 | end 53 | else 54 | if err then 55 | promise = Promise.reject(err) 56 | elseif data then 57 | promise = Promise.resolve(data) 58 | else 59 | promise = END 60 | end 61 | self.list[index] = promise 62 | end 63 | end 64 | 65 | function promisesStreamHandler:available() 66 | return self.writeIndex > self.readIndex 67 | end 68 | 69 | function promisesStreamHandler:onData(data) 70 | write(self, nil, data) 71 | end 72 | 73 | function promisesStreamHandler:onError(err) 74 | write(self, err or 'unknown error') 75 | end 76 | 77 | function promisesStreamHandler:close() 78 | for index = self.readIndex - 1, 1, -1 do 79 | local promise = self.list[index] 80 | if promise then 81 | promise._apply('closed') 82 | else 83 | break 84 | end 85 | end 86 | setPromise(self, Promise.reject('closed')) 87 | end 88 | 89 | end) 90 | 91 | -------------------------------------------------------------------------------- /tests/full/Buffer.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Buffer = require('jls.lang.Buffer') 4 | local BufferFile = require('jls.lang.BufferFile') 5 | local BufferView = require('jls.lang.BufferView') 6 | local serialization = require('jls.lang.serialization') 7 | 8 | function Test_buffer_allocate() 9 | local buffer = Buffer.allocate(10) 10 | lu.assertEquals(buffer:length(), 10) 11 | buffer = Buffer.allocate('Hello') 12 | lu.assertEquals(buffer:length(), 5) 13 | lu.assertEquals(buffer:get(), 'Hello') 14 | lu.assertFalse(pcall(Buffer.allocate, -1)) 15 | lu.assertFalse(pcall(Buffer.allocate)) 16 | lu.assertFalse(pcall(Buffer.allocate, 1, 'unknown')) 17 | end 18 | 19 | local function assertSetGetBytes(buffer) 20 | buffer:setBytes(1, 11, 12, 13, 14, 15) 21 | lu.assertEquals({buffer:getBytes(2, 4)}, {12, 13, 14}) 22 | end 23 | 24 | local function assertSetGet(buffer) 25 | buffer:set('Hello') 26 | lu.assertEquals(buffer:get(1, 5), 'Hello') 27 | end 28 | 29 | local function assertBuffers(assertFn, size) 30 | assertFn(Buffer.allocate(size)) 31 | if BufferFile ~= Buffer then 32 | assertFn(BufferFile.allocate(size)) 33 | end 34 | assertFn(Buffer.allocate(size + 2):view(2, size + 1)) 35 | end 36 | 37 | function Test_buffer_bytes() 38 | assertBuffers(assertSetGetBytes, 5) 39 | end 40 | 41 | function Test_buffer_string() 42 | assertBuffers(assertSetGet, 5) 43 | end 44 | 45 | function Test_buffer_serialization() 46 | local buffer = Buffer.allocate(10) 47 | local lb = serialization.deserialize(serialization.serialize(buffer)) 48 | lb:set('Hello') 49 | lu.assertEquals(lb:get(1, 5), 'Hello') 50 | end 51 | 52 | function Test_buffer_view() 53 | local buffer = Buffer.allocate(10) 54 | buffer:set(' ') 55 | local vb = buffer:view(3, 7) 56 | lu.assertEquals(vb:length(), 5) 57 | lu.assertEquals(vb:get(), ' ') 58 | vb:set('Hello') 59 | local vvb = vb:view(2, 4) 60 | lu.assertEquals(vb:get(), 'Hello') 61 | lu.assertEquals(vvb:get(), 'ell') 62 | lu.assertEquals(buffer:get(), ' Hello ') 63 | end 64 | 65 | function Test_buffer_view_serialization() 66 | local buffer = Buffer.allocate(10) 67 | buffer:set(' ') 68 | local vb = buffer:view(3, 7) 69 | vb = serialization.deserialize(serialization.serialize(vb)) 70 | lu.assertTrue(BufferView:isInstance(vb)) 71 | lu.assertEquals(vb:length(), 5) 72 | lu.assertEquals(vb:get(), ' ') 73 | vb:set('Hello') 74 | lu.assertEquals(vb:get(), 'Hello') 75 | lu.assertEquals(buffer:get(), ' Hello ') 76 | end 77 | 78 | os.exit(lu.LuaUnit.run()) 79 | -------------------------------------------------------------------------------- /tests/full/dns.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local dns = require('jls.net.dns') 4 | local logger = require('jls.lang.logger') 5 | local tables = require('jls.util.tables') 6 | local Codec = require('jls.util.Codec') 7 | local hex = Codec.getInstance('hex') 8 | 9 | local loop = require('jls.lang.loopWithTimeout') 10 | 11 | local function assertAddressInfo(host, addr, family) 12 | local infos = {} 13 | dns.getAddressInfo(host):next(function(result) 14 | infos = result 15 | end) 16 | if not loop() then 17 | lu.fail('Timeout reached') 18 | end 19 | local found = false 20 | for _, info in ipairs(infos) do 21 | if (not family or info.family == family) and info.addr == addr then 22 | found = true 23 | end 24 | end 25 | if not found then 26 | logger:error('getAddressInfo(%s) %t', host, infos) 27 | end 28 | lu.assertTrue(found) 29 | end 30 | 31 | function Test_getAddressInfo() 32 | assertAddressInfo('localhost', '127.0.0.1', 'inet') 33 | end 34 | 35 | function Test_getNameInfo() 36 | local info 37 | dns.getNameInfo('127.0.0.1'):next(function(result) 38 | info = result 39 | end) 40 | if not loop() then 41 | lu.fail('Timeout reached') 42 | end 43 | --lu.assertEquals(info, 'localhost') 44 | lu.assertEquals(type(info), 'string') 45 | --assertAddressInfo(info, '127.0.0.1', 'inet') 46 | end 47 | 48 | function Test_decode_encode_message() 49 | local raw = hex:decode('007b00000002000000000000045f697070045f746370056c6f63616c00000c0001055f69707073045f746370056c6f63616c00000c0001') 50 | local message = dns.decodeMessage(raw) 51 | lu.assertEquals(dns.encodeMessage(message), raw) 52 | end 53 | 54 | function Test_encode_decode_message() 55 | local message = { 56 | id = 123, 57 | flags = { 58 | qr = true, 59 | opcode = dns.OPCODES.UPDATE, 60 | aa = true, 61 | tc = false, 62 | rd = false, 63 | ra = false, 64 | z = false, 65 | ad = false, 66 | cd = false, 67 | rcode = dns.RCODES.NOTAUTH, 68 | }, 69 | questions = {{ 70 | name = '_ipp._tcp.local', 71 | type = dns.TYPES.PTR, 72 | class = dns.CLASSES.IN, 73 | }, { 74 | name = '_ipps._tcp.local', 75 | type = dns.TYPES.PTR, 76 | class = dns.CLASSES.IN, 77 | unicastResponse = true, 78 | }}, 79 | answers={}, 80 | authorities={}, 81 | additionals={}, 82 | } 83 | --print(tables.stringify(message, 2)) 84 | local raw = dns.encodeMessage(message) 85 | lu.assertEquals(dns.decodeMessage(raw), message) 86 | end 87 | 88 | os.exit(lu.LuaUnit.run()) 89 | -------------------------------------------------------------------------------- /tests/full/system_execute.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local system = require('jls.lang.system') 4 | local ProcessHandle = require('jls.lang.ProcessHandle') 5 | local loop = require('jls.lang.loopWithTimeout') 6 | 7 | local LUA_EXE_PATH = ProcessHandle.getExecutablePath() 8 | 9 | local function commandArgs(code) 10 | return {LUA_EXE_PATH, '-e', 'os.exit('..tostring(code)..')'} 11 | end 12 | 13 | local function commandLine(code) 14 | return system.formatCommandLine(commandArgs(code)) 15 | end 16 | 17 | function Test_execute_success() 18 | local success = false 19 | system.execute(commandLine(0)):next(function() 20 | success = true 21 | end) 22 | if not loop() then 23 | lu.fail('Timeout reached') 24 | end 25 | lu.assertTrue(success) 26 | end 27 | 28 | function Test_execute_failure() 29 | local failure = false 30 | local failureReason = nil 31 | system.execute(commandLine(1)):catch(function(reason) 32 | failureReason = reason 33 | failure = true 34 | end) 35 | if not loop() then 36 | lu.fail('Timeout reached') 37 | end 38 | lu.assertEquals(failureReason, 'Execute fails with exit code 1') 39 | lu.assertTrue(failure) 40 | end 41 | 42 | local function assertExitCode(code) 43 | local exitCode = nil 44 | system.execute(commandLine(code), true):next(function(info) 45 | exitCode = info.code 46 | end) 47 | if not loop() then 48 | lu.fail('Timeout reached') 49 | end 50 | lu.assertEquals(exitCode, code) 51 | end 52 | 53 | function Test_execute_with_exitCode() 54 | assertExitCode(0) 55 | assertExitCode(11) 56 | end 57 | 58 | function Test_findExecutablePath() 59 | lu.assertNil(system.findExecutablePath('unlikely-executable-name')) 60 | local executableName = system.isWindows() and 'cmd' or 'sh' 61 | local executablePath = system.findExecutablePath(executableName) 62 | --print(executableName, executablePath) 63 | lu.assertNotNil(executablePath) 64 | end 65 | 66 | function Test_exec() 67 | local ph = system.exec(commandArgs(0)) 68 | lu.assertTrue(ph:isAlive()) 69 | if not loop() then 70 | lu.fail('Timeout reached') 71 | end 72 | lu.assertFalse(ph:isAlive()) 73 | end 74 | 75 | function Test_getArguments() 76 | lu.assertEquals(type(system.getArguments()), 'table') 77 | lu.assertEquals(type(system.getLibraryExtension()), 'string') 78 | end 79 | 80 | function Test_gc() 81 | local flag = false 82 | local t = setmetatable({}, { 83 | __gc = function() 84 | flag = true 85 | end 86 | }) 87 | lu.assertFalse(flag) 88 | t = nil 89 | system.gc() 90 | lu.assertTrue(flag) 91 | end 92 | 93 | os.exit(lu.LuaUnit.run()) 94 | -------------------------------------------------------------------------------- /jls/lang/event-luv.lua: -------------------------------------------------------------------------------- 1 | local luvLib = require('luv') 2 | local class = require('jls.lang.class') 3 | local logger = require('jls.lang.logger'):get(...) 4 | local Exception = require('jls.lang.Exception') 5 | 6 | return class.create(function(event) 7 | 8 | function event:onError(err) 9 | logger:warn('Event failed due to "%s"', err) 10 | end 11 | 12 | local function newTimer(callback, timeoutMs, repeatMs, ...) 13 | local args = table.pack(...) 14 | local timer = luvLib.new_timer() 15 | timer:start(timeoutMs, repeatMs, function() 16 | if repeatMs <= 0 then 17 | timer:close() 18 | end 19 | local status, err = Exception.pcall(callback, table.unpack(args, 1, args.n)) 20 | if not status then 21 | logger:warn('event timer callback on error "%s"', err) 22 | end 23 | end) 24 | return timer -- as opaque id 25 | end 26 | 27 | function event:setTimeout(callback, delayMs, ...) 28 | return newTimer(callback, delayMs or 0, 0, ...) 29 | end 30 | 31 | function event:clearTimeout(timer) 32 | if timer then 33 | timer:stop() 34 | if not timer:is_closing() then 35 | timer:close() 36 | end 37 | end 38 | end 39 | 40 | function event:setInterval(callback, delayMs, ...) 41 | return newTimer(callback, delayMs, delayMs, ...) 42 | end 43 | 44 | event.setTask = class.notImplementedFunction 45 | 46 | event.clearInterval = event.clearTimeout 47 | 48 | function event:daemon(timer, daemon) 49 | if daemon then 50 | luvLib.unref(timer) 51 | else 52 | luvLib.ref(timer) 53 | end 54 | end 55 | 56 | function event:loop() 57 | -- returns true if uv_stop() was called and there are still active handles or requests, false otherwise 58 | -- may returns nil then an error message in case of libuv returning <0 59 | local ret, err = luvLib.run() 60 | if ret then 61 | logger:fine('loop() return %s', ret) 62 | elseif ret == nil then 63 | logger:fine('loop() in error %s', err) 64 | end 65 | end 66 | 67 | function event:stop() 68 | logger:fine('stop()') 69 | luvLib.stop() 70 | end 71 | 72 | function event:loopAlive() 73 | return luvLib.loop_alive() 74 | end 75 | 76 | function event:runOnce() 77 | luvLib.run('once') 78 | end 79 | 80 | function event:runNoWait() 81 | luvLib.run('nowait') 82 | end 83 | 84 | function event:close() 85 | --luvLib.loop_close() -- the loop will automatically be closed when it is garbage collected by Lua 86 | end 87 | 88 | function event:print() 89 | luvLib.print_all_handles() 90 | end 91 | 92 | end):new() 93 | -------------------------------------------------------------------------------- /jls/lang/Lock-.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | local system = require('jls.lang.system') 3 | 4 | local function read(file, offset) 5 | file:seek('set', offset) 6 | local s = file:read(1) 7 | if s then 8 | return (string.byte(s)) 9 | end 10 | return 0 11 | end 12 | 13 | local function write(file, offset, value) 14 | file:seek('set', offset) 15 | file:write(string.char(value)) 16 | file:flush() 17 | end 18 | 19 | local MAX_ID = 2 20 | 21 | local function getId(self) 22 | return self.initialized and 0 or 1 23 | end 24 | 25 | local function softUnlock(file, id) 26 | write(file, MAX_ID + id, 0) 27 | end 28 | 29 | -- From Lamport's bakery algorithm 30 | local function softLock(file, id, try) 31 | local max = 0 32 | write(file, id, 1) 33 | for j = MAX_ID, MAX_ID * 2 - 1 do 34 | max = math.max(max, read(file, j)) 35 | end 36 | local tic = 1 + max; 37 | write(file, MAX_ID + id, tic) 38 | write(file, id, 0) 39 | --print(string.format("mutex_soft_init(%s, %s) max: %d", id, try, max)); 40 | if try and max ~= 0 then 41 | softUnlock(file, id) 42 | return false 43 | end 44 | for i = 0, MAX_ID - 1 do 45 | if i ~= id then 46 | while read(file, i) == 1 do 47 | system.sleep(0) 48 | end 49 | local j = MAX_ID + i 50 | while true do 51 | local cur = read(file, j) 52 | if cur ~= 0 and (cur < tic or (cur == tic and i < id)) then 53 | system.sleep(0) 54 | else 55 | break 56 | end 57 | end 58 | end 59 | end 60 | return true 61 | end 62 | 63 | return class.create(function(lock) 64 | 65 | function lock:initialize() 66 | self.name = string.format('.%s-%p.tmp', 'jls.lang.Lock', self) 67 | self.file = io.open(self.name, 'w+') 68 | self.initialized = true 69 | self.file:write(string.rep('\0', MAX_ID * 2)) 70 | self.file:flush() 71 | end 72 | 73 | function lock:finalize() 74 | if self.initialized then 75 | self.initialized = false 76 | self.file:close() 77 | os.remove(self.name) 78 | end 79 | end 80 | 81 | function lock:lock() 82 | softLock(self.file, getId(self), false) 83 | end 84 | 85 | function lock:unlock() 86 | softUnlock(self.file, getId(self)) 87 | end 88 | 89 | function lock:tryLock() 90 | return softLock(self.file, getId(self), true) 91 | end 92 | 93 | function lock:serialize(w) 94 | w(self.name) 95 | end 96 | 97 | function lock:deserialize(r) 98 | self.name = r('string') 99 | self.file = io.open(self.name, 'r+') 100 | end 101 | 102 | end) -------------------------------------------------------------------------------- /jls/net/http/HttpSession.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | HTTP session class. 3 | 4 | A session is associated to the HTTP exchange. 5 | It can be used for tasks such as authentication, access control. 6 | 7 | @module jls.net.http.HttpSession 8 | @pragma nostrip 9 | ]] 10 | 11 | --- A HttpSession class. 12 | -- The HttpSession class inherits from @{Attributes}. 13 | -- @type HttpSession 14 | return require('jls.lang.class').create('jls.net.http.Attributes', function(httpSession, super) 15 | 16 | --- Creates an HTTP session. 17 | -- @function HttpSession:new 18 | function httpSession:initialize(id, creationTime) 19 | super.initialize(self) 20 | self.id = id or '' 21 | self.creationTime = creationTime or 0 22 | self.lastAccessTime = self.creationTime 23 | end 24 | 25 | --- Returns this session id. 26 | -- @treturn string the session id. 27 | function httpSession:getId() 28 | return self.id 29 | end 30 | 31 | --- Returns the creation time of this session. 32 | -- The time is given as the number of milliseconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). 33 | -- @treturn number the creation time. 34 | function httpSession:getCreationTime() 35 | return self.creationTime 36 | end 37 | 38 | --- Returns the time where this session was last accessed. 39 | -- The time is given as the number of milliseconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). 40 | -- @treturn number the last access time. 41 | function httpSession:getLastAccessTime() 42 | return self.lastAccessTime 43 | end 44 | 45 | function httpSession:setLastAccessTime(time) 46 | self.lastAccessTime = time 47 | end 48 | 49 | --- Invalidates this session. 50 | function httpSession:invalidate() 51 | self.id = '' 52 | self.creationTime = 0 53 | self.lastAccessTime = 0 54 | self:cleanAttributes() 55 | end 56 | 57 | function httpSession:serialize(write) 58 | write(self.id) 59 | write(self.creationTime) 60 | write(self.lastAccessTime) 61 | for name, value in pairs(self:getAttributes()) do 62 | write(name) 63 | if not write(value, true) then 64 | write(nil) 65 | end 66 | end 67 | write(nil) 68 | end 69 | 70 | function httpSession:deserialize(read) 71 | super.initialize(self) 72 | self.id = read('string') 73 | self.creationTime = read('number') 74 | self.lastAccessTime = read('number') 75 | while true do 76 | local name = read('string|nil') 77 | if name then 78 | local value = read() 79 | if value ~= nil then 80 | self:setAttribute(name, value) 81 | end 82 | else 83 | break 84 | end 85 | end 86 | end 87 | 88 | end) 89 | -------------------------------------------------------------------------------- /jls/io/streams/BlockStreamHandler.lua: -------------------------------------------------------------------------------- 1 | --- Provide a block stream handler. 2 | -- @module jls.io.streams.BlockStreamHandler 3 | -- @pragma nostrip 4 | 5 | local logger = require('jls.lang.logger'):get(...) 6 | local Promise = require('jls.lang.Promise') 7 | local StringBuffer = require('jls.lang.StringBuffer') 8 | local StreamHandler = require('jls.io.StreamHandler') 9 | 10 | --- A BlockStreamHandler class. 11 | -- This class allows to pass fixed size blocks to the wrapped handler. 12 | -- @type BlockStreamHandler 13 | return require('jls.lang.class').create(StreamHandler.WrappedStreamHandler, function(blockStreamHandler, super) 14 | 15 | --- Creates a block @{StreamHandler}. 16 | -- @tparam[opt] StreamHandler handler the handler to wrap 17 | -- @tparam[opt] number size the block size, default to 512 18 | -- @tparam[opt] boolean multiple true to indicate that the resulting size must be a multiple 19 | -- @function BlockStreamHandler:new 20 | function blockStreamHandler:initialize(handler, size, multiple) 21 | logger:finest('initialize()') 22 | super.initialize(self, handler) 23 | self.size = size or 512 24 | self.buffer = StringBuffer:new() 25 | self.multiple = multiple and true or false 26 | end 27 | 28 | function blockStreamHandler:getStringBuffer() 29 | return self.buffer 30 | end 31 | 32 | function blockStreamHandler:getBuffer() 33 | return self.buffer:toString() 34 | end 35 | 36 | function blockStreamHandler:onData(data) 37 | logger:finer('onData(#%l)', data) 38 | if data then 39 | self.buffer:append(data) 40 | local len = self.buffer:length() 41 | if len == 0 then 42 | return self.handler:onData('') 43 | elseif len >= self.size then 44 | if self.multiple then 45 | local r = len % self.size 46 | local q = len - r 47 | local s = self.buffer:sub(1, q) 48 | return self.handler:onData(s:toString()) 49 | else 50 | local l = {} 51 | while self.buffer:length() >= self.size do 52 | local s = self.buffer:sub(1, self.size) 53 | local r = self.handler:onData(s:toString()) 54 | if r then 55 | table.insert(l, Promise:isInstance(r) and r or Promise.resolve(r)) 56 | end 57 | end 58 | if #l > 0 then 59 | return Promise.all(l) 60 | end 61 | end 62 | end 63 | else 64 | local r 65 | if self.buffer:length() > 0 then 66 | r = self.handler:onData(self.buffer:toString()) 67 | self.buffer:clear() 68 | end 69 | self.handler:onData(nil) 70 | return r 71 | end 72 | end 73 | 74 | end) 75 | -------------------------------------------------------------------------------- /jls/util/Worker-.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | Provides a way to process tasks in background. 3 | 4 | The worker thread interacts with the current thread via message passing. 5 | 6 | By default the worker thread has an event loop and will be triggered when receiving message. 7 | You could disable the event loop and incoming messages by using the option `disableReceive`. 8 | 9 | @module jls.util.Worker 10 | @pragma nostrip 11 | --]] 12 | 13 | local class = require('jls.lang.class') 14 | local logger = require('jls.lang.logger'):get(...) 15 | local Promise = require('jls.lang.Promise') 16 | 17 | --- The Worker class. 18 | -- The Worker provides a way to process task in background. 19 | -- @type Worker 20 | return class.create(function(worker, _, Worker) 21 | 22 | --[[-- Creates a new Worker. 23 | @function Worker:new 24 | @tparam function fn the function that the worker will execute 25 | @param[opt] data the data to pass to the worker function 26 | @tparam[opt] function onMessage the function that will handle messages 27 | @tparam[opt] table options the worker options 28 | @return a new Worker 29 | @usage 30 | local w = Worker:new(function(w) 31 | function w:onMessage(message) 32 | print('received in worker', message) 33 | w:postMessage('Hi '..tostring(message)) 34 | end 35 | end, nil, function(self, message) 36 | print('received from worker', message) 37 | self:close() 38 | end) 39 | w:postMessage('John') 40 | ]] 41 | function worker:initialize(fn, data, onMessage) 42 | if type(fn) ~= 'function' then 43 | error('Invalid arguments') 44 | end 45 | if type(onMessage) == 'function' then 46 | self.onMessage = onMessage 47 | end 48 | local w = class.makeInstance(Worker) 49 | self._remote = w 50 | w._remote = self 51 | fn(w, data) -- posted messages will be lost 52 | end 53 | 54 | --- Sends a message to the worker. 55 | -- @param message the message to send 56 | -- @treturn jls.lang.Promise a promise that resolves once the message is sent 57 | function worker:postMessage(message) 58 | self._remote:onMessage(message) 59 | return Promise.resolve() 60 | end 61 | 62 | --- Receives a message from the worker. 63 | -- @param message the message to handle 64 | function worker:onMessage(message) 65 | logger:finer('onMessage() not overriden') 66 | end 67 | 68 | --- Returns true if this worker is connected. 69 | -- @treturn boolean true if this worker is connected 70 | function worker:isConnected() 71 | return self._remote ~= nil 72 | end 73 | 74 | --- Closes the worker. 75 | function worker:close() 76 | if self._remote then 77 | self._remote._remote = nil 78 | self._remote = nil 79 | end 80 | end 81 | 82 | end) 83 | -------------------------------------------------------------------------------- /tests/full/http_headers.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local HttpHeaders = require('jls.net.http.HttpHeaders') 4 | local HttpMessage = require('jls.net.http.HttpMessage') 5 | 6 | --[[ 7 | Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5 8 | Accept-Encoding: gzip, deflate, br 9 | Accept-Language: en-US,en;q=0.9,fr;q=0.8 10 | cache-control: no-store, no-cache, must-revalidate 11 | content-type: text/html; charset=UTF-8 12 | Keep-Alive: timeout=15, max=94 13 | ]] 14 | function Test_getHeaderValues() 15 | local message = HttpMessage:new() 16 | message:setHeader('Accept', 'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') 17 | message:setHeader('Accept-Language', 'en-US,en;q=0.9,fr;q=0.8') 18 | lu.assertEquals(message:getHeader('accept-language'), 'en-US,en;q=0.9,fr;q=0.8') 19 | lu.assertEquals(message:getHeaderValues('Accept-Language'), {'en-US', 'en;q=0.9', 'fr;q=0.8'}) 20 | end 21 | 22 | function Test_hasHeaderValue() 23 | local message = HttpMessage:new() 24 | message:setHeader('Accept-Language', 'en-US,en;q=0.9,fr;q=0.8') 25 | message:setHeader('Content-Type', 'application/json; charset=utf-8') 26 | lu.assertEquals(message:hasHeaderValue('accept-language', 'en'), true) 27 | lu.assertEquals(message:hasHeaderValue('accept-language', 'en-US'), true) 28 | lu.assertEquals(message:hasHeaderValue('accept-language', 'fr'), true) 29 | lu.assertEquals(message:hasHeaderValue('accept-language', 'en-GB'), false) 30 | lu.assertEquals(message:hasHeaderValue('content-type', 'application/JSON'), false) 31 | lu.assertEquals(message:hasHeaderValue('content-type', 'application/JSON', true), true) 32 | lu.assertEquals(message:hasHeaderValueIgnoreCase('content-type', 'application/JSON'), true) 33 | end 34 | 35 | function Test_parseHeaderValue() 36 | lu.assertNil(HttpMessage.parseHeaderValue(nil)) 37 | lu.assertEquals(HttpMessage.parseHeaderValue('text/html'), 'text/html') 38 | lu.assertEquals(HttpMessage.parseHeaderValue('text/html;level=2;q=0.4'), 'text/html') 39 | lu.assertEquals({HttpMessage.parseHeaderValue('text/html;level=2;q=0.4')}, {'text/html', {'level=2', 'q=0.4'}}) 40 | end 41 | 42 | function Test_setCookie() 43 | local name, value = 'aname', 'avalue' 44 | local name2, value2 = 'aname2', 'avalue2' 45 | local headers = HttpHeaders:new() 46 | headers:setCookie(name, value) 47 | headers:setCookie(name2, value2) 48 | lu.assertEquals(headers:getHeader('set-cookie'), {'aname=avalue', 'aname2=avalue2'}) 49 | 50 | headers:setHeader('cookie', 'aname=avalue; aname2=avalue2') 51 | lu.assertEquals(headers:getCookie(name), value) 52 | lu.assertEquals(headers:getCookie(name2), value2) 53 | end 54 | 55 | os.exit(lu.LuaUnit.run()) 56 | -------------------------------------------------------------------------------- /examples/zip.lua: -------------------------------------------------------------------------------- 1 | local system = require('jls.lang.system') 2 | local tables = require('jls.util.tables') 3 | local ZipFile = require('jls.util.zip.ZipFile') 4 | 5 | local options = tables.createArgumentTable(system.getArguments(), { 6 | helpPath = 'help', 7 | emptyPath = 'file', 8 | logPath = 'log-level', 9 | aliases = { 10 | h = 'help', 11 | a = 'action', 12 | d = 'dir', 13 | f = 'file', 14 | o = 'overwrite', 15 | ll = 'log-level', 16 | }, 17 | schema = { 18 | title = 'ZIP utility', 19 | type = 'object', 20 | additionalProperties = false, 21 | required = {'file'}, 22 | properties = { 23 | help = { 24 | title = 'Show the help', 25 | type = 'boolean', 26 | default = false 27 | }, 28 | file = { 29 | title = 'The ZIP file', 30 | type = 'string' 31 | }, 32 | overwrite = { 33 | title = 'Overwrite existing ZIP file', 34 | type = 'boolean', 35 | default = false 36 | }, 37 | dir = { 38 | title = 'The directory', 39 | type = 'string', 40 | default = '.' 41 | }, 42 | action = { 43 | title = 'The mode to execute', 44 | type = 'string', 45 | default = 'list', 46 | enum = {'list', 'create', 'extract', 'check'}, 47 | }, 48 | } 49 | } 50 | }) 51 | 52 | if options.action == 'extract' then 53 | ZipFile.unzipTo(options.file, options.dir) 54 | elseif options.action == 'create' then 55 | ZipFile.zipTo(options.file, options.dir, options.overwrite) 56 | elseif options.action == 'list' then 57 | local zFile = ZipFile:new(options.file, false) 58 | local entries = zFile:getEntries() 59 | zFile:close() 60 | print('Name', 'Datetime', 'Method', 'CompressedSize', 'Size') 61 | for _, entry in ipairs(entries) do 62 | print(entry:getName(), entry:getDatetime():toString(), entry:getMethod(), entry:getCompressedSize(), entry:getSize()) 63 | end 64 | elseif options.action == 'check' then 65 | local MessageDigest = require('jls.util.MessageDigest') 66 | local md = MessageDigest.getInstance('CRC32') 67 | local zFile = ZipFile:new(options.file, false) 68 | local entries = zFile:getEntries() 69 | print('Name', 'CRC', 'Check', 'Method', 'CompressedSize', 'Size') 70 | for _, entry in ipairs(entries) do 71 | local crc32 = entry:getCrc32() 72 | local computedCrc32 = 0 73 | if entry:getSize() > 0 and crc32 ~= 0 then 74 | local rawContent = assert(zFile:getContentSync(entry)) 75 | computedCrc32 = md:update(rawContent):digest() 76 | end 77 | print(entry:getName(), crc32, crc32 == computedCrc32 and 'ok' or computedCrc32, entry:getMethod(), entry:getCompressedSize(), entry:getSize()) 78 | end 79 | zFile:close() 80 | end 81 | -------------------------------------------------------------------------------- /tests/full/serial.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Serial = require('jls.io.Serial') 4 | local event = require('jls.lang.event') 5 | local system = require('jls.lang.system') 6 | local StreamHandler = require('jls.io.StreamHandler') 7 | local ProcessBuilder = require('jls.lang.ProcessBuilder') 8 | local Promise = require('jls.lang.Promise') 9 | local File = require('jls.io.File') 10 | 11 | local loop = require('jls.lang.loopWithTimeout') 12 | 13 | local SOCAT = '/usr/bin/socat' 14 | local TEST_PATH = 'tests/full' 15 | local TEST_SERIAL = TEST_PATH..'/pty0' 16 | local TEST_SERIAL_2 = TEST_PATH..'/pty1' 17 | local SERIAL_OPTS = ',b9600,raw,echo=0' 18 | local SERIAL_PARAMS = { 19 | baudRate = 9600, 20 | dataBits = 8, 21 | stopBits = 1, 22 | parity = 0 23 | } 24 | 25 | -- socat -hh | grep -e PTY -e TERM 26 | -- socat PTY,link=/tmp/ttys0,raw,echo=0 PTY,link=/tmp/ttys1,raw,echo=0 27 | 28 | local function wait(millis) 29 | return Promise:new(function(resolve, reject) 30 | event:setTimeout(resolve, millis) 31 | end) 32 | end 33 | 34 | function Test_serial_read_write_with_socat() 35 | if not File:new(SOCAT):exists() then 36 | print('/!\\ skipping test, socat not found', SOCAT) 37 | lu.success() 38 | return 39 | end 40 | local received, received2 41 | local pb = ProcessBuilder:new(SOCAT, 'pty,link='..TEST_SERIAL..SERIAL_OPTS, 'pty,link='..TEST_SERIAL_2..SERIAL_OPTS) 42 | pb:setRedirectOutput(system.output) 43 | pb:setRedirectError(system.error) 44 | 45 | -- TODO test getSerial 46 | 47 | local ph, err = pb:start() 48 | lu.assertNil(err) 49 | lu.assertNotNil(ph) 50 | -- redirect error 51 | Promise.async(function(await) 52 | await(wait(500)) 53 | if not ph:isAlive() then 54 | print('socat is not running') 55 | return 56 | end 57 | local serial = Serial:open(TEST_SERIAL, SERIAL_PARAMS) 58 | local sh = StreamHandler.promises() 59 | local serial2 = Serial:open(TEST_SERIAL_2, SERIAL_PARAMS) 60 | local sh2 = StreamHandler.promises() 61 | --print('start read/write on serial') 62 | --serial:readStart(StreamHandler.tee(StreamHandler.std, sh)) 63 | serial:readStart(sh) 64 | serial2:readStart(sh2) 65 | serial:write('Hi\n') 66 | serial2:write('Hello\n') 67 | received = await(sh:read()) 68 | received2 = await(sh2:read()) 69 | ph:destroy() 70 | await(serial:close()) 71 | await(serial2:close()) 72 | end):catch(function(r) 73 | print('error', r) 74 | end) 75 | if not loop() then 76 | ph:destroy() 77 | print(string.format('received "%s", "%s"', received, received2)) 78 | lu.fail('Timeout reached') 79 | end 80 | lu.assertEquals(received, 'Hello\n') 81 | lu.assertEquals(received2, 'Hi\n') 82 | end 83 | 84 | os.exit(lu.LuaUnit.run()) 85 | -------------------------------------------------------------------------------- /jls/io/streams/RangeStreamHandler.lua: -------------------------------------------------------------------------------- 1 | --- This class allows to restrict the stream to pass to the wrapped handler to a specified range. 2 | -- @module jls.io.streams.RangeStreamHandler 3 | -- @pragma nostrip 4 | local logger = require('jls.lang.logger'):get(...) 5 | local StreamHandler = require('jls.io.StreamHandler') 6 | 7 | --- A RangeStreamHandler class. 8 | -- This class allows to restrict the stream to pass to the wrapped handler to a specified range. 9 | -- @type RangeStreamHandler 10 | return require('jls.lang.class').create(StreamHandler.WrappedStreamHandler, function(rangeStreamHandler, super, RangeStreamHandler) 11 | 12 | --- Creates a @{StreamHandler} with a range. 13 | -- The data in the range will be pass to the wrapped handler. 14 | -- @tparam StreamHandler handler the handler to wrap 15 | -- @tparam[opt] number offset the offset of the range, default is 0 16 | -- @tparam[opt] number length the length of the range 17 | -- @function RangeStreamHandler:new 18 | function rangeStreamHandler:initialize(handler, offset, length) 19 | logger:finest('initialize(?, %s, %s)', offset, length) 20 | super.initialize(self, handler) 21 | self.first = offset or 0 22 | self.last = self.first + (length or math.maxinteger) - 1 23 | self.offset = 0 24 | self.preHandler = RangeStreamHandler.null 25 | self.postHandler = RangeStreamHandler.null 26 | end 27 | 28 | function rangeStreamHandler:onData(data) 29 | if data then 30 | local size = #data 31 | local first = self.offset 32 | self.offset = first + size 33 | if logger:isLoggable(logger.FINER) then 34 | logger:finer('onData(#%s) [%s-%s] => [%s-%s]', size, self.first, self.last, first, self.offset) 35 | end 36 | if first >= self.first and self.offset < self.last then 37 | return self.handler:onData(data) 38 | end 39 | if self.offset <= self.first then 40 | return self.preHandler:onData(data) 41 | end 42 | if first > self.last then 43 | return self.postHandler:onData(data) 44 | end 45 | local i = 1 46 | if self.first > first then 47 | i = self.first - first + 1 48 | end 49 | if i > 1 then 50 | self.preHandler:onData(string.sub(data, 1, i - 1)) 51 | end 52 | if self.offset <= self.last then 53 | return self.handler:onData(string.sub(data, i)) 54 | end 55 | local j = self.last - first + 1 56 | self.postHandler:onData(string.sub(data, j + 1)) 57 | return RangeStreamHandler.fill(self.handler, string.sub(data, i, j)) 58 | end 59 | if self.offset < self.last then 60 | self.offset = self.last 61 | return self.handler:onData() 62 | end 63 | return self.postHandler:onData() 64 | end 65 | 66 | end) 67 | -------------------------------------------------------------------------------- /tests/full/WebSocket.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local loader = require('jls.lang.loader') 4 | local loop = require('jls.lang.loopWithTimeout') 5 | local Map = require('jls.util.Map') 6 | local WebSocket = require('jls.net.http.WebSocket') 7 | local HttpServer = require('jls.net.http.HttpServer') 8 | 9 | local genCertificateAndPKey = loader.load('tests.genCertificateAndPKey') 10 | 11 | local CACERT_PEM, PKEY_PEM = genCertificateAndPKey() 12 | local TEST_PORT = 3002 13 | 14 | local function assert_send_receive(withH2) 15 | local scheme = 'ws' 16 | local server 17 | if withH2 then 18 | scheme = scheme..'s' 19 | server = HttpServer.createSecure({ 20 | key = PKEY_PEM, 21 | certificate = CACERT_PEM, 22 | alpnProtocols = {'h2'} 23 | }) 24 | else 25 | server = HttpServer:new() 26 | end 27 | local reply 28 | server:createContext('/ws/', Map.assign(WebSocket.UpgradeHandler:new(), { 29 | onOpen = function(_, webSocket, exchange) 30 | function webSocket:onTextMessage(payload) 31 | webSocket:sendTextMessage('You said '..payload):next(function() 32 | webSocket:close() 33 | server:close() 34 | end) 35 | end 36 | webSocket:readStart() 37 | end 38 | })) 39 | local webSocket = WebSocket:new(scheme..'://127.0.0.1:'..tostring(TEST_PORT)..'/ws/') 40 | webSocket:setSecureContext({ 41 | alpnProtocols = {'h2'}, 42 | cafile = CACERT_PEM 43 | }) 44 | server:bind('::', TEST_PORT):next(function() 45 | webSocket:open():next(function() 46 | function webSocket:onTextMessage(payload) 47 | reply = payload 48 | webSocket:close() 49 | end 50 | webSocket:readStart() 51 | webSocket:sendTextMessage('Hello') 52 | end) 53 | end) 54 | if not loop(function() 55 | webSocket:close() 56 | server:close() 57 | end) then 58 | lu.fail('Timeout reached') 59 | end 60 | lu.assertEquals(reply, 'You said Hello') 61 | end 62 | 63 | function Test_send_receive() 64 | assert_send_receive() 65 | end 66 | 67 | function Test_send_receive_h2() 68 | assert_send_receive(true) 69 | end 70 | 71 | function Test_applyMask() 72 | local values = {'', 'a', 'ab', 'abc', 'abcd', 'abcde'} 73 | local mask = WebSocket.generateMask() 74 | for _, value in ipairs(values) do 75 | local maskedValue = WebSocket.applyMask(mask, value) 76 | lu.assertEquals(#maskedValue, #value) 77 | if value ~= '' then 78 | lu.assertNotEquals(maskedValue, value) 79 | end 80 | lu.assertEquals(WebSocket.applyMask(mask, maskedValue), value) 81 | end 82 | end 83 | 84 | function _Test_applyMask_perf() 85 | local value = WebSocket.randomChars(8195) 86 | local mask = WebSocket.generateMask() 87 | for _ = 1, 1000 do 88 | WebSocket.applyMask(mask, value) 89 | end 90 | end 91 | 92 | os.exit(lu.LuaUnit.run()) 93 | -------------------------------------------------------------------------------- /jls/io/fs-.lua: -------------------------------------------------------------------------------- 1 | local isWindowsOS = string.sub(package.config, 1, 1) == '\\' 2 | 3 | local function execute(cmd) 4 | local s, k, c = os.execute(cmd) 5 | --print('execute', cmd, '=>', s, k, c) 6 | return s, k, c 7 | end 8 | 9 | local function popen(cmd, def, mode) 10 | local l 11 | local f = io.popen(cmd) 12 | if f then 13 | l = f:read(mode or 'l') 14 | f:close() 15 | end 16 | --print('popen', cmd, '=>', l or def) 17 | return l or def 18 | end 19 | 20 | return { 21 | utime = function(path, atime, mtime) 22 | atime = atime or os.time() 23 | mtime = mtime or atime 24 | if isWindowsOS then 25 | return execute('powershell $d=(Get-Date 1970-01-01).AddSeconds('..mtime..'); $f=Get-Item "'..path..'"; % $f.LastWriteTime=$d') 26 | end 27 | return execute('touch -m -d @'..mtime..' "'..path..'"') 28 | end, 29 | stat = function(path) 30 | local item 31 | if isWindowsOS then 32 | item = popen('if exist "'..path..'" powershell $f=Get-Item "'..path..'"; \'{0},{1},{2}\' -f $f.Mode,$f.Length,([DateTimeOffset]$f.LastWriteTime).ToUnixTimeSeconds()', '') 33 | else 34 | item = popen('stat -c "%F,%s,%Y" "'..path..'"', '') 35 | end 36 | if item then 37 | local mode, size, modification = string.match(item, '^([^,]*),([^,]*),([^,]*)$') 38 | if mode then 39 | return { 40 | mode = string.find(mode, '^d') and 'directory' or 'file', 41 | modification = tonumber(modification), 42 | size = tonumber(size) 43 | } 44 | end 45 | end 46 | return nil 47 | end, 48 | currentdir = function() 49 | return popen(isWindowsOS and 'cd' or 'pwd', '.') 50 | end, 51 | mkdir = function(path) 52 | return execute('mkdir "'..path..'"') 53 | end, 54 | rmdir = function(path) 55 | return execute('rmdir "'..path..'"') 56 | end, 57 | unlink = function(path) 58 | return os.remove(path) 59 | end, 60 | rename = function(path, newPath) 61 | return os.rename(path, newPath) 62 | end, 63 | copyfile = function(path, newPath) 64 | local fd, err = io.open(path, 'rb') 65 | if not fd then 66 | return nil, err 67 | end 68 | local data = fd:read('*a') 69 | fd:close() 70 | fd, err = io.open(newPath, 'wb') 71 | if not fd then 72 | return nil, err 73 | end 74 | fd:write(data) 75 | fd:close() 76 | end, 77 | dir = function(path) 78 | local f = io.popen((isWindowsOS and 'dir /b "' or 'ls -1 "')..path..'"') 79 | local names = {} 80 | if f then 81 | while true do 82 | local name = f:read('l') 83 | if name == nil then 84 | break 85 | end 86 | table.insert(names, name) 87 | end 88 | f:close() 89 | end 90 | local i = 0 91 | return function() 92 | i = i + 1 93 | return names[i] 94 | end 95 | end 96 | } 97 | -------------------------------------------------------------------------------- /jls/util/Struct.lua: -------------------------------------------------------------------------------- 1 | --- Provide Struct class. 2 | -- @module jls.util.Struct 3 | -- @pragma nostrip 4 | 5 | local strings = require('jls.util.strings') 6 | 7 | --- The Struct class. 8 | -- The Struct provides a way to represents C like structure. 9 | -- It allows decode or encode a table based on its fields. 10 | -- @type Struct 11 | return require('jls.lang.class').create(function(struct) 12 | 13 | --- Creates a new Struct. 14 | -- @function Struct:new 15 | -- @tparam table structDef the fields structure definition with name and type, convertion option 16 | -- @tparam[opt] string byteOrder '<', '>' or '=' for little, big or native endian 17 | -- @return a new Struct 18 | function struct:initialize(structDef, byteOrder) 19 | self.struct = structDef or {} 20 | local format = byteOrder or '=' 21 | local fixedSize = true 22 | for _, def in ipairs(self.struct) do 23 | local ct = string.gsub(def.type, '^S(%d+)$', 'c%1') 24 | if string.find(ct, '^[sz]') then 25 | fixedSize = false 26 | end 27 | format = format..ct 28 | end 29 | self.format = format 30 | self.size = fixedSize and string.packsize(self.format) or -1 31 | end 32 | 33 | --- Returns the size of this Struct that is the total size of its fields. 34 | -- @treturn number the size of this Struct. 35 | function struct:getSize() 36 | return self.size 37 | end 38 | 39 | --- Decodes the specifed byte array as a string. 40 | -- @tparam string s the value to decode as a string 41 | -- @treturn table the decoded values. 42 | function struct:fromString(s) 43 | local t = {} 44 | local values = table.pack(string.unpack(self.format, s)) 45 | for i, def in ipairs(self.struct) do 46 | local value = values[i] 47 | if string.find(def.type, '^S') then 48 | value = string.gsub(value, '\0*$', '') 49 | end 50 | t[def.name] = value 51 | end 52 | return t 53 | end 54 | 55 | --- Encodes the specifed values provided as a table. 56 | -- @tparam string t the values to encode as a table 57 | -- @tparam[opt] boolean strict true to indicate that all the value are expected 58 | -- @treturn string the encoded values as a string. 59 | function struct:toString(t, strict) 60 | if type(t) ~= 'table' then 61 | return '' 62 | end 63 | local values = {} 64 | for i, def in ipairs(self.struct) do 65 | local value = t[def.name] 66 | if value == nil then 67 | if strict then 68 | error('Missing value for field "'..tostring(def.name)..'" at index '..tostring(i)) 69 | end 70 | if string.find(def.type, '^[csSz]') then 71 | value = '' 72 | else 73 | value = 0 74 | end 75 | end 76 | table.insert(values, value) 77 | end 78 | return string.pack(self.format, table.unpack(values)) 79 | end 80 | 81 | end) 82 | -------------------------------------------------------------------------------- /tests/full/zip.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Deflater = require('jls.util.zip.Deflater') 4 | local Inflater = require('jls.util.zip.Inflater') 5 | local Codec = require('jls.util.Codec') 6 | local base64 = Codec.getInstance('base64') 7 | 8 | local EMPTY_DEFLATED = base64:decode('eJwDAAAAAAE=') 9 | local SPACES_INFLATED = ' ' 10 | local SPACES_DEFLATED = base64:decode('eJxTUKAuAACVXwoB') 11 | local SPACES_DEFLATED_100 = base64:decode('eJztwYEAAAAAgCCV/SkXqQoAAAAAAAAAGKcS6C4=') 12 | local HELLO_WORLD_INFLATED = 'Hello world!' 13 | local HELLO_WORLD_DEFLATED = base64:decode('eJzzSM3JyVcozy/KSVEEAB0JBF4=') 14 | 15 | local VALUES = {'', 'a', 'ab', 'abc', HELLO_WORLD_INFLATED} 16 | 17 | local function print_base64_deflated(s) 18 | print('"'..s..'" => '..base64:encode(Deflater:new():deflate(s, 'finish'))) 19 | end 20 | 21 | local function print_base64_deflated_n(s, n) 22 | local deflater = Deflater:new() 23 | local deflated = '' 24 | for i = 1, n do 25 | deflated = deflated..deflater:deflate(s) 26 | end 27 | deflated = deflated..deflater:finish() 28 | print('"'..s..'" x '..tostring(n)..' => '..base64:encode(deflated)) 29 | end 30 | 31 | local function print_base64_deflated_l(l) 32 | local deflater = Deflater:new() 33 | local deflated = '' 34 | for i, s in ipairs(l) do 35 | deflated = deflated..deflater:deflate(s) 36 | print('['..tostring(i)..'] => '..base64:encode(deflated)) 37 | end 38 | deflated = deflated..deflater:finish() 39 | print('['..tostring(#l)..'] => '..base64:encode(deflated)) 40 | end 41 | 42 | --[[] 43 | print_base64_deflated('') 44 | print_base64_deflated(SPACES_INFLATED) 45 | print_base64_deflated(HELLO_WORLD_INFLATED) 46 | print_base64_deflated_n(SPACES_INFLATED, 100) 47 | ]] 48 | 49 | function Test_deflate() 50 | lu.assertEquals(Deflater:new():deflate(HELLO_WORLD_INFLATED, 'finish'), HELLO_WORLD_DEFLATED) 51 | end 52 | 53 | function Test_inflate() 54 | lu.assertEquals(Inflater:new():inflate(HELLO_WORLD_DEFLATED), HELLO_WORLD_INFLATED) 55 | end 56 | 57 | local function assertDeflateInflate(value, compressionLevel, windowBits) 58 | local deflated = Deflater:new(compressionLevel, windowBits):deflate(value, 'finish') 59 | local inflated = Inflater:new(windowBits):inflate(deflated) 60 | lu.assertEquals(inflated, value) 61 | end 62 | 63 | local function assertDeflateInflateAll(values, compressionLevel, windowBits) 64 | for _, value in ipairs(values) do 65 | assertDeflateInflate(value, compressionLevel, windowBits) 66 | end 67 | end 68 | 69 | function Test_deflate_inflate() 70 | assertDeflateInflateAll(VALUES) 71 | end 72 | 73 | function Test_deflate_inflate_compressionLevel() 74 | assertDeflateInflateAll(VALUES, 1) 75 | assertDeflateInflateAll(VALUES, 9) 76 | end 77 | 78 | function Test_deflate_inflate_windowBits() 79 | assertDeflateInflateAll(VALUES, nil, -15) 80 | end 81 | 82 | os.exit(lu.LuaUnit.run()) 83 | -------------------------------------------------------------------------------- /tests/base/Map.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local Map = require("jls.util.Map") 4 | 5 | local function sort(t) 6 | table.sort(t) 7 | return t 8 | end 9 | 10 | function Test_set() 11 | local m = Map:new() 12 | m:set('a', true) 13 | m:set('b', 'A value') 14 | m:set('c', 1) 15 | lu.assertEquals(m.map, {a = true, b = 'A value', c = 1}) 16 | lu.assertTrue(m:delete('b')) 17 | lu.assertFalse(m:delete('b')) 18 | lu.assertEquals(m.map, {a = true, c = 1}) 19 | m:clear() 20 | lu.assertEquals(m.map, {}) 21 | end 22 | 23 | function Test_get() 24 | local m = Map:new() 25 | m:set('a', true) 26 | m:set('b', 'A value') 27 | m:set('c', 1) 28 | lu.assertEquals(m:get('a'), true) 29 | lu.assertEquals(m:get('b'), 'A value') 30 | lu.assertEquals(m:get('c'), 1) 31 | end 32 | 33 | function Test_has() 34 | local m = Map:new() 35 | m:set('a', true) 36 | m:set('b', 'A value') 37 | m:set('c', 1) 38 | lu.assertTrue(m:has('a')) 39 | lu.assertTrue(m:has('b')) 40 | lu.assertTrue(m:has('c')) 41 | end 42 | 43 | function Test_size() 44 | lu.assertEquals(Map.size({a = true, b = 'A value', c = 1}), 3) 45 | lu.assertEquals(Map.size({}), 0) 46 | end 47 | 48 | function Test_keys() 49 | lu.assertEquals(sort(Map.keys({a = true, b = 'A value', c = 1})), {'a', 'b', 'c'}) 50 | end 51 | 52 | function Test_values() 53 | lu.assertEquals(sort(Map.values({a = 1, b = 2, c = 3})), {1, 2, 3}) 54 | end 55 | 56 | function Test_spairs() 57 | local keyValues = {} 58 | for k, v in Map.spairs({a = 1, c = 3, b = 2}) do 59 | table.insert(keyValues, {k, v}) 60 | end 61 | lu.assertEquals(keyValues, {{'a', 1}, {'b', 2}, {'c', 3}}) 62 | end 63 | 64 | function Test_pairs() 65 | if _VERSION == 'Lua 5.1' then 66 | print('/!\\ skipping test due to Lua version') 67 | lu.success() 68 | return 69 | end 70 | local m = {a = 1, c = 3, b = 2} 71 | local keyValues = Map:new(m) 72 | local n = {} 73 | for k, v in pairs(keyValues) do 74 | n[k] = v 75 | end 76 | lu.assertEquals(n, m) 77 | end 78 | 79 | function Test_assign() 80 | lu.assertEquals(Map.assign({}), {}) 81 | lu.assertEquals(Map.assign({}, {a = true}), {a = true}) 82 | lu.assertEquals(Map.assign({}, nil, {a = true}), {a = true}) 83 | lu.assertEquals(Map.assign({a = true}, {}), {a = true}) 84 | lu.assertEquals(Map.assign({a = true}, {b = true}), {a = true, b = true}) 85 | lu.assertEquals(Map.assign({}, {a = true}, {b = true}), {a = true, b = true}) 86 | end 87 | 88 | function Test_reverse() 89 | lu.assertEquals(Map.reverse({k1 = 'v1', k2 = 'v2'}), {v1 = 'k1', v2 = 'k2'}) 90 | lu.assertEquals(Map.reverse({k = 'v'}), {v = 'k'}) 91 | lu.assertEquals(Map.reverse({}), {}) 92 | end 93 | 94 | function Test_collision() 95 | local m = Map() 96 | lu.assertNil(m:get('get')) 97 | m:set('get', true) 98 | lu.assertNotNil(m:get('get')) 99 | end 100 | 101 | os.exit(lu.LuaUnit.run()) 102 | -------------------------------------------------------------------------------- /jls/net/http/filter/BasicAuthenticationHttpFilter.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple HTTP filter for basic authentication. 2 | -- @module jls.net.http.filter.BasicAuthenticationHttpFilter 3 | -- @pragma nostrip 4 | 5 | local logger = require('jls.lang.logger'):get(...) 6 | local Codec = require('jls.util.Codec') 7 | local HTTP_CONST = require('jls.net.http.HttpMessage').CONST 8 | 9 | --- A BasicAuthenticationHttpFilter class. 10 | -- @type BasicAuthenticationHttpFilter 11 | return require('jls.lang.class').create('jls.net.http.HttpFilter', function(filter) 12 | 13 | local function checkAnyCredentials() 14 | return true 15 | end 16 | 17 | --- Creates a basic authentication @{HttpFilter}. 18 | -- @param checkCredentials a table with user name and password pairs or a function. 19 | -- @tparam[opt] string realm an optional message. 20 | -- @function BasicAuthenticationHttpFilter:new 21 | function filter:initialize(checkCredentials, realm) 22 | if type(checkCredentials) == 'function' then 23 | self.checkCredentials = checkCredentials 24 | elseif type(checkCredentials) == 'table' then 25 | self.checkCredentials = function(user, password) 26 | return checkCredentials[user] == password 27 | end 28 | else 29 | self.checkCredentials = checkAnyCredentials 30 | end 31 | self.realm = realm or 'User Visible Realm' 32 | end 33 | 34 | function filter:onAuthorizationFailed(exchange, user) 35 | logger:warn('basicAuthentication() user "%s" from %s is not authorized', user, exchange:clientAsString()) 36 | end 37 | 38 | function filter:doFilter(exchange) 39 | local request = exchange:getRequest() 40 | local response = exchange:getResponse() 41 | local authorization = request:getHeader(HTTP_CONST.HEADER_AUTHORIZATION) 42 | if not authorization then 43 | response:setHeader(HTTP_CONST.HEADER_WWW_AUTHENTICATE, 'Basic realm="'..self.realm..'"') 44 | response:setStatusCode(HTTP_CONST.HTTP_UNAUTHORIZED, 'Unauthorized') 45 | return false 46 | end 47 | logger:finest('basicAuthentication() authorization: "%s"', authorization) 48 | if string.find(authorization, 'Basic ') == 1 then 49 | authorization = Codec.decode('base64', string.sub(authorization, 7)) 50 | if authorization then 51 | local user, password = string.match(authorization, '^([^:]+):(.+)$') 52 | if user then 53 | if self.checkCredentials(user, password) then 54 | return 55 | end 56 | response:setHeader(HTTP_CONST.HEADER_WWW_AUTHENTICATE, 'Basic realm="'..self.realm..'"') 57 | response:setStatusCode(HTTP_CONST.HTTP_UNAUTHORIZED, 'Unauthorized') 58 | self:onAuthorizationFailed(exchange, user) 59 | return false 60 | end 61 | end 62 | end 63 | logger:warn('Bad authentication request from %s', exchange:clientAsString()) 64 | response:setStatusCode(HTTP_CONST.HTTP_BAD_REQUEST, 'Bad request') 65 | return false 66 | end 67 | 68 | end) 69 | -------------------------------------------------------------------------------- /tests/base/xml.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local xml = require("jls.util.xml") 4 | 5 | local function normalize(s) 6 | local ns = s 7 | ns = string.gsub(ns, '^%s*', '') 8 | ns = string.gsub(ns, '%s*\r?\n%s*', '') 9 | return ns 10 | end 11 | 12 | local function checkEncodeDecode(s) 13 | local ds = xml.decode(s) 14 | local es = xml.encode(ds) 15 | lu.assertEquals(normalize(es), normalize(s)) 16 | end 17 | 18 | function Test_decode_encode() 19 | checkEncodeDecode([[ 20 | 21 | Manoel 22 | Palmas-TO 23 | 24 | 25 | University of Brasília 26 | Brasília-DF 27 | 28 | ]]) 29 | end 30 | 31 | -- 32 | 33 | function Test_decode_encode_ns() 34 | checkEncodeDecode([[text]]) 35 | end 36 | 37 | function Test_decode_encode_2() 38 | checkEncodeDecode('A value') 39 | end 40 | 41 | function Test_decode_encode_escaped() 42 | checkEncodeDecode([["2' & 2]]) 43 | end 44 | 45 | local function getSampleXmlTable() 46 | return { 47 | name = 'people', 48 | { 49 | name = 'person', 50 | attr = {type = 'fiction'}, 51 | {name = 'name', 'Luigi'} 52 | } 53 | } 54 | end 55 | 56 | local function getSampleXmlTable2() 57 | return { 58 | name = 'people', 59 | { 60 | name = 'person', 61 | attr = {type = 'fiction'}, 62 | {name = 'name', 'Luigi'} 63 | }, 64 | { 65 | name = 'person', 66 | attr = {type = 'real'}, 67 | {name = 'name', 'Mario'} 68 | } 69 | } 70 | end 71 | 72 | function Test_encode_decode() 73 | local t = getSampleXmlTable() 74 | lu.assertEquals(xml.decode(xml.encode(t)), t) 75 | end 76 | 77 | function Test_decode() 78 | lu.assertEquals(xml.decode('A value'), 79 | {name = 'a', {name = 'b', attr = {c = 'c'}, 'A value'}}) 80 | end 81 | 82 | function Test_encode() 83 | lu.assertEquals(xml.encode(getSampleXmlTable()), 84 | 'Luigi') 85 | end 86 | 87 | function Test_encode_2() 88 | lu.assertEquals(xml.encode(getSampleXmlTable2()), 89 | 'LuigiMario') 90 | end 91 | 92 | function Test_encode_3() 93 | lu.assertEquals(xml.encode({name = 'a', {name = 'b', attr = {c = 'c'}, 'A value'}}), 94 | 'A value') 95 | end 96 | 97 | function Test_encode_ns() 98 | lu.assertEquals(xml.encode(xml.setNamespace(getSampleXmlTable(), 'DNS:', 'D')), 99 | 'Luigi') 100 | end 101 | 102 | os.exit(lu.LuaUnit.run()) 103 | -------------------------------------------------------------------------------- /tests/base/PromiseAsync.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local event = require('jls.lang.event') 4 | local Promise = require('jls.lang.Promise') 5 | local Exception = require('jls.lang.Exception') 6 | 7 | local function future(value, millis) 8 | return Promise:new(function(resolve, reject) 9 | event:setTimeout(function() 10 | if Promise.isPromise(value) then 11 | value:next(resolve, reject) 12 | else 13 | resolve(value) 14 | end 15 | end, millis or 0) 16 | end) 17 | end 18 | 19 | local function printReason(reason) 20 | print('reason:', reason) 21 | end 22 | 23 | if _VERSION == 'Lua 5.1' then 24 | -- attempt to yield across metamethod/C-call boundary 25 | print('/!\\ skipping tests due to Lua version') 26 | os.exit(lu.LuaUnit.run()) 27 | end 28 | 29 | function Test_async_await() 30 | local v 31 | Promise.async(function(await, w) 32 | return await(future(w + 1)) 33 | end, 1):next(function(r) 34 | v = r 35 | end):catch(printReason) 36 | lu.assertNil(v) 37 | event:loop() 38 | lu.assertEquals(v, 2) 39 | end 40 | 41 | function Test_async_await_n() 42 | local v 43 | Promise.async(function(await, n) 44 | local w = 0 45 | for i = 1, n do 46 | w = await(future(w + 1)) 47 | end 48 | return w 49 | end, 3):next(function(r) 50 | v = r 51 | end):catch(printReason) 52 | lu.assertNil(v) 53 | event:loop() 54 | lu.assertEquals(v, 3) 55 | end 56 | 57 | function Test_async_await_error() 58 | --[[ 59 | local cr = coroutine.create(function() 60 | local function r() 61 | error('ouch') 62 | --void() 63 | end 64 | r() 65 | end) 66 | print('resume:', coroutine.resume(cr)) 67 | print('traceback:', debug.traceback(cr, nil, 1)) 68 | ]] 69 | local v 70 | Promise.async(function(await) 71 | await(future()) 72 | local function aFunction() 73 | error('ouch', 0) 74 | --void() 75 | end 76 | aFunction() 77 | end):catch(function(e) 78 | v = e 79 | end) 80 | lu.assertNil(v) 81 | event:loop() 82 | --print(v) 83 | lu.assertEquals(Exception.getMessage(v), 'ouch') 84 | end 85 | 86 | function Test_async_await_reject() 87 | local v 88 | Promise.async(function(await) 89 | await(future(Promise.reject('ouch'))) 90 | end):catch(function(e) 91 | v = e 92 | end) 93 | lu.assertNil(v) 94 | event:loop() 95 | lu.assertEquals(Exception.getMessage(v), 'ouch') 96 | end 97 | 98 | function Test_async_n_await() 99 | local function asyncInc(n) 100 | return Promise.async(function(await, m) 101 | return await(future(m + 1)) 102 | end, n) 103 | end 104 | local v 105 | Promise.async(function(await, n) 106 | return await(asyncInc(n)) * await(asyncInc(n)) 107 | end, 1):next(function(r) 108 | v = r 109 | end):catch(printReason) 110 | lu.assertNil(v) 111 | event:loop() 112 | lu.assertEquals(v, 4) 113 | end 114 | 115 | os.exit(lu.LuaUnit.run()) 116 | -------------------------------------------------------------------------------- /jls/util/cd/hex.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | 3 | -- see openssl.hex(msg, true) 4 | 5 | local string_char = string.char 6 | local string_byte = string.byte 7 | 8 | -- character to nibble 9 | local function c2n(c) 10 | if c >= 48 and c <= 57 then 11 | return c - 48 12 | elseif c >= 65 and c <= 70 then 13 | return c - 55 14 | elseif c >= 97 and c <= 102 then 15 | return c - 87 16 | end 17 | error('invalid hexa character '..tostring(c)) 18 | end 19 | 20 | -- nibble to character 21 | local function n2c(n) 22 | local m = n & 0xf 23 | if m < 10 then 24 | return 48 + m 25 | end 26 | return 87 + m 27 | end 28 | local function n2cl(n) 29 | local m = n & 0xf 30 | if m < 10 then 31 | return 48 + m 32 | end 33 | return 55 + m 34 | end 35 | 36 | local function decoden(cc) 37 | local c1, c2 = string_byte(cc, 1, 2) 38 | local hn, ln = c2n(c1), c2n(c2) 39 | return string_char((hn << 4) + ln) 40 | end 41 | local function decode(value) 42 | return (string.gsub(value, '..', decoden)) 43 | end 44 | 45 | local function encodec(c) 46 | local b = string_byte(c) 47 | return string_char(n2c(b >> 4), n2c(b)) 48 | end 49 | local function encodecl(c) 50 | local b = string_byte(c) 51 | return string_char(n2cl(b >> 4), n2cl(b)) 52 | end 53 | local function encode(value, lc) 54 | return (string.gsub(value, '.', lc and encodecl or encodec)) 55 | end 56 | 57 | local DecodeStreamHandler = class.create('jls.io.streams.BlockStreamHandler', function(decodeStreamHandler, super) 58 | function decodeStreamHandler:initialize(handler) 59 | super.initialize(self, handler, 2, true) 60 | end 61 | function decodeStreamHandler:onData(data) 62 | return self.handler:onData(data and decode(data)) 63 | end 64 | end) 65 | 66 | local EncodeStreamHandler = class.create(require('jls.io.StreamHandler').WrappedStreamHandler, function(encodeStreamHandler, super) 67 | function encodeStreamHandler:initialize(handler, upperCase) 68 | super.initialize(self, handler) 69 | self.upperCase = upperCase 70 | end 71 | function encodeStreamHandler:onData(data) 72 | return self.handler:onData(data and encode(data, self.upperCase)) 73 | end 74 | end) 75 | 76 | return require('jls.lang.class').create('jls.util.Codec', function(hex) 77 | 78 | function hex:initialize(upperCase, ignoreSpaces) 79 | self.upperCase = upperCase 80 | self.ignoreSpaces = ignoreSpaces 81 | end 82 | 83 | function hex:decode(value) 84 | if self.ignoreSpaces then 85 | return decode(string.gsub(value, '%s+', '')) 86 | end 87 | return decode(value) 88 | end 89 | 90 | function hex:encode(value) 91 | return encode(value, self.upperCase) 92 | end 93 | 94 | function hex:decodeStream(sh) 95 | return DecodeStreamHandler:new(sh) 96 | end 97 | 98 | function hex:encodeStream(sh) 99 | return EncodeStreamHandler:new(sh, self.upperCase) 100 | end 101 | 102 | function hex:getName() 103 | return 'hex' 104 | end 105 | 106 | end) 107 | -------------------------------------------------------------------------------- /jls/io/streams/DelayedStreamHandler.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | Provide a delayed stream handler. 3 | 4 | This class allows to buffer a stream while the sub handler is not available. 5 | 6 | @module jls.io.streams.DelayedStreamHandler 7 | @pragma nostrip 8 | ]] 9 | 10 | local logger = require('jls.lang.logger'):get(...) 11 | local StringBuffer = require('jls.lang.StringBuffer') 12 | local StreamHandler = require('jls.io.StreamHandler') 13 | 14 | --- A DelayedStreamHandler class. 15 | -- @type DelayedStreamHandler 16 | return require('jls.lang.class').create(StreamHandler.WrappedStreamHandler, function(delayedStreamHandler, super) 17 | 18 | --- Creates a delayed @{StreamHandler}. 19 | -- @function DelayedStreamHandler:new 20 | function delayedStreamHandler:initialize() 21 | logger:finest('initialize()') 22 | super.initialize(self) 23 | self.buffer = StringBuffer:new() 24 | self.error = nil 25 | self.ended = false 26 | self.closed = false 27 | end 28 | 29 | --- Sets the sub handler. 30 | -- The buffered and future data will be passed to the sub handler. 31 | -- @tparam StreamHandler handler the handler to use 32 | function delayedStreamHandler:setStreamHandler(handler) 33 | if handler and not self.handler then 34 | if self.error then 35 | handler:onError(self.error) 36 | else 37 | if self.buffer:length() > 0 then 38 | for _, part in ipairs(self.buffer:getParts()) do 39 | handler:onData(part) 40 | end 41 | end 42 | if self.ended then 43 | handler:onData() 44 | end 45 | end 46 | if self.closed then 47 | handler:close() 48 | end 49 | self.buffer = nil 50 | self.error = nil 51 | end 52 | self.handler = handler 53 | end 54 | 55 | function delayedStreamHandler:isEnded() 56 | return self.ended 57 | end 58 | 59 | function delayedStreamHandler:isClosed() 60 | return self.closed 61 | end 62 | 63 | function delayedStreamHandler:getError() 64 | return self.error 65 | end 66 | 67 | function delayedStreamHandler:getStringBuffer() 68 | return self.buffer 69 | end 70 | 71 | function delayedStreamHandler:getBuffer() 72 | return self.buffer:toString() 73 | end 74 | 75 | function delayedStreamHandler:onData(data) 76 | if self.handler then 77 | return self.handler:onData(data) 78 | end 79 | logger:finer('onData(#%l)', data) 80 | if data then 81 | self.buffer:append(data) 82 | else 83 | self.ended = true 84 | end 85 | end 86 | 87 | function delayedStreamHandler:onError(err) 88 | if self.handler then 89 | self.handler:onError(err) 90 | else 91 | self.ended = true 92 | if not self.error then 93 | self.error = err 94 | end 95 | end 96 | end 97 | 98 | function delayedStreamHandler:close() 99 | if self.handler then 100 | self.handler:close() 101 | else 102 | self.closed = true 103 | end 104 | end 105 | 106 | end) 107 | -------------------------------------------------------------------------------- /tests/base/EventPublisher.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local EventPublisher = require("jls.util.EventPublisher") 4 | 5 | function Test_subscribe_publish() 6 | local v = 0 7 | local ep = EventPublisher:new() 8 | lu.assertFalse(ep:publishEvent('test')) 9 | lu.assertEquals(v, 0) 10 | ep:subscribeEvent('test', function() 11 | v = v + 1 12 | end) 13 | lu.assertEquals(v, 0) 14 | lu.assertTrue(ep:publishEvent('test')) 15 | lu.assertEquals(v, 1) 16 | lu.assertTrue(ep:publishEvent('test')) 17 | lu.assertEquals(v, 2) 18 | end 19 | 20 | function Test_subscribe_publish_args() 21 | local v = 0 22 | local va, vb 23 | local ep = EventPublisher:new() 24 | ep:publishEvent('test') 25 | lu.assertEquals(v, 0) 26 | ep:subscribeEvent('test', function(a, b) 27 | v = v + 1 28 | va = a 29 | vb = b 30 | end) 31 | lu.assertEquals(v, 0) 32 | lu.assertNil(va) 33 | lu.assertNil(vb) 34 | ep:publishEvent('test', 'Hi', 123) 35 | lu.assertEquals(v, 1) 36 | lu.assertEquals(va, 'Hi') 37 | lu.assertEquals(vb, 123) 38 | ep:publishEvent('test') 39 | lu.assertEquals(v, 2) 40 | lu.assertNil(va) 41 | lu.assertNil(vb) 42 | end 43 | 44 | function Test_subscribe_publish_error() 45 | local capturedError 46 | local ep = EventPublisher:new() 47 | ep:subscribeEvent('test', function(a) 48 | error('Test') 49 | end) 50 | lu.assertFalse(pcall(function() 51 | ep:publishEvent('test') 52 | end)) 53 | ep:subscribeEvent('error', function(err) 54 | capturedError = err or true 55 | end) 56 | lu.assertNil(capturedError) 57 | lu.assertTrue(pcall(function() 58 | ep:publishEvent('test') 59 | end)) 60 | lu.assertNotNil(capturedError) 61 | end 62 | 63 | function Test_subscribes_publish() 64 | local v = 0 65 | local ep = EventPublisher:new() 66 | ep:publishEvent('test') 67 | lu.assertEquals(v, 0) 68 | ep:subscribeEvent('test', function() 69 | v = v + 1 70 | end) 71 | ep:subscribeEvent('test', function() 72 | v = v + 100 73 | end) 74 | lu.assertEquals(v, 0) 75 | ep:publishEvent('test') 76 | lu.assertEquals(v, 101) 77 | ep:publishEvent('test') 78 | lu.assertEquals(v, 202) 79 | end 80 | 81 | function Test_unsubscribe() 82 | local v = 0 83 | local ep = EventPublisher:new() 84 | lu.assertFalse(ep:unsubscribeEvent('test', {})) 85 | local eventFn = ep:subscribeEvent('test', function() 86 | v = v + 1 87 | end) 88 | ep:publishEvent('test') 89 | lu.assertEquals(v, 1) 90 | lu.assertTrue(ep:unsubscribeEvent('test', eventFn)) 91 | ep:publishEvent('test') 92 | lu.assertFalse(ep:unsubscribeEvent('test', eventFn)) 93 | lu.assertEquals(v, 1) 94 | end 95 | 96 | function Test_unsubscribeAllEvents() 97 | local v = 0 98 | local ep = EventPublisher:new() 99 | ep:subscribeEvent('test', function() 100 | v = v + 1 101 | end) 102 | ep:publishEvent('test') 103 | lu.assertEquals(v, 1) 104 | ep:unsubscribeAllEvents() 105 | ep:publishEvent('test') 106 | lu.assertEquals(v, 1) 107 | end 108 | 109 | os.exit(lu.LuaUnit.run()) 110 | -------------------------------------------------------------------------------- /examples/browser.lua: -------------------------------------------------------------------------------- 1 | local logger = require('jls.lang.logger') 2 | local system = require('jls.lang.system') 3 | local WebView = require('jls.util.WebView') 4 | local tables = require('jls.util.tables') 5 | 6 | local CONFIG_SCHEMA = { 7 | title = 'Tiny Web Browser', 8 | type = 'object', 9 | additionalProperties = false, 10 | properties = { 11 | url = { 12 | title = 'An URL to open, could point to a local file', 13 | type = 'string' 14 | }, 15 | ['search-url'] = { 16 | title = 'The search URL', 17 | type = 'string', 18 | default = 'https://www.google.com/search?q=' 19 | }, 20 | webview = { 21 | type = 'object', 22 | additionalProperties = false, 23 | properties = { 24 | title = { 25 | title = 'The window title', 26 | type = 'string', 27 | default = 'Tiny Web Browser' 28 | }, 29 | width = { 30 | title = 'The window width', 31 | type = 'integer', 32 | default = 1024, 33 | minimum = 320, 34 | maximum = 65535, 35 | }, 36 | height = { 37 | title = 'The window width', 38 | type = 'integer', 39 | default = 768, 40 | minimum = 240, 41 | maximum = 65535, 42 | }, 43 | resizable = { 44 | title = 'True to allow window size change', 45 | type = 'boolean', 46 | default = true 47 | }, 48 | ['debug'] = { 49 | title = 'Enables the browser devtools', 50 | type = 'boolean', 51 | default = false 52 | }, 53 | } 54 | } 55 | } 56 | } 57 | 58 | local config = tables.createArgumentTable(system.getArguments(), { 59 | configPath = 'config', 60 | emptyPath = 'url', 61 | helpPath = 'help', 62 | logPath = 'log-level', 63 | aliases = { 64 | h = 'help', 65 | s = 'search-url', 66 | t = 'webview.title', 67 | width = 'webview.width', 68 | height = 'webview.height', 69 | r = 'webview.resizable', 70 | d = 'webview.debug', 71 | ll = 'log-level', 72 | }, 73 | schema = CONFIG_SCHEMA 74 | }) 75 | 76 | local dataUrl = config.url 77 | if not dataUrl then 78 | dataUrl = WebView.toDataUrl([[ 79 | 80 | 81 | 82 | 98 | 99 | ]]) 100 | end 101 | 102 | local webview = WebView:new(dataUrl, config.webview) 103 | logger:fine('Enters WebView loop') 104 | webview:loop() 105 | logger:fine('WebView loop ended') 106 | -------------------------------------------------------------------------------- /tests/genCertificateAndPKey.lua: -------------------------------------------------------------------------------- 1 | local logger = require('jls.lang.logger') 2 | local File = require('jls.io.File') 3 | local Date = require('jls.util.Date') 4 | 5 | local opensslLib = require('openssl') 6 | 7 | local function createPrivateKeyAndCertificates() 8 | -- openssl x509 -in tests/cacert.pem -text 9 | local pkey = opensslLib.pkey.new() 10 | local cadn = opensslLib.x509.name.new({{commonName='localhost'}, {C='ZZ'}, {O='JLS'}}) 11 | local req = opensslLib.x509.req.new(cadn, pkey) 12 | local ext = opensslLib.x509.extension.new_extension({object='subjectAltName', value='IP:127.0.0.1'}) 13 | req:extensions({ext}) 14 | local cacert = opensslLib.x509.new(1, req) 15 | cacert:validat(os.time(), os.time() + 3600*24*365) 16 | cacert:sign(pkey, cacert) --self sign 17 | -- invalid 18 | local reqi = opensslLib.x509.req.new(cadn, pkey) 19 | reqi:extensions({ext}) 20 | local cacerti = opensslLib.x509.new(1, reqi) 21 | cacerti:validat(os.time() - 3600*24*365, os.time() - 3600*24) 22 | cacerti:sign(pkey, cacerti) 23 | -- unkown host 24 | local cadnu = opensslLib.x509.name.new({{commonName='NA'}, {C='ZZ'}, {O='JLS'}}) 25 | local requ = opensslLib.x509.req.new(cadnu, pkey) 26 | local cacertu = opensslLib.x509.new(1, requ) 27 | cacertu:validat(os.time(), os.time() + 3600*24*365) 28 | cacertu:sign(pkey, cacertu) --self sign 29 | return pkey, cacert, cacerti, cacertu 30 | end 31 | 32 | local function writePrivateKeyAndCertificates(...) 33 | local files = {...} 34 | local items = {createPrivateKeyAndCertificates()} 35 | -- pkey:export('pem', true, 'secret') -- format='pem' raw=true, passphrase='secret' 36 | for i, item in ipairs(items) do 37 | local data = item:export('pem') 38 | local file = files[i] 39 | if file then 40 | file:write(data) 41 | end 42 | end 43 | end 44 | 45 | local function readCertificate(certFile) 46 | return opensslLib.x509.read(certFile:readAll()) 47 | end 48 | 49 | return function(caCertPem, pKeyPem) 50 | local cacertFile = File:new(caCertPem or 'tests/cacert.pem') 51 | local pkeyFile = File:new(pKeyPem or 'tests/pkey.pem') 52 | local cacertInvalidFile = File:new(caCertPem or 'tests/cacert-invalid.pem') 53 | local cacertUnknownFile = File:new(caCertPem or 'tests/cacert-unknown.pem') 54 | if not (cacertFile:isFile() and pkeyFile:isFile() and cacertInvalidFile:isFile() and cacertUnknownFile:isFile()) then 55 | logger:info('creating private key and certificates') 56 | writePrivateKeyAndCertificates(pkeyFile, cacertFile, cacertInvalidFile, cacertUnknownFile) 57 | else 58 | local cert = readCertificate(cacertFile) 59 | local isValid, notbefore, notafter = cert:validat() 60 | local notafterDate = Date:new(notafter:get() * 1000) 61 | local notafterText = notafterDate:toISOString(true) 62 | logger:info('certificate valid until %s', notafterText) 63 | if not isValid then 64 | logger:warn('re-creating invalid certificate') 65 | writePrivateKeyAndCertificates(pkeyFile, cacertFile, cacertInvalidFile, cacertUnknownFile) 66 | end 67 | end 68 | return cacertFile:getPath(), pkeyFile:getPath(), cacertInvalidFile:getPath(), cacertUnknownFile:getPath() 69 | end 70 | -------------------------------------------------------------------------------- /tests/full/http_form.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local form = require('jls.net.http.form') 4 | local HttpMessage = require('jls.net.http.HttpMessage') 5 | local HttpHeaders = require('jls.net.http.HttpHeaders') 6 | local strings = require('jls.util.strings') 7 | 8 | function Test_create_parse_form() 9 | local request = HttpMessage:new() 10 | local msg1 = HttpMessage:new() 11 | local msg2 = HttpMessage:new() 12 | local messages = {msg1, msg2} 13 | form.setFormDataName(msg1, 'input') 14 | msg1:setBody('input value') 15 | form.setFormDataName(msg2, 'pictures', 'PIC0001.JPG', 'application/octet-stream') 16 | msg2:setBody('FF123456') 17 | form.createFormRequest(request, messages) 18 | --print('request:getBody()', request:getBody()) 19 | local parsedMessages = form.parseFormRequest(request) 20 | lu.assertEquals(#parsedMessages, #messages) 21 | lu.assertEquals(form.getFormDataName(parsedMessages[1]), 'input') 22 | lu.assertEquals(parsedMessages[1]:getBody(), 'input value') 23 | end 24 | 25 | function Test_create_parse_form_url_encoded() 26 | local request = HttpMessage:new() 27 | request:setHeader(HttpMessage.CONST.HEADER_CONTENT_TYPE, 'application/x-www-form-urlencoded') 28 | request:setBody('name=test&password=test') 29 | lu.assertEquals(form.parseFormRequest(request), {name = 'test', password = 'test'}) 30 | end 31 | 32 | function Test_HttpHeaders() 33 | local headers = HttpHeaders:new() 34 | local lines = { 35 | 'cache-control: public, max-age=3600', 36 | 'content-encoding: gzip', 37 | "content-security-policy: default-src 'self' 'unsafe-inline' data: https://sample.org; frame-ancestors 'self' sample.org *.sample.org", 38 | 'content-type: text/html; charset=utf-8', 39 | 'date: Sun, 26 Jun 2022 16:40:32 GMT', 40 | 'expires: Sun, 26 Jun 2022 17:40:32 GMT', 41 | 'referrer-policy: strict-origin-when-cross-origin', 42 | 'strict-transport-security: max-age=3600; includeSubDomains', 43 | 'vary: Accept-Encoding', 44 | 'x-content-type-options: nosniff', 45 | 'x-frame-options: SAMEORIGIN', 46 | 'x-xss-protection: 1; mode=block', 47 | } 48 | for _, line in ipairs(lines) do 49 | headers:parseHeaderLine(line) 50 | end 51 | local rLines = strings.split(headers:getRawHeaders(), '\r\n') 52 | lu.assertEquals(rLines, lines) 53 | end 54 | 55 | function Test_HttpHeaders_set_cookie() 56 | local headers = HttpHeaders:new() 57 | local lines = { 58 | 'cache-control: public, max-age=3600', 59 | 'set-cookie: a=b', 60 | 'set-cookie: b=c', 61 | } 62 | for _, line in ipairs(lines) do 63 | headers:parseHeaderLine(line) 64 | end 65 | local rLines = strings.split(headers:getRawHeaders(), '\r\n') 66 | lu.assertEquals(rLines, lines) 67 | end 68 | 69 | function Test_HttpHeaders_values() 70 | local headers = HttpHeaders:new() 71 | local lines = { 72 | 'cache-control: public', 73 | 'cache-control: max-age=3600', 74 | } 75 | for _, line in ipairs(lines) do 76 | headers:parseHeaderLine(line) 77 | end 78 | local rLines = strings.split(headers:getRawHeaders(), '\r\n') 79 | lu.assertEquals(rLines, {'cache-control: public, max-age=3600'}) 80 | end 81 | 82 | os.exit(lu.LuaUnit.run()) 83 | -------------------------------------------------------------------------------- /jls/net/http/handler/TableHttpHandler.lua: -------------------------------------------------------------------------------- 1 | --- Provide a simple HTTP handler for Lua tables. 2 | -- This handler allows to access and maintain a deep Lua table. 3 | -- Exposes a table content throught HTTP REST APIs. 4 | -- @module jls.net.http.handler.TableHttpHandler 5 | -- @pragma nostrip 6 | 7 | local logger = require('jls.lang.logger'):get(...) 8 | local json = require('jls.util.json') 9 | local tables = require('jls.util.tables') 10 | local HTTP_CONST = require('jls.net.http.HttpMessage').CONST 11 | local HttpExchange = require('jls.net.http.HttpExchange') 12 | 13 | --- A TableHttpHandler class. 14 | -- @type TableHttpHandler 15 | return require('jls.lang.class').create('jls.net.http.HttpHandler', function(tableHttpHandler) 16 | 17 | --- Creates a Lua table @{HttpHandler}. 18 | -- @tparam table table the table. 19 | -- @tparam[opt] string path the table base path. 20 | -- @tparam[opt] boolean editable true to indicate that the table can be modified. 21 | function tableHttpHandler:initialize(table, path, editable) 22 | self.table = table or {} 23 | self.path = path or '' 24 | self.editable = editable == true 25 | end 26 | 27 | function tableHttpHandler:handle(exchange) 28 | local method = exchange:getRequestMethod() 29 | local path = exchange:getRequestPath() 30 | local tp = self.path..string.gsub(path, '/$', '') 31 | logger:fine('method: "%s", path: "%s"', method, tp) 32 | if method == HTTP_CONST.METHOD_GET then 33 | local value = tables.getPath(self.table, tp) 34 | HttpExchange.ok(exchange, json.encode({ 35 | --success = true, 36 | --path = path, 37 | value = value 38 | }), HttpExchange.CONTENT_TYPES.json) 39 | elseif not self.editable then 40 | HttpExchange.methodNotAllowed(exchange) 41 | elseif method == HTTP_CONST.METHOD_PUT or method == HTTP_CONST.METHOD_POST or method == HTTP_CONST.METHOD_PATCH then 42 | local request = exchange:getRequest() 43 | request:bufferBody() 44 | return request:consume():next(function() 45 | if logger:isLoggable(logger.FINEST) then 46 | logger:finest('request body: "%s"', request:getBody()) 47 | end 48 | if request:getBodyLength() > 0 then 49 | local rt = json.decode(request:getBody()) 50 | if type(rt) == 'table' and rt.value then 51 | if method == HTTP_CONST.METHOD_PUT then 52 | tables.setPath(self.table, tp, rt.value) 53 | elseif method == HTTP_CONST.METHOD_POST then 54 | local value = tables.getPath(self.table, tp) 55 | if type(value) == 'table' then 56 | tables.setByPath(value, rt.value) 57 | end 58 | elseif method == HTTP_CONST.METHOD_PATCH then 59 | tables.mergePath(self.table, tp, rt.value) 60 | end 61 | end 62 | end 63 | HttpExchange.ok(exchange) 64 | end) 65 | elseif method == HTTP_CONST.METHOD_DELETE then 66 | tables.removePath(self.table, tp) 67 | HttpExchange.ok(exchange) 68 | else 69 | HttpExchange.methodNotAllowed(exchange) 70 | end 71 | logger:fine('handled %s', exchange) 72 | end 73 | 74 | end) 75 | -------------------------------------------------------------------------------- /examples/webviewDoc.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.event') 2 | local system = require('jls.lang.system') 3 | local File = require('jls.io.File') 4 | local WebView = require('jls.util.WebView') 5 | local FileHttpHandler = require('jls.net.http.handler.FileHttpHandler') 6 | local ZipFileHttpHandler = require('jls.net.http.handler.ZipFileHttpHandler') 7 | 8 | local function contentHandler(body) 9 | return function(exchange) 10 | exchange:getResponse():setBody(body) 11 | end 12 | end 13 | 14 | local contentHeader = [[ 15 | 16 | 17 | 18 | 38 | ]] 39 | 40 | local frames = { 41 | {name = 'luajls', href = 'docs/index.html', path = ''}, 42 | {name = 'Lua', href = 'docs/lua/contents.html', path = ''}, 43 | {name = 'LDoc', href = 'docs/ldoc.html', path = ''}, 44 | {name = 'LuaUnit', href = 'docs/luaunit.html', path = ''}, 45 | {name = 'LuaCov', href = 'docs/luacov/index.html', path = ''}, 46 | } 47 | 48 | local contentBody = '
' 49 | for i, frame in ipairs(frames) do 50 | if i > 1 then 51 | contentBody = contentBody..' - ' 52 | end 53 | contentBody = contentBody..''..frame.name..'' 54 | end 55 | contentBody = contentBody..'
' 56 | 57 | local scriptFile = File:new(system.getArguments()[0]):getAbsoluteFile() 58 | if not scriptFile:exists() then 59 | local filename = package.searchpath('examples.webviewDoc', package.path) 60 | if filename then 61 | scriptFile = File:new(filename):getAbsoluteFile() 62 | end 63 | end 64 | local scriptDir = scriptFile:getParentFile() 65 | local devDir = File:new('../luaclibs') 66 | 67 | WebView.open('http://localhost:0/index.html', { 68 | title = 'Lua JLS Documentation', 69 | width = 1024, 70 | height = 768, 71 | resizable = true, 72 | contexts = { 73 | ['/index.html'] = contentHandler(contentHeader..contentBody..'') 74 | } 75 | }):next(function(webview) 76 | local httpServer = webview:getHttpServer() 77 | print('WebView opened with HTTP Server bound on port '..tostring(select(2, httpServer:getAddress()))) 78 | if devDir:isDirectory() then 79 | httpServer:createContext('/docs/(.*)', FileHttpHandler:new('./doc')) 80 | httpServer:createContext('/docs/lua/(.*)', FileHttpHandler:new(File:new(devDir, 'lua/doc'))) 81 | httpServer:createContext('/docs/luacov/(.*)', FileHttpHandler:new(File:new(devDir, 'luacov/docs'))) 82 | else 83 | httpServer:createContext('/docs/(.*)', ZipFileHttpHandler:new(File:new(scriptDir, '../docs.zip'))) 84 | end 85 | return webview:getThread():ended() 86 | end):catch(function(reason) 87 | print('Cannot open webview due to '..tostring(reason)) 88 | end) 89 | 90 | --print('Looping') 91 | event:loop() 92 | event:close() 93 | -------------------------------------------------------------------------------- /jls/io/Pipe-luachild.lua: -------------------------------------------------------------------------------- 1 | local lcLib = require('luachild') 2 | 3 | local class = require('jls.lang.class') 4 | local Promise = require('jls.lang.Promise') 5 | local logger = require('jls.lang.logger'):get(...) 6 | local loader = require('jls.lang.loader') 7 | local StreamHandler = require('jls.io.StreamHandler') 8 | local event = loader.requireOne('jls.lang.event-') 9 | local FileDescriptor = loader.requireOne('jls.io.FileDescriptor-') 10 | local linuxLib = loader.tryRequire('linux') 11 | 12 | return class.create(function(pipe, _, Pipe) 13 | 14 | function pipe:initialize() 15 | local r, w = lcLib.pipe() 16 | if not r then 17 | error(w or 'fail to create pipe') 18 | end 19 | if linuxLib then 20 | local flags = linuxLib.fcntl(r, linuxLib.constants.F_GETFL) 21 | linuxLib.fcntl(r, linuxLib.constants.F_SETFL, flags | linuxLib.constants.O_NONBLOCK) 22 | end 23 | self.readFd = FileDescriptor:new(r) 24 | self.writeFd = FileDescriptor:new(w) 25 | logger:finest('Pipe:new() r: %s, w: %s', r, w) 26 | end 27 | 28 | function pipe:bind(name, backlog) 29 | error('Not supported') 30 | end 31 | 32 | function pipe:connect(name, callback) 33 | error('Not supported') 34 | end 35 | 36 | function pipe:open(f) -- f as integer 37 | end 38 | 39 | function pipe:readSync(size) 40 | return self.readFd:readSync(size) 41 | end 42 | 43 | function pipe:writeSync(data) 44 | return self.writeFd:writeSync(data) 45 | end 46 | 47 | function pipe:readStart(callback) 48 | if self.readTaskId then 49 | error('already started') 50 | end 51 | local cb = StreamHandler.ensureCallback(callback) 52 | local size = 1024 53 | self.readTaskId = event:setTask(function() 54 | local err 55 | if self.readFd then 56 | local data, errnum 57 | data, err, errnum = self.readFd:readSync(size) -- will block on Windows 58 | if data then 59 | cb(nil, data) 60 | return true 61 | elseif linuxLib and errnum == linuxLib.constants.EAGAIN then 62 | return true 63 | end 64 | end 65 | self.readTaskId = nil 66 | cb(err) 67 | return false 68 | end) 69 | end 70 | 71 | function pipe:readStop() 72 | if self.readTaskId then 73 | event:clearInterval(self.readTaskId) 74 | self.readTaskId = nil 75 | end 76 | end 77 | 78 | function pipe:write(data, callback) 79 | return self.writeFd:write(data, nil, callback) 80 | end 81 | 82 | function pipe:chmod(mode) 83 | end 84 | 85 | function pipe:close(callback) 86 | local a = {} 87 | if self.readFd then 88 | table.insert(a, self.readFd:close()) 89 | self.readFd = nil 90 | end 91 | if self.writeFd then 92 | table.insert(a, self.writeFd:close()) 93 | self.writeFd = nil 94 | end 95 | return Promise.all(a) 96 | end 97 | 98 | function pipe:isClosed() 99 | return not (self.readFd or self.readFd) 100 | end 101 | 102 | function pipe:shutdown(callback) 103 | logger:finest('shutdown()') 104 | local fd = self.writeFd 105 | if fd then 106 | self.writeFd = nil 107 | return fd:close(callback) 108 | end 109 | end 110 | 111 | end) 112 | -------------------------------------------------------------------------------- /examples/acme.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.event') 2 | local Acme = require('jls.net.Acme') 3 | local system = require('jls.lang.system') 4 | local tables = require('jls.util.tables') 5 | 6 | local options = tables.createArgumentTable(system.getArguments(), { 7 | helpPath = 'help', 8 | emptyPath = 'url', 9 | logPath = 'log-level', 10 | aliases = { 11 | h = 'help', 12 | d = 'domain', 13 | u = 'url', 14 | t = 'test', 15 | w = 'webRoot', 16 | ak = 'accountKey', 17 | au = 'accountUrl', 18 | dk = 'domainKey', 19 | ll = 'log-level', 20 | }, 21 | schema = { 22 | title = 'Order a certificate', 23 | type = 'object', 24 | additionalProperties = false, 25 | required = {'domain'}, 26 | properties = { 27 | help = { 28 | title = 'Show the help', 29 | type = 'boolean', 30 | default = false 31 | }, 32 | domain = { 33 | title = 'The certificate domain', 34 | type = 'string' 35 | }, 36 | contactEMail = { 37 | title = 'The contact email', 38 | type = 'string', 39 | pattern = '^.+@.+$' 40 | }, 41 | url = { 42 | title = 'The ACME v2 URL', 43 | type = 'string', 44 | pattern = '^https?://.+$', 45 | default = 'https://acme-v02.api.letsencrypt.org/directory' 46 | }, 47 | stagingUrl = { 48 | title = 'The ACME v2 staging endpoint', 49 | type = 'string', 50 | pattern = '^https?://.+$', 51 | default = 'https://acme-staging-v02.api.letsencrypt.org/directory' 52 | }, 53 | test = { 54 | title = 'Use the staging endpoint', 55 | type = 'boolean', 56 | default = false 57 | }, 58 | webRoot = { 59 | title = 'The web root directory to use for HTTP challenges', 60 | type = 'string', 61 | default = '.' 62 | }, 63 | accountUrl = { 64 | title = 'The account URL', 65 | type = 'string', 66 | pattern = '^https?://.+$' 67 | }, 68 | accountKey = { 69 | title = 'The account key file name', 70 | type = 'string' 71 | }, 72 | domainKey = { 73 | title = 'The domain key file name', 74 | type = 'string' 75 | }, 76 | certificate = { 77 | title = 'The certificate file name', 78 | type = 'string' 79 | }, 80 | names = { 81 | title = 'The certificate names', 82 | type = 'object' 83 | }, 84 | } 85 | } 86 | }) 87 | 88 | local acme = Acme:new(options.test and options.stagingUrl or options.url, { 89 | wwwDir = options.webRoot, 90 | domains = options.domain, 91 | accountUrl = options.accountUrl, 92 | contactEMails = options.contactEMail, 93 | accountKeyFile = options.accountKey, 94 | domainKeyFile = options.domainKey, 95 | certificateFile = options.certificate, 96 | certificateNames = options.names, 97 | }) 98 | 99 | local cert 100 | acme:orderCertificate():next(function(rawCertificate) 101 | cert = rawCertificate 102 | end, function(reason) 103 | print('error: ', reason) 104 | end):finally(function() 105 | acme:close() 106 | end) 107 | 108 | event:loop() 109 | 110 | if cert then 111 | print(cert) 112 | else 113 | os.exit(1) 114 | end 115 | -------------------------------------------------------------------------------- /jls/lang/ProcessHandle-luachild.lua: -------------------------------------------------------------------------------- 1 | local lcLib = require('luachild') 2 | local loader = require('jls.lang.loader') 3 | 4 | local ProcessHandle 5 | local isWindowsOS = string.sub(package.config, 1, 1) == '\\' 6 | if isWindowsOS then 7 | ProcessHandle = loader.tryRequire('jls.lang.ProcessHandle-win32') 8 | else 9 | ProcessHandle = loader.tryRequire('jls.lang.ProcessHandle-linux') 10 | end 11 | 12 | local function parsePid(process) 13 | local spid, state = string.match(tostring(process), '^process%s*%((%d+),%s*(%a+)%)') 14 | local pid = tonumber(spid) 15 | if pid and pid > 0 then 16 | return pid, state 17 | end 18 | return nil, 'unable to parse pid' 19 | end 20 | 21 | if not ProcessHandle then 22 | local Promise = require('jls.lang.Promise') 23 | local event = loader.requireOne('jls.lang.event-') 24 | 25 | ProcessHandle = require('jls.lang.class').create('jls.lang.ProcessHandleBase', function(processHandle, super) 26 | function processHandle:ended() 27 | if not self.endPromise then 28 | self.endPromise = Promise:new(function(resolve, reject) 29 | event:setTask(function() 30 | if self:isAlive() then 31 | return true 32 | end 33 | local code, err = lcLib.wait(self.process) 34 | if code then 35 | self.code = code 36 | resolve(code) 37 | else 38 | reject(err) 39 | end 40 | return false 41 | end) 42 | end) 43 | end 44 | return self.endPromise 45 | end 46 | end) 47 | end 48 | 49 | local function getFdKey(key) 50 | return key == 'stdin' and 'readFd' or 'writeFd' 51 | end 52 | 53 | local function getPipeFd(processBuilder, key) 54 | local pipe = processBuilder[key] 55 | if type(pipe) == 'table' then 56 | if pipe.fd then 57 | return pipe.fd 58 | end 59 | local fd = pipe[getFdKey(key)] 60 | if fd and fd.fd then 61 | return fd.fd 62 | end 63 | end 64 | end 65 | 66 | local function closePipeFd(processBuilder, key) 67 | local pipe = processBuilder[key] 68 | if type(pipe) == 'table' then 69 | local fdKey = getFdKey(key) 70 | local fd = pipe[fdKey] 71 | if fd then 72 | fd:close() 73 | pipe[fdKey] = nil 74 | end 75 | end 76 | end 77 | 78 | ProcessHandle.build = function(processBuilder) 79 | local params = {} 80 | for _, v in ipairs(processBuilder.cmd) do 81 | table.insert(params, v) 82 | end 83 | if type(processBuilder.env) == 'table' then 84 | params.env = processBuilder.env 85 | end 86 | if processBuilder.dir then 87 | error('cannot set dir') 88 | end 89 | params.stdin = getPipeFd(processBuilder, 'stdin') 90 | params.stderr = getPipeFd(processBuilder, 'stderr') 91 | params.stdout = getPipeFd(processBuilder, 'stdout') 92 | local process, err = lcLib.spawn(params) 93 | closePipeFd(processBuilder, 'stdin') 94 | closePipeFd(processBuilder, 'stderr') 95 | closePipeFd(processBuilder, 'stdout') 96 | if not process then 97 | return nil, err 98 | end 99 | local pid, state = parsePid(process) 100 | if pid then 101 | local ph = ProcessHandle:new(pid) 102 | ph.process = process 103 | return ph 104 | end 105 | return nil, state 106 | end 107 | 108 | return ProcessHandle 109 | -------------------------------------------------------------------------------- /tests/full/secure.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local loader = require('jls.lang.loader') 4 | local secure = require('jls.net.secure') 5 | local TcpSocket = secure.TcpSocket 6 | local StreamHandler = require('jls.io.StreamHandler') 7 | 8 | local loop = require('jls.lang.loopWithTimeout') 9 | 10 | local genCertificateAndPKey = loader.load('tests.genCertificateAndPKey') 11 | local CACERT_PEM, PKEY_PEM = genCertificateAndPKey() 12 | 13 | local TEST_HOST, TEST_PORT = '127.0.0.1', 3002 14 | 15 | local function prepareServer(server) 16 | -- reuse previous context 17 | server:setSecureContext(secure.Context:new({ 18 | certificate = CACERT_PEM, 19 | key = PKEY_PEM, 20 | }, true)) 21 | end 22 | 23 | function Test_TcpClient_TcpServer() 24 | local payload = 'Hello' 25 | --payload = string.rep('1234567890', 10000) 26 | local server = TcpSocket:new() 27 | prepareServer(server) 28 | function server:onAccept(client) 29 | client:readStart(StreamHandler:new(function(_, data) 30 | if data then 31 | client:write(data) 32 | else 33 | client:close() 34 | server:close() 35 | end 36 | end)) 37 | end 38 | local client = TcpSocket:new() 39 | client:setSecureContext(secure.Context:new({skipVerification = true})) 40 | local u = {} 41 | server:bind(TEST_HOST, TEST_PORT):next(function() 42 | client:connect(TEST_HOST, TEST_PORT):next(function(err) 43 | client:readStart(StreamHandler:new(function(_, data) 44 | if data then 45 | table.insert(u, (string.gsub(data, '%c', ''))) 46 | if string.find(data, '\n') then 47 | client:close() 48 | end 49 | else 50 | client:close() 51 | end 52 | end)) 53 | client:write(payload) 54 | client:write('\n') 55 | end) 56 | end) 57 | if not loop(function() 58 | client:close() 59 | server:close() 60 | end) then 61 | lu.fail('Timeout reached') 62 | end 63 | lu.assertEquals(table.concat(u), payload) 64 | end 65 | 66 | function Test_TcpClient_TcpServer_table() 67 | local t = {'Received: '} 68 | local server = TcpSocket:new() 69 | prepareServer(server) 70 | function server:onAccept(client) 71 | client:readStart(StreamHandler:new(function(_, data) 72 | if data then 73 | table.insert(t, data) 74 | if string.find(data, '\n') then 75 | client:write(t) 76 | end 77 | else 78 | client:close() 79 | server:close() 80 | end 81 | end)) 82 | end 83 | local client = TcpSocket:new() 84 | client:setSecureContext(secure.Context:new({skipVerification = true})) 85 | local u = {} 86 | server:bind(TEST_HOST, TEST_PORT):next(function() 87 | client:connect(TEST_HOST, TEST_PORT):next(function(err) 88 | client:readStart(StreamHandler:new(function(_, data) 89 | if data then 90 | table.insert(u, (string.gsub(data, '%c', ''))) 91 | if string.find(data, '\n') then 92 | client:close() 93 | end 94 | else 95 | client:close() 96 | end 97 | end)) 98 | client:write({'Hello, ', 'My name is ', 'John', '\n'}) 99 | end) 100 | end) 101 | if not loop(function() 102 | client:close() 103 | server:close() 104 | end) then 105 | lu.fail('Timeout reached') 106 | end 107 | lu.assertEquals(table.concat(u), 'Received: Hello, My name is John') 108 | end 109 | 110 | os.exit(lu.LuaUnit.run()) 111 | -------------------------------------------------------------------------------- /tests/full/fd.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | -- JLS_LOGGER_LEVEL=finer 4 | -- JLS_REQUIRES=\!luv 5 | 6 | local FileDescriptor = require('jls.io.FileDescriptor') 7 | local File = require('jls.io.File') 8 | local Promise = require('jls.lang.Promise') 9 | local loop = require('jls.lang.loopWithTimeout') 10 | 11 | function Test_openSync_r() 12 | local fd, err = FileDescriptor.openSync('tests/does_not_exist', 'r') 13 | lu.assertNil(fd) 14 | lu.assertNotNil(err) 15 | fd, err = FileDescriptor.openSync('tests/full/fd.lua', 'r') 16 | if fd then 17 | fd:closeSync() 18 | end 19 | lu.assertNotNil(fd) 20 | lu.assertNil(err) 21 | end 22 | 23 | local TMP_FILENAME = 'tests/to_remove.tmp' 24 | local TMP_FILE = File:new(TMP_FILENAME) 25 | 26 | function Test_openSync_w() 27 | local fd, err = FileDescriptor.openSync('tests/does_not_exist/to_remove.tmp', 'w') 28 | lu.assertNil(fd) 29 | lu.assertNotNil(err) 30 | fd, err = FileDescriptor.openSync(TMP_FILENAME, 'w') 31 | if fd then 32 | fd:closeSync() 33 | end 34 | TMP_FILE:delete() 35 | lu.assertNotNil(fd) 36 | lu.assertNil(err) 37 | end 38 | 39 | function Test_writeSync() 40 | local part1, part2 = 'Hello', ' world!' 41 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'w') 42 | lu.assertNotNil(fd) 43 | fd:writeSync(part1) 44 | fd:writeSync(part2) 45 | fd:closeSync() 46 | local content = TMP_FILE:readAll() 47 | TMP_FILE:delete() 48 | lu.assertEquals(content, part1..part2) 49 | end 50 | 51 | function Test_readSync() 52 | local part1, part2 = 'Hello', ' world!' 53 | TMP_FILE:write(part1..part2) 54 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'r') 55 | lu.assertNotNil(fd) 56 | local data1 = fd:readSync(#part1) 57 | local data2 = fd:readSync(1024) 58 | local data3 = fd:readSync(1024) 59 | fd:closeSync() 60 | TMP_FILE:delete() 61 | lu.assertEquals(data1, part1) 62 | lu.assertEquals(data2, part2) 63 | lu.assertNil(data3) 64 | end 65 | 66 | function Test_read() 67 | local part1, part2 = 'Hello', ' world!' 68 | local data1, data2, data3 69 | local err = nil 70 | TMP_FILE:write(part1..part2) 71 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'r') 72 | lu.assertNotNil(fd) 73 | fd:read(#part1):next(function(d) 74 | data1 = d 75 | return fd:read(1024) 76 | end):next(function(d) 77 | data2 = d 78 | return fd:read(1024) 79 | end):next(function(d) 80 | data3 = d 81 | end):catch(function(reason) 82 | err = reason 83 | end) 84 | loop() 85 | fd:closeSync() 86 | TMP_FILE:delete() 87 | lu.assertNil(err) 88 | lu.assertEquals(data1, part1) 89 | lu.assertEquals(data2, part2) 90 | lu.assertNil(data3) 91 | end 92 | 93 | function Test_read_async() 94 | if _VERSION == 'Lua 5.1' then 95 | print('/!\\ skipping test due to Lua version') 96 | lu.success() 97 | return 98 | end 99 | local part1, part2 = 'Hello', ' world!' 100 | local data1, data2, data3 101 | TMP_FILE:write(part1..part2) 102 | local fd = FileDescriptor.openSync(TMP_FILENAME, 'r') 103 | lu.assertNotNil(fd) 104 | Promise.async(function(await) 105 | data1 = await(fd:read(#part1)) 106 | data2 = await(fd:read(1024)) 107 | data3 = await(fd:read(1024)) 108 | end):catch(function(reason) 109 | print(reason) 110 | end) 111 | --require('jls.lang.event'):loop() 112 | loop() 113 | fd:closeSync() 114 | TMP_FILE:delete() 115 | lu.assertEquals(data1, part1) 116 | lu.assertEquals(data2, part2) 117 | lu.assertNil(data3) 118 | end 119 | 120 | os.exit(lu.LuaUnit.run()) 121 | -------------------------------------------------------------------------------- /jls/util/RingBuffer.lua: -------------------------------------------------------------------------------- 1 | local class = require('jls.lang.class') 2 | local logger = require('jls.lang.logger'):get(...) 3 | 4 | return class.create('jls.util.Queue', function(ringBuffer) 5 | 6 | local NEXT_SIZE = string.packsize('I4I4') 7 | local HEADER_SIZE = string.packsize('I3') 8 | 9 | local function get(buffer, length, pos, size) 10 | local to = pos + size - 1 11 | local cut = to - length 12 | if cut > 0 then 13 | local first = NEXT_SIZE + 1 + cut 14 | local a = buffer:get(pos, length) 15 | local b = buffer:get(NEXT_SIZE + 1, first - 1) 16 | --logger:finer('get(%d, %d) cut=%d => "%s"-"%s" %d', pos, size, cut, a, b, first) 17 | return a..b, first 18 | end 19 | local data = buffer:get(pos, to) 20 | if cut == 0 then 21 | return data, NEXT_SIZE + 1 22 | end 23 | return data, pos + size 24 | end 25 | 26 | local function set(buffer, length, pos, data, size) 27 | local cut = length - pos + 1 28 | if size > cut then 29 | local a, b = string.sub(data, 1, cut), string.sub(data, cut + 1) 30 | local next = NEXT_SIZE + 1 + size - cut 31 | --logger:finer('set(%d "%s", %d) cut=%d => "%s""%s" %d', pos, data, size, cut, a, b, next) 32 | buffer:set(a, pos) 33 | buffer:set(b, NEXT_SIZE + 1) 34 | return next 35 | end 36 | buffer:set(data, pos) 37 | if size == cut then 38 | return NEXT_SIZE + 1 39 | end 40 | return pos + size 41 | end 42 | 43 | local function getFirstNext(self) 44 | local first, next = string.unpack('I4I4', self.buffer:get(1, NEXT_SIZE)) 45 | --logger:finer('getFirstNext() => %d, %d', first, next) 46 | return first, next 47 | end 48 | 49 | local function setFirstNext(self, first, next) 50 | --logger:finer('setFirstNext(%d, %d)', first, next) 51 | self.buffer:set(string.pack('I4I4', first, next), 1) 52 | end 53 | 54 | function ringBuffer:initialize(buffer) 55 | self.buffer = buffer 56 | setFirstNext(self, NEXT_SIZE + 1, NEXT_SIZE + 1) 57 | end 58 | 59 | function ringBuffer:enqueue(data) 60 | assert(type(data) == 'string', 'invalid data type') 61 | local length = self.buffer:length() 62 | local size = #data 63 | local first, next = getFirstNext(self) 64 | local remaining 65 | if first > next then 66 | remaining = first - next 67 | else 68 | remaining = length - next + first - 1 - NEXT_SIZE 69 | end 70 | if HEADER_SIZE + size > remaining then 71 | assert(NEXT_SIZE + HEADER_SIZE + size < length, 'data too large') 72 | return false 73 | end 74 | local header = string.pack('I3', size) 75 | next = set(self.buffer, length, next, header, HEADER_SIZE) 76 | next = set(self.buffer, length, next, data, size) 77 | setFirstNext(self, first, next) 78 | return true 79 | end 80 | 81 | function ringBuffer:dequeue() 82 | local length = self.buffer:length() 83 | local data, size 84 | local first, next = getFirstNext(self) 85 | if first ~= next then 86 | data, first = get(self.buffer, length, first, HEADER_SIZE) 87 | size = string.unpack('I3', data) 88 | assert(size < length, 'corrupted size') 89 | data, first = get(self.buffer, length, first, size) 90 | setFirstNext(self, first, next) 91 | end 92 | return data 93 | end 94 | 95 | function ringBuffer:serialize(write) 96 | write(self.buffer) 97 | end 98 | 99 | function ringBuffer:deserialize(read) 100 | self.buffer = read('jls.lang.Buffer') 101 | end 102 | 103 | end) 104 | -------------------------------------------------------------------------------- /tests/full/FileStreamHandler.lua: -------------------------------------------------------------------------------- 1 | local lu = require('luaunit') 2 | 3 | local event = require('jls.lang.event') 4 | local Path = require('jls.io.Path') 5 | local FileStreamHandler = require('jls.io.streams.FileStreamHandler') 6 | local StreamHandler = require('jls.io.StreamHandler') 7 | local BufferedStreamHandler = require('jls.io.streams.BufferedStreamHandler') 8 | 9 | local TMP_FILENAME = Path.cleanPath('tests/test_fsh.tmp') 10 | 11 | local function createFile(path, content) 12 | local file = io.open(path, 'wb') 13 | file:write(content) -- TODO check for errors 14 | file:close() 15 | end 16 | 17 | local function assertFileContent(path, expectedContent) 18 | local file = io.open(path, 'rb') 19 | lu.assertNotIsNil(file) 20 | local fileContent = file:read('a') -- TODO check for errors 21 | file:close() 22 | lu.assertEquals(fileContent, expectedContent) 23 | end 24 | 25 | function Test_readAll() 26 | local data = string.rep('1234567890', 10) 27 | createFile(TMP_FILENAME, data) 28 | 29 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 30 | local buffer = bufferedStream:getStringBuffer() 31 | 32 | FileStreamHandler.readAll(TMP_FILENAME, bufferedStream) 33 | event:loop() 34 | lu.assertEquals(buffer:toString(), data) 35 | 36 | buffer:clear() 37 | FileStreamHandler.readAll(TMP_FILENAME, bufferedStream, 7) 38 | event:loop() 39 | lu.assertEquals(buffer:toString(), data) 40 | end 41 | 42 | function Test_read() 43 | local data = string.rep('1234567890', 10) 44 | createFile(TMP_FILENAME, data) 45 | 46 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 47 | local buffer = bufferedStream:getStringBuffer() 48 | 49 | FileStreamHandler.read(TMP_FILENAME, bufferedStream, 0) 50 | event:loop() 51 | lu.assertEquals(buffer:toString(), data) 52 | 53 | buffer:clear() 54 | FileStreamHandler.read(TMP_FILENAME, bufferedStream, 0, nil, 7) 55 | event:loop() 56 | lu.assertEquals(buffer:toString(), data) 57 | 58 | buffer:clear() 59 | FileStreamHandler.read(TMP_FILENAME, bufferedStream, 0, 5, 7) 60 | event:loop() 61 | lu.assertEquals(buffer:toString(), string.sub(data, 1, 5)) 62 | 63 | buffer:clear() 64 | FileStreamHandler.read(TMP_FILENAME, bufferedStream, 1, nil, 7) 65 | event:loop() 66 | lu.assertEquals(buffer:toString(), string.sub(data, 2)) 67 | 68 | buffer:clear() 69 | FileStreamHandler.read(TMP_FILENAME, bufferedStream, 1, 5, 7) 70 | event:loop() 71 | lu.assertEquals(buffer:toString(), string.sub(data, 2, 6)) 72 | end 73 | 74 | function Test_readSync() 75 | local data = string.rep('1234567890', 10) 76 | createFile(TMP_FILENAME, data) 77 | 78 | local bufferedStream = BufferedStreamHandler:new(StreamHandler.null) 79 | local buffer = bufferedStream:getStringBuffer() 80 | 81 | FileStreamHandler.readSync(TMP_FILENAME, bufferedStream, 0) 82 | lu.assertEquals(buffer:toString(), data) 83 | 84 | buffer:clear() 85 | FileStreamHandler.readSync(TMP_FILENAME, bufferedStream, 0, nil, 7) 86 | lu.assertEquals(buffer:toString(), data) 87 | 88 | buffer:clear() 89 | FileStreamHandler.readSync(TMP_FILENAME, bufferedStream, 0, 5, 7) 90 | lu.assertEquals(buffer:toString(), string.sub(data, 1, 5)) 91 | 92 | buffer:clear() 93 | FileStreamHandler.readSync(TMP_FILENAME, bufferedStream, 1, nil, 7) 94 | lu.assertEquals(buffer:toString(), string.sub(data, 2)) 95 | 96 | buffer:clear() 97 | FileStreamHandler.readSync(TMP_FILENAME, bufferedStream, 1, 5, 7) 98 | lu.assertEquals(buffer:toString(), string.sub(data, 2, 6)) 99 | end 100 | 101 | function Test_z_cleanup() 102 | -- delete tmp file 103 | os.remove(TMP_FILENAME) 104 | end 105 | 106 | os.exit(lu.LuaUnit.run()) 107 | -------------------------------------------------------------------------------- /jls/lang/Buffer.lua: -------------------------------------------------------------------------------- 1 | --- Represents a byte array to store temporary data. 2 | -- Data could be get or set using byte or byte array as string. 3 | -- @module jls.lang.Buffer 4 | -- @pragma nostrip 5 | 6 | local class = require('jls.lang.class') 7 | 8 | --- The Buffer class. 9 | -- @type Buffer 10 | return class.create(function(buffer) 11 | 12 | --- Returns the size of the buffer. 13 | -- @return The size of the buffer 14 | -- @function buffer:length 15 | buffer.length = class.notImplementedFunction 16 | 17 | --- Returns the string at the specified position. 18 | -- @tparam[opt] number from The start position, default to 1 19 | -- @tparam[opt] number to The end position included, default to this buffer size 20 | -- @return The string at the specified position 21 | -- @function buffer:get 22 | buffer.get = class.notImplementedFunction 23 | 24 | --- Sets the string at the specified position. 25 | -- @param value The value to set in this buffer, could be a buffer or a string 26 | -- @tparam[opt] number offset The position in this buffer to set, default to 1 27 | -- @tparam[opt] number from The start position in the value, default to 1 28 | -- @tparam[opt] number to The end position in the value included, default to the length of the value 29 | -- @function buffer:set 30 | buffer.set = class.notImplementedFunction 31 | 32 | --- Returns the bytes at the specified position. 33 | -- @tparam[opt] number from The start position, default to 1 34 | -- @tparam[opt] number to The end position included, default to from 35 | -- @return The bytes at the specified position 36 | function buffer:getBytes(from, to) 37 | local s = self:get(from, to) 38 | return string.byte(s, 1, #s) 39 | end 40 | 41 | --- Sets the bytes at the specified position. 42 | -- @tparam number at The position in this buffer to set, default to 1 43 | -- @param ... The bytes 44 | function buffer:setBytes(at, ...) 45 | self:set(string.char(...), at or 1) 46 | end 47 | 48 | --- Returns a view on a sub part of this buffer. 49 | -- @tparam[opt] number from The start position, default to 1 50 | -- @tparam[opt] number to The end position included, default to this buffer size 51 | -- @return The buffer view 52 | function buffer:view(from, to) 53 | local BufferView = require('jls.lang.BufferView') 54 | if BufferView:isInstance(self) then 55 | return BufferView:new(self.buffer, self.offset + (from or 1), self.offset + (to or self.size)) 56 | end 57 | return BufferView:new(self, from, to) 58 | end 59 | 60 | end, function(Buffer) 61 | 62 | local function byName(mode) 63 | if mode == 'local' then 64 | return require('jls.lang.BufferLocal') 65 | elseif mode == 'global' then 66 | return require('jls.lang.BufferGlobal') 67 | elseif mode == 'shared' then 68 | return require('jls.lang.BufferShared') 69 | end 70 | error('invalid buffer mode '..tostring(mode)) 71 | end 72 | 73 | --- Returns a new buffer for the specified mode. 74 | -- A local buffer resides in the Lua state. 75 | -- A global buffer resides out of the Lua state but in the Lua process. 76 | -- A shared buffer resides out of the Lua process but in the host. 77 | -- @tparam number size the size to allocate, could be a non empty string 78 | -- @tparam[opt] string mode the mode of buffer, defaults to local 79 | -- @return The new allocated buffer 80 | function Buffer.allocate(size, mode) 81 | if type(size) == 'string' then 82 | local b = Buffer.allocate(#size, mode) 83 | b:set(size) 84 | return b 85 | end 86 | if math.type(size) ~= 'integer' or size <= 0 then 87 | error('invalid size '..tostring(size)) 88 | end 89 | if mode == nil then 90 | mode = 'local' 91 | end 92 | return byName(mode).allocate(size, mode) 93 | end 94 | 95 | end) --------------------------------------------------------------------------------