├── 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([[