From c2c95f23d0a9adeb6a7c473461e74cfbbf8307eb Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 1 Aug 2017 11:04:26 +0200 Subject: Web require searcher --- src/lauxlib.js | 185 ++++++++++++++++++++++++++++++++++++++------------------- src/loadlib.js | 11 ++++ 2 files changed, 136 insertions(+), 60 deletions(-) diff --git a/src/lauxlib.js b/src/lauxlib.js index 2f86720..0db125e 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -644,7 +644,60 @@ const luaL_unref = function(L, t, ref) { } }; -// Only with Node + +const errfile = function(L, what, fnameindex, error) { + let serr = error.message; + let filename = lua.lua_tostring(L, fnameindex).slice(1); + lua.lua_pushstring(L, lua.to_luastring(`cannot ${what} ${lua.to_jsstring(filename)}: ${serr}`)); + lua.lua_remove(L, fnameindex); + return lua.LUA_ERRFILE; +}; + +let getc; + +const utf8_bom = [0XEF, 0XBB, 0XBF]; /* UTF-8 BOM mark */ +const skipBOM = function(lf) { + lf.n = 0; + let c; + let p = 0; + do { + c = getc(lf); + if (c === null || c !== utf8_bom[p]) return c; + p++; + lf.buff[lf.n++] = c; /* to be read by the parser */ + } while (p < utf8_bom.length); + lf.n = 0; /* prefix matched; discard it */ + return getc(lf); /* return next character */ +}; + +/* +** reads the first character of file 'f' and skips an optional BOM mark +** in its beginning plus its first line if it starts with '#'. Returns +** true if it skipped the first line. In any case, '*cp' has the +** first "valid" character of the file (after the optional BOM and +** a first-line comment). +*/ +const skipcomment = function(lf) { + let c = skipBOM(lf); + if (c === '#'.charCodeAt(0)) { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = getc(lf); + } while (c && c !== '\n'.charCodeAt(0)); + + return { + skipped: true, + c: getc(lf) /* skip end-of-line, if present */ + }; + } else { + return { + skipped: false, + c: c + }; + } +}; + +let luaL_loadfilex; + if (!WEB) { const fs = require('fs'); @@ -679,63 +732,14 @@ if (!WEB) { else return null; }; - const errfile = function(L, what, fnameindex, error) { - let serr = error.message; - let filename = lua.lua_tostring(L, fnameindex).slice(1); - lua.lua_pushstring(L, lua.to_luastring(`cannot ${what} ${lua.to_jsstring(filename)}: ${serr}`)); - lua.lua_remove(L, fnameindex); - return lua.LUA_ERRFILE; - }; - - const getc = function(lf) { + getc = function(lf) { let b = new Buffer(1); let bytes = fs.readSync(lf.f, b, 0, 1, lf.pos); lf.pos += bytes; return bytes > 0 ? b.readUInt8() : null; }; - const utf8_bom = [0XEF, 0XBB, 0XBF]; /* UTF-8 BOM mark */ - const skipBOM = function(lf) { - lf.n = 0; - let c; - let p = 0; - do { - c = getc(lf); - if (c === null || c !== utf8_bom[p]) return c; - p++; - lf.buff[lf.n++] = c; /* to be read by the parser */ - } while (p < utf8_bom.length); - lf.n = 0; /* prefix matched; discard it */ - return getc(lf); /* return next character */ - }; - - /* - ** reads the first character of file 'f' and skips an optional BOM mark - ** in its beginning plus its first line if it starts with '#'. Returns - ** true if it skipped the first line. In any case, '*cp' has the - ** first "valid" character of the file (after the optional BOM and - ** a first-line comment). - */ - const skipcomment = function(lf) { - let c = skipBOM(lf); - if (c === '#'.charCodeAt(0)) { /* first line is a comment (Unix exec. file)? */ - do { /* skip first line */ - c = getc(lf); - } while (c && c !== '\n'.charCodeAt(0)); - - return { - skipped: true, - c: getc(lf) /* skip end-of-line, if present */ - }; - } else { - return { - skipped: false, - c: c - }; - } - }; - - const luaL_loadfilex = function(L, filename, mode) { + luaL_loadfilex = function(L, filename, mode) { let lf = new LoadF(); let fnameindex = lua.lua_gettop(L) + 1; /* index of filename on the stack */ if (filename === null) { @@ -743,7 +747,7 @@ if (!WEB) { lf.f = process.stdin.fd; } else { let jsfilename = lua.to_jsstring(filename); - lua.lua_pushliteral(L, `@${jsfilename}`); + lua.lua_pushfstring(L, "@%s", filename); try { lf.f = fs.openSync(jsfilename, "r"); if (!fs.fstatSync(lf.f).isFile()) @@ -771,20 +775,81 @@ if (!WEB) { lua.lua_remove(L, fnameindex); return status; }; +} else { + class LoadF { + constructor() { + this.n = NaN; /* number of pre-read characters */ + this.f = null; /* file being read */ + this.buff = new Buffer(1024); /* area for reading file */ + this.pos = 0; /* current position in file */ + this.err = void 0; + } + } - const luaL_loadfile = function(L, filename) { - return luaL_loadfilex(L, filename, null); + const getF = function(L, ud) { + let lf = ud; + let dv = lf.f instanceof Uint8Array ? new DataView(new ArrayBuffer(lf.f)) : null; + lf.f = null; + return dv; }; - const luaL_dofile = function(L, filename) { - return (luaL_loadfile(L, filename) || lua.lua_pcall(L, 0, lua.LUA_MULTRET, 0)); + getc = function(lf) { + return lf.pos < lf.f.length ? lf.f[lf.pos++] : null; }; - module.exports.luaL_dofile = luaL_dofile; - module.exports.luaL_loadfilex = luaL_loadfilex; - module.exports.luaL_loadfile = luaL_loadfile; + luaL_loadfilex = function(L, filename, mode) { + let lf = new LoadF(); + let fnameindex = lua.lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename === null) { + throw new Error("Can't read stdin in the browser"); + } else { + let jsfilename = lua.to_jsstring(filename); + lua.lua_pushliteral(L, `@${jsfilename}`); + + let xhr = new XMLHttpRequest(); + xhr.open("GET", jsfilename, false); + xhr.responseType = "arraybuffer"; + xhr.send(); + + if (xhr.status >= 200 && xhr.status <= 299) { + lf.f = new Uint8Array(xhr.response); + } else { + lf.err = xhr.status; + return errfile(L, "open", fnameindex, xhr.status); + } + } + let com = skipcomment(lf); + /* check for signature first, as we don't want to add line number corrections in binary case */ + if (com.c === lua.LUA_SIGNATURE.charCodeAt(0) && filename) { /* binary file? */ + /* no need to re-open in node.js */ + } else if (com.skipped) { /* read initial portion */ + lf.buff[lf.n++] = '\n'.charCodeAt(0); /* add line to correct line numbers */ + } + if (com.c !== null) + lf.buff[lf.n++] = com.c; /* 'c' is the first character of the stream */ + let status = lua.lua_load(L, getF, lf, lua.lua_tostring(L, -1), mode); + let readstatus = lf.err; + if (readstatus) { + lua.lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ + return errfile(L, "read", fnameindex, readstatus); + } + lua.lua_remove(L, fnameindex); + return status; + }; } +const luaL_loadfile = function(L, filename) { + return luaL_loadfilex(L, filename, null); +}; + +const luaL_dofile = function(L, filename) { + return (luaL_loadfile(L, filename) || lua.lua_pcall(L, 0, lua.LUA_MULTRET, 0)); +}; + +module.exports.luaL_dofile = luaL_dofile; +module.exports.luaL_loadfilex = luaL_loadfilex; +module.exports.luaL_loadfile = luaL_loadfile; + const lua_writestringerror = function(s) { if (process.stderr) process.stderr.write(s); else console.error(s); diff --git a/src/loadlib.js b/src/loadlib.js index dfa082a..7458704 100644 --- a/src/loadlib.js +++ b/src/loadlib.js @@ -95,6 +95,17 @@ if (!WEB) { return true; }; +} else { + /* TODO: use async/await ? */ + readable = function(filename) { + /* TODO: do a GET and store it somewhere to avoid doing two roundtrips ? */ + let xhr = new XMLHttpRequest(); + xhr.open("HEAD", filename, false); + xhr.send(); + /* TODO: subresource integrity check? */ + + return xhr.status >= 200 && xhr.status <= 299; + }; } -- cgit v1.2.3-70-g09d2