summaryrefslogtreecommitdiff
path: root/tex/context/base/mkxl
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkxl')
-rw-r--r--tex/context/base/mkxl/cont-new.mkxl2
-rw-r--r--tex/context/base/mkxl/context.mkxl2
-rw-r--r--tex/context/base/mkxl/data-sch.lmt312
-rw-r--r--tex/context/base/mkxl/data-vir.lmt29
-rw-r--r--tex/context/base/mkxl/lang-def.mkxl2
-rw-r--r--tex/context/base/mkxl/lpdf-img.lmt12
-rw-r--r--tex/context/base/mkxl/luat-lib.mkxl4
-rw-r--r--tex/context/base/mkxl/math-ini.mkxl4
-rw-r--r--tex/context/base/mkxl/phys-dim.lmt4
-rw-r--r--tex/context/base/mkxl/util-sbx.lmt658
10 files changed, 1016 insertions, 13 deletions
diff --git a/tex/context/base/mkxl/cont-new.mkxl b/tex/context/base/mkxl/cont-new.mkxl
index 23dc6bbef..030a373aa 100644
--- a/tex/context/base/mkxl/cont-new.mkxl
+++ b/tex/context/base/mkxl/cont-new.mkxl
@@ -13,7 +13,7 @@
% \normalend % uncomment this to get the real base runtime
-\newcontextversion{2023.04.27 16:54}
+\newcontextversion{2023.05.05 18:36}
%D This file is loaded at runtime, thereby providing an excellent place for hacks,
%D patches, extensions and new features. There can be local overloads in cont-loc
diff --git a/tex/context/base/mkxl/context.mkxl b/tex/context/base/mkxl/context.mkxl
index 9ee1d3803..4cfa73091 100644
--- a/tex/context/base/mkxl/context.mkxl
+++ b/tex/context/base/mkxl/context.mkxl
@@ -29,7 +29,7 @@
%D {YYYY.MM.DD HH:MM} format.
\immutable\edef\contextformat {\jobname}
-\immutable\edef\contextversion{2023.04.27 16:54}
+\immutable\edef\contextversion{2023.05.05 18:36}
%overloadmode 1 % check frozen / warning
%overloadmode 2 % check frozen / error
diff --git a/tex/context/base/mkxl/data-sch.lmt b/tex/context/base/mkxl/data-sch.lmt
new file mode 100644
index 000000000..36f89040c
--- /dev/null
+++ b/tex/context/base/mkxl/data-sch.lmt
@@ -0,0 +1,312 @@
+if not modules then modules = { } end modules ['data-sch'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local load, tonumber, require = load, tonumber, require
+local gsub, format = string.gsub, string.format
+local savedata = io.savedata
+local sortedhash, concat = table.sortedhash, table.concat
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+local addsuffix, suffix, splitbase = file.addsuffix, file.suffix, file.splitbase
+local md5hex = md5.hex
+local removefile, renamefile, fileexists = os.remove, os.rename, io.exists
+
+-- todo: more locals
+
+local trace_schemes = false trackers.register("resolvers.schemes",function(v) trace_schemes = v end)
+local report_schemes = logs.reporter("resolvers","schemes")
+
+local http = require("socket.http")
+local ltn12 = require("ltn12")
+
+if mbox then mbox = nil end -- useless and even bugged (helper overwrites lib)
+
+local resolvers = resolvers
+local schemes = resolvers.schemes or { }
+resolvers.schemes = schemes
+
+local cleaners = { }
+schemes.cleaners = cleaners
+
+local threshold = 24 * 60 * 60
+local inmemory = false
+local uselibrary = false
+
+directives.register("schemes.threshold", function(v) threshold = tonumber(v) or threshold end)
+directives.register("schemes.inmemory", function(v) inmemory = v end)
+directives.register("schemes.uselibrary", function(v) uselibrary = v end)
+
+function cleaners.none(specification)
+ return specification.original
+end
+
+-- function cleaners.strip(specification)
+-- -- todo: only keep suffix periods, so after the last
+-- return (gsub(specification.original,"[^%a%d%.]+","-")) -- so we keep periods
+-- end
+
+function cleaners.strip(specification) -- keep suffixes
+ local path, name = splitbase(specification.original)
+ if path == "" then
+ return (gsub(name,"[^%a%d%.]+","-"))
+ else
+ return (gsub((gsub(path,"%.","-") .. "-" .. name),"[^%a%d%.]+","-"))
+ end
+end
+
+function cleaners.md5(specification)
+ return addsuffix(md5hex(specification.original),suffix(specification.path))
+end
+
+local cleaner = cleaners.strip
+
+directives.register("schemes.cleanmethod", function(v) cleaner = cleaners[v] or cleaners.strip end)
+
+function resolvers.schemes.cleanname(specification)
+ local hash = cleaner(specification)
+ if trace_schemes then
+ report_schemes("hashing %a to %a",specification.original,hash)
+ end
+ return hash
+end
+
+local cached = { }
+local loaded = { }
+local reused = { }
+local thresholds = { }
+local handlers = { }
+
+local function fetcher(report)
+ if uselibrary then
+ local curl = require("curl") or require("libs-imp-curl") -- we have curl preloaded
+ local fetch = curl and curl.fetch
+ if fetch then
+ return function(str)
+ local data, message = fetch {
+ url = str,
+ followlocation = true,
+ sslverifyhost = false,
+ sslverifypeer = false,
+ }
+ if not data then
+ report("some error: %s",message)
+ end
+ return data
+ end
+ end
+ end
+end
+
+local runner = sandbox.registerrunner {
+ name = "to file curl resolver",
+ method = "execute",
+ program = "curl",
+ template = '--silent --insecure --create-dirs --output "%cachename%" "%original%"',
+ internal = function(specification)
+ local fetch = fetcher(specification.reporter)
+ return fetch and function(name,program,template,checkers,defaults,variables,reporter,finalized)
+ local data = fetch(variables.original)
+ savedata(variables.cachename,data or "")
+ end
+ end,
+ checkers = {
+ cachename = "cache",
+ original = "url",
+ }
+}
+
+local memrunner = sandbox.registerrunner {
+ name = "in memory curl resolver",
+ method = "resultof",
+ program = "curl",
+ template = '--silent --insecure "%original%"',
+ internal = function(specification)
+ local fetch = fetcher(specification.reporter)
+ return fetch and function(name,program,template,checkers,defaults,variables,reporter,finalized)
+ return fetch(variables.original) or ""
+ end
+ end,
+ checkers = {
+ original = "url",
+ }
+}
+
+local function fetch(specification)
+ local original = specification.original
+ local scheme = specification.scheme
+ local cleanname = schemes.cleanname(specification)
+ if inmemory then
+ statistics.starttiming(schemes)
+ local cachename = resolvers.savers.virtualname(cleanname)
+ local handler = handlers[scheme]
+ -- if handler and not uselibrary then
+ if handler then -- internal sockets are twice as fast as library
+ if trace_schemes then
+ report_schemes("fetching %a, protocol %a, method %a",original,scheme,"built-in")
+ end
+ logs.flush()
+ handler(specification,cachename)
+ else
+ if trace_schemes then
+ report_schemes("fetching %a, protocol %a, method %a",original,scheme,"curl")
+ end
+ logs.flush()
+ local result = memrunner {
+ original = original,
+ }
+ resolvers.savers.directvirtual(cachename,result,true) -- persistent
+ end
+ loaded[scheme] = loaded[scheme] + 1
+ statistics.stoptiming(schemes)
+ return cachename
+ else
+ local cachename = caches.setfirstwritablefile(cleanname,"schemes")
+ if not cached[original] or threshold == 0 then
+ statistics.starttiming(schemes)
+ if threshold == 0 or not fileexists(cachename) or (os.difftime(os.time(),lfs.attributes(cachename).modification) > (thresholds[protocol] or threshold)) then
+ -- removefile(cachename)
+ cached[original] = cachename
+ local handler = handlers[scheme]
+ if handler then
+ if trace_schemes then
+ report_schemes("fetching %a, protocol %a, method %a",original,scheme,"built-in")
+ end
+ logs.flush()
+ handler(specification,cachename)
+ else
+ if trace_schemes then
+ report_schemes("fetching %a, protocol %a, method %a",original,scheme,"curl")
+ end
+ logs.flush()
+ runner {
+ original = original,
+ cachename = cachename,
+ }
+ end
+ end
+ if fileexists(cachename) then
+ cached[original] = cachename
+ if trace_schemes then
+ report_schemes("using cached %a, protocol %a, cachename %a",original,scheme,cachename)
+ end
+ else
+ cached[original] = ""
+ if trace_schemes then
+ report_schemes("using missing %a, protocol %a",original,scheme)
+ end
+ end
+ loaded[scheme] = loaded[scheme] + 1
+ statistics.stoptiming(schemes)
+ else
+ if trace_schemes then
+ report_schemes("reusing %a, protocol %a",original,scheme)
+ end
+ reused[scheme] = reused[scheme] + 1
+ end
+ return cached[original]
+ end
+end
+
+local function finder(specification,filetype)
+ return resolvers.methodhandler("finders",fetch(specification),filetype)
+end
+
+local opener = openers.file
+local loader = loaders.file
+
+local function install(scheme,handler,newthreshold)
+ handlers [scheme] = handler
+ loaded [scheme] = 0
+ reused [scheme] = 0
+ finders [scheme] = finder
+ openers [scheme] = opener
+ loaders [scheme] = loader
+ thresholds[scheme] = newthreshold or threshold
+end
+
+schemes.install = install
+
+local function http_handler(specification,cachename)
+ if inmemory then
+ local result = { }
+ local status, message = http.request {
+ url = specification.original,
+ sink = ltn12.sink.table(result)
+ }
+ resolvers.savers.directvirtual(cachename,concat(result),true) -- persistent
+ else
+ local tempname = cachename .. ".tmp"
+ local handle = io.open(tempname,"wb")
+ local status, message = http.request {
+ url = specification.original,
+ sink = ltn12.sink.file(handle)
+ }
+ if not status then
+ removefile(tempname)
+ else
+ removefile(cachename)
+ renamefile(tempname,cachename)
+ end
+ end
+ return cachename
+end
+
+install('http',http_handler)
+install('https') -- see pod
+install('ftp')
+
+statistics.register("scheme handling time", function()
+ local l, r, nl, nr = { }, { }, 0, 0
+ for k, v in sortedhash(loaded) do
+ if v > 0 then
+ nl = nl + 1
+ l[nl] = k .. ":" .. v
+ end
+ end
+ for k, v in sortedhash(reused) do
+ if v > 0 then
+ nr = nr + 1
+ r[nr] = k .. ":" .. v
+ end
+ end
+ local n = nl + nr
+ if n > 0 then
+ if nl == 0 then l = { "none" } end
+ if nr == 0 then r = { "none" } end
+ return format("%s seconds, %s processed, threshold %s seconds, loaded: %s, reused: %s",
+ statistics.elapsedtime(schemes), n, threshold, concat(l," "), concat(l," "))
+ else
+ return nil
+ end
+end)
+
+-- We provide a few more helpers:
+
+----- http = require("socket.http")
+local httprequest = http.request
+local toquery = url.toquery
+
+local function fetchstring(url,data)
+ local q = data and toquery(data)
+ if q then
+ url = url .. "?" .. q
+ end
+ local reply = httprequest(url)
+ return reply -- just one argument
+end
+
+schemes.fetchstring = fetchstring
+
+function schemes.fetchtable(url,data)
+ local reply = fetchstring(url,data)
+ if reply then
+ local s = load("return " .. reply)
+ if s then
+ return s()
+ end
+ end
+end
diff --git a/tex/context/base/mkxl/data-vir.lmt b/tex/context/base/mkxl/data-vir.lmt
index b78211fc9..75a8c68c0 100644
--- a/tex/context/base/mkxl/data-vir.lmt
+++ b/tex/context/base/mkxl/data-vir.lmt
@@ -20,26 +20,42 @@ local savers = resolvers.savers
local cleaners = resolvers.cleaners
local data = { }
+local keep = { }
local n = 0 -- hm, number can be query
local f_virtual_n = formatters["virtual://%s.%s"]
local f_virtual_y = formatters["virtual://%s-%s.%s"]
-function savers.virtual(specification,content,suffix)
+local function virtualname(specification,suffix)
n = n + 1 -- one number for all namespaces
local path = type(specification) == "table" and specification.path or specification
if type(path) ~= "string" or path == "" then
path = "virtualfile"
end
- local filename = suffix and f_virtual_y(path,n,suffix) or f_virtual_n(path,n)
+ return suffix and f_virtual_y(path,n,suffix) or f_virtual_n(path,n)
+end
+
+local function directvirtual(filename,content,persistent)
+ if not content then
+ content = ""
+ end
if trace_virtual then
- report_virtual("saver: file %a saved",filename)
+ report_virtual("saver: file %a saved, size %i",filename,#content)
end
- data[filename] = content
+ data[filename] = content or ""
+ keep[filename] = persistent
return filename
end
+function savers.virtual(specification,content,suffix)
+ return directvirtual(virtualname(specification,suffix),content)
+end
+
+savers.virtualname = virtualname
+savers.directvirtual = directvirtual
+
function cleaners.virtual(filename)
data[filename] = nil
+ keep[filename] = nil
end
local finders = resolvers.finders
@@ -94,7 +110,10 @@ function loaders.virtual(specification)
if trace_virtual then
report_virtual("loader: file %a loaded",original)
end
- data[original] = nil
+ if not keep[original] then
+ data[original] = nil
+ keep[original] = nil
+ end
return true, d, #d
end
if trace_virtual then
diff --git a/tex/context/base/mkxl/lang-def.mkxl b/tex/context/base/mkxl/lang-def.mkxl
index 04215a779..192c9c6f9 100644
--- a/tex/context/base/mkxl/lang-def.mkxl
+++ b/tex/context/base/mkxl/lang-def.mkxl
@@ -798,7 +798,7 @@
\c!rightquote=\upperrightsingleninequote,
\c!leftquotation=\upperleftdoublesixquote,
\c!rightquotation=\upperrightdoubleninequote,
- \c!date={\v!year,\space,\v!month,\space,\v!day}]
+ \c!date={\v!day,\space,\v!month,\space,\v!year}]
\installlanguage[\s!pt-br][\c!default=\s!pt] % Brazil
\installlanguage[\s!es-es][\c!default=\s!es] % Spain
diff --git a/tex/context/base/mkxl/lpdf-img.lmt b/tex/context/base/mkxl/lpdf-img.lmt
index c241ccdb7..e7dc663d6 100644
--- a/tex/context/base/mkxl/lpdf-img.lmt
+++ b/tex/context/base/mkxl/lpdf-img.lmt
@@ -67,6 +67,7 @@ local zlibcompress = xzip.compress
local zlibdecompress = xzip.decompress
local trace = false
+local cleanvirtual = resolvers.cleaners.virtual -- false -- for now
local report_jpg = logs.reporter("graphics","jpg")
local report_jp2 = logs.reporter("graphics","jp2")
@@ -74,6 +75,8 @@ local report_png = logs.reporter("graphics","png")
trackers.register("graphics.backend", function(v) trace = v end)
+directives.register("graphics.cleanvirtuals", function(v) cleanvirtual = v and resolvers.cleaners.virtual or false end)
+
local injectors = { }
lpdf.injectors = injectors
@@ -158,6 +161,9 @@ do
if trace then
report_jpg("%s: width %i, height %i, colordepth %i, size %i",filename,xsize,ysize,colordepth,#content)
end
+ if cleanvirtual then
+ cleanvirtual(filename)
+ end
return createimage {
bbox = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
transform = specification.transform,
@@ -197,6 +203,9 @@ do
if trace then
report_jp2("%s: width %i, height %i, size %i",filename,xsize,ysize,#content)
end
+ if cleanvirtual then
+ cleanvirtual(filename)
+ end
return createimage {
bbox = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
transform = specification.transform,
@@ -1211,6 +1220,9 @@ do
end
local width = specification.width or xsize * 65536
local height = specification.height or ysize * 65536
+ if cleanvirtual then
+ cleanvirtual(filename)
+ end
return createimage {
bbox = { 0, 0, width/xsize, height/ysize }, -- mandate
transform = specification.transform,
diff --git a/tex/context/base/mkxl/luat-lib.mkxl b/tex/context/base/mkxl/luat-lib.mkxl
index cb161cafa..d701fd293 100644
--- a/tex/context/base/mkxl/luat-lib.mkxl
+++ b/tex/context/base/mkxl/luat-lib.mkxl
@@ -34,7 +34,7 @@
\registerctxluafile{util-sta}{}
-\registerctxluafile{util-sbx}{} % needs tracker and templates
+\registerctxluafile{util-sbx}{autosuffix} % needs tracker and templates
\registerctxluafile{util-soc-imp-reset} {}
\registerctxluafile{util-soc-imp-socket} {}
@@ -66,7 +66,7 @@
\registerctxluafile{data-dec}{}
\registerctxluafile{data-tar}{}
%registerctxluafile{data-crl}{}
-\registerctxluafile{data-sch}{}
+\registerctxluafile{data-sch}{autosuffix}
\registerctxluafile{data-tre}{}
\registerctxluafile{data-lua}{}
\registerctxluafile{data-ctx}{}
diff --git a/tex/context/base/mkxl/math-ini.mkxl b/tex/context/base/mkxl/math-ini.mkxl
index 2529890d7..f9b733d9c 100644
--- a/tex/context/base/mkxl/math-ini.mkxl
+++ b/tex/context/base/mkxl/math-ini.mkxl
@@ -4587,8 +4587,8 @@
\def\math_axis_inject_indeed#1%
{\srule
- \s!height \dimexpr\scratchdimentwo+\scratchdimenone\relax
- \s!depth -\dimexpr\scratchdimentwo-\scratchdimenone\relax
+ \s!height \dimexpr(\scratchdimentwo+\scratchdimenone)*\c_math_m_scaled/\plusthousand\relax
+ \s!depth -\dimexpr(\scratchdimentwo-\scratchdimenone)*\c_math_m_scaled/\plusthousand\relax
\s!attr \mathaxisattribute#1%
\relax}
diff --git a/tex/context/base/mkxl/phys-dim.lmt b/tex/context/base/mkxl/phys-dim.lmt
index 8575962e9..d3b6f80ba 100644
--- a/tex/context/base/mkxl/phys-dim.lmt
+++ b/tex/context/base/mkxl/phys-dim.lmt
@@ -464,7 +464,7 @@ local short_units = { -- I'm not sure about casing
s = "second",
g = "gram",
n = "newton",
- v = "volt",
+ V = "volt",
t = "tonne",
l = "liter",
-- w = "watt",
@@ -472,6 +472,8 @@ local short_units = { -- I'm not sure about casing
-- a = "ampere",
A = "ampere",
+ Ω = "ohm",
+
-- C = "coulomb", -- needs checking with (c)enti
-- K = "kelvin", -- needs checking with (k)ilo
-- N = "newton", -- needs checking with (n)ewton
diff --git a/tex/context/base/mkxl/util-sbx.lmt b/tex/context/base/mkxl/util-sbx.lmt
new file mode 100644
index 000000000..b48d4a9f6
--- /dev/null
+++ b/tex/context/base/mkxl/util-sbx.lmt
@@ -0,0 +1,658 @@
+if not modules then modules = { } end modules ['util-sbx'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- Note: we use expandname and collapsepath and these use chdir which is overloaded
+-- so we need to use originals there. Just something to keep in mind. This is old
+-- code that I might need to upgrade to fit lmtx better. I'll stepwise also try to
+-- integrate e.g. curl and graphicmagick a bit more natural. Some code below can
+-- be simplified because we don't share any longer with luatex.
+
+if not sandbox then require("l-sandbox") end -- for testing
+
+local next, type = next, type
+
+local replace = utilities.templates.replace
+local collapsepath = file.collapsepath
+local expandname = dir.expandname
+local sortedhash = table.sortedhash
+local lpegmatch = lpeg.match
+local platform = os.type
+local P, S, C = lpeg.P, lpeg.S, lpeg.C
+local gsub = string.gsub
+local lower = string.lower
+local find = string.find
+local concat = string.concat
+local unquoted = string.unquoted
+local optionalquoted = string.optionalquoted
+local basename = file.basename
+local nameonly = file.nameonly
+
+local sandbox = sandbox
+local validroots = { }
+local validrunners = { }
+local validbinaries = true -- all permitted
+local validlibraries = true -- all permitted
+local validators = { }
+local finalized = nil
+local trace = false
+
+local p_validroot = nil
+local p_split = lpeg.firstofsplit(" ")
+
+local report = logs.reporter("sandbox")
+
+trackers.register("sandbox",function(v) trace = v end) -- often too late anyway
+
+sandbox.setreporter(report)
+
+sandbox.finalizer {
+ category = "files",
+ action = function()
+ finalized = true
+ end
+}
+
+local function registerroot(root,what) -- what == read|write
+ if finalized then
+ report("roots are already finalized")
+ else
+ if type(root) == "table" then
+ root, what = root[1], root[2]
+ end
+ if type(root) == "string" and root ~= "" then
+ root = collapsepath(expandname(root))
+ if what == "r" or what == "ro" or what == "readable" then
+ what = "read"
+ elseif what == "w" or what == "wo" or what == "writable" then
+ what = "write"
+ end
+ -- true: read & write | false: read
+ validroots[root] = what == "write" or false
+ end
+ end
+end
+
+sandbox.finalizer {
+ category = "files",
+ action = function() -- initializers can set the path
+ if p_validroot then
+ report("roots are already initialized")
+ else
+ sandbox.registerroot(".","write") -- always ok
+ -- also register texmf as read
+ for name in sortedhash(validroots) do
+ if p_validroot then
+ p_validroot = P(name) + p_validroot
+ else
+ p_validroot = P(name)
+ end
+ end
+ p_validroot = p_validroot / validroots
+ end
+ end
+}
+
+local function registerbinary(name)
+ if finalized then
+ report("binaries are already finalized")
+ elseif type(name) == "string" and name ~= "" then
+ if not validbinaries then
+ return
+ end
+ if validbinaries == true then
+ validbinaries = { [name] = true }
+ else
+ validbinaries[name] = true
+ end
+ elseif name == true then
+ validbinaries = { }
+ end
+end
+
+local function registerlibrary(name)
+ if finalized then
+ report("libraries are already finalized")
+ elseif type(name) == "string" and name ~= "" then
+ if not validlibraries then
+ return
+ end
+ if validlibraries == true then
+ validlibraries = { [nameonly(name)] = true }
+ else
+ validlibraries[nameonly(name)] = true
+ end
+ elseif name == true then
+ validlibraries = { }
+ end
+end
+
+-- begin of validators
+
+local p_write = S("wa") p_write = (1 - p_write)^0 * p_write
+local p_path = S("\\/~$%:") p_path = (1 - p_path )^0 * p_path -- be easy on other arguments
+
+local function normalized(name) -- only used in executers
+ if platform == "windows" then
+ name = gsub(name,"/","\\")
+ end
+ return name
+end
+
+function sandbox.possiblepath(name)
+ return lpegmatch(p_path,name) and true or false
+end
+
+local filenamelogger = false
+
+function sandbox.setfilenamelogger(l)
+ filenamelogger = type(l) == "function" and l or false
+end
+
+local function validfilename(name,what)
+ if p_validroot and type(name) == "string" and lpegmatch(p_path,name) then
+ local asked = collapsepath(expandname(name))
+ -- if platform == "windows" then
+ -- asked = lower(asked) -- we assume ascii names
+ -- end
+ local okay = lpegmatch(p_validroot,asked)
+ if okay == true then
+ -- read and write access
+ if filenamelogger then
+ filenamelogger(name,"w",asked,true)
+ end
+ return name
+ elseif okay == false then
+ -- read only access
+ if not what then
+ -- no further argument to io.open so a readonly case
+ if filenamelogger then
+ filenamelogger(name,"r",asked,true)
+ end
+ return name
+ elseif lpegmatch(p_write,what) then
+ if filenamelogger then
+ filenamelogger(name,"w",asked,false)
+ end
+ return -- we want write access
+ else
+ if filenamelogger then
+ filenamelogger(name,"r",asked,true)
+ end
+ return name
+ end
+ elseif filenamelogger then
+ filenamelogger(name,"*",name,false)
+ end
+ else
+ return name
+ end
+end
+
+local function readable(name,finalized)
+-- if platform == "windows" then -- yes or no
+-- name = lower(name) -- we assume ascii names
+-- end
+ return validfilename(name,"r")
+end
+
+local function normalizedreadable(name,finalized)
+-- if platform == "windows" then -- yes or no
+-- name = lower(name) -- we assume ascii names
+-- end
+ local valid = validfilename(name,"r")
+ if valid then
+ return normalized(valid)
+ end
+end
+
+local function writeable(name,finalized)
+-- if platform == "windows" then
+-- name = lower(name) -- we assume ascii names
+-- end
+ return validfilename(name,"w")
+end
+
+local function normalizedwriteable(name,finalized)
+-- if platform == "windows" then
+-- name = lower(name) -- we assume ascii names
+-- end
+ local valid = validfilename(name,"w")
+ if valid then
+ return normalized(valid)
+ end
+end
+
+validators.readable = readable
+validators.writeable = normalizedwriteable
+validators.normalizedreadable = normalizedreadable
+validators.normalizedwriteable = writeable
+validators.filename = readable
+
+table.setmetatableindex(validators,function(t,k)
+ if k then
+ t[k] = readable
+ end
+ return readable
+end)
+
+-- function validators.verbose(s)
+-- return s
+-- end
+
+function validators.string(s,finalized)
+ -- can be used to prevent filename checking (todo: only when registered)
+ if finalized and suspicious(s) then
+ return ""
+ else
+ return s
+ end
+end
+
+function validators.cache(s)
+ if finalized then
+ return basename(s)
+ else
+ return s
+ end
+end
+
+function validators.url(s)
+ if finalized and find("^file:") then
+ return ""
+ else
+ return s
+ end
+end
+
+-- end of validators
+
+local function filehandlerone(action,one,...)
+ local checkedone = validfilename(one)
+ if checkedone then
+ return action(one,...)
+ else
+ -- report("file %a is unreachable",one)
+ end
+end
+
+local function filehandlertwo(action,one,two,...)
+ local checkedone = validfilename(one)
+ if checkedone then
+ local checkedtwo = validfilename(two)
+ if checkedtwo then
+ return action(one,two,...)
+ else
+ -- report("file %a is unreachable",two)
+ end
+ else
+ -- report("file %a is unreachable",one)
+ end
+end
+
+local function iohandler(action,one,...)
+ if type(one) == "string" then
+ local checkedone = validfilename(one)
+ if checkedone then
+ return action(one,...)
+ end
+ elseif one then
+ return action(one,...)
+ else
+ return action()
+ end
+end
+
+-- runners can be strings or tables
+--
+-- os.execute : string
+-- os.exec : string or table with program in [0|1] -- no longer there
+-- os.spawn : string or table with program in [0|1] -- no longer there
+--
+-- our execute: registered program with specification
+
+local osexecute = sandbox.original(os.execute)
+local iopopen = sandbox.original(io.popen)
+local reported = { }
+
+local function validcommand(name,program,template,checkers,defaults,variables,reporter,strict)
+ if validbinaries ~= false and (validbinaries == true or validbinaries[program]) then
+ local binpath = nil
+ if variables then
+ for variable, value in next, variables do
+ local chktype = checkers[variable]
+ if chktype == "verbose" then
+ -- for now, we will have a "flags" checker
+ else
+ local checker = validators[chktype]
+ if checker and type(value) == "string" then
+ value = checker(unquoted(value),strict)
+ if value then
+ variables[variable] = optionalquoted(value)
+ else
+ report("variable %a with value %a fails the check",variable,value)
+ return
+ end
+ else
+ report("variable %a has no checker",variable)
+ return
+ end
+ end
+ end
+ for variable, default in next, defaults do
+ local value = variables[variable]
+ if not value or value == "" then
+ local chktype = checkers[variable]
+ if chktype == "verbose" then
+ -- for now, we will have a "flags" checker
+ elseif type(default) == "string" then
+ local checker = validators[chktype]
+ if checker then
+ default = checker(unquoted(default),strict)
+ if default then
+ variables[variable] = optionalquoted(default)
+ else
+ report("variable %a with default %a fails the check",variable,default)
+ return
+ end
+ end
+ end
+ end
+ end
+ binpath = variables.binarypath
+ end
+ if type(binpath) == "string" and binpath ~= "" then
+ -- this works on the console but not from e.g. scite
+ -- program = '"' .. binpath .. "/" .. program .. '"'
+ program = binpath .. "/" .. program
+ end
+ local command = program .. " " .. replace(template,variables)
+ if reporter then
+ reporter("executing runner %a: %s",name,command)
+ elseif trace then
+ report("executing runner %a: %s",name,command)
+ end
+ return command
+ elseif not reported[name] then
+ report("executing program %a of runner %a is not permitted",program,name)
+ reported[name] = true
+ end
+end
+
+local runners = {
+ --
+ -- name,program,template,checkers,variables,reporter
+ --
+ resultof = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("resultof: %s",command)
+ end
+ local handle = iopopen(command,"rb") -- already has flush
+ if handle then
+ local result = handle:read("*all") or ""
+ handle:close()
+ return result
+ end
+ end
+ end,
+ execute = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("execute: %s",command)
+ end
+ local okay = osexecute(command)
+ return okay
+ end
+ end,
+ pipeto = function(...)
+ local command = validcommand(...)
+ if command then
+ if trace then
+ report("pipeto: %s",command)
+ end
+ return iopopen(command,"w") -- already has flush
+ end
+ end,
+}
+
+function sandbox.registerrunner(specification)
+ if type(specification) == "string" then
+ local wrapped = validrunners[specification]
+ inspect(table.sortedkeys(validrunners))
+ if wrapped then
+ return wrapped
+ else
+ report("unknown predefined runner %a",specification)
+ return
+ end
+ end
+ if type(specification) ~= "table" then
+ report("specification should be a table (or string)")
+ return
+ end
+ local name = specification.name
+ if type(name) ~= "string" then
+ report("invalid name, string expected",name)
+ return
+ end
+ if validrunners[name] then
+ report("invalid name, runner %a already defined",name)
+ return
+ end
+ local program = specification.program
+ if type(program) == "string" then
+ -- common for all platforms
+ elseif type(program) == "table" then
+ program = program[platform] or program.default or program.unix
+ end
+ if type(program) ~= "string" or program == "" then
+ report("invalid runner %a specified for platform %a",name,platform)
+ return
+ end
+ local template = specification.template
+ if not template then
+ report("missing template for runner %a",name)
+ return
+ end
+ local method = specification.method or "execute"
+ local checkers = specification.checkers or { }
+ local defaults = specification.defaults or { }
+ local internal = specification.internal
+ local runner = runners[method]
+ if runner then
+ local finalized = finalized -- so, the current situation is frozen
+ local internalized = false
+ local wrapped = function(variables)
+ if internal and not internalized then
+ -- So internal returns a function but can also just return false in which
+ -- case we fallback in the runner; this permits optional library support.
+ internal = internal(specification)
+ if type(internal) ~= "function" then
+ internal = false
+ end
+ internalized = true
+ end
+ return (internal or runner)(name,program,template,checkers,defaults,variables,specification.reporter,finalized)
+ end
+ validrunners[name] = wrapped
+ return wrapped
+ else
+ validrunners[name] = nil
+ report("invalid method for runner %a",name)
+ end
+end
+
+function sandbox.getrunner(name)
+ return name and validrunners[name]
+end
+
+local function suspicious(str)
+ return (find(str,"[/\\]") or find(command,"..",1,true)) and true or false
+end
+
+local function binaryrunner(action,command,...)
+ if validbinaries == false then
+ -- nothing permitted
+ report("no binaries permitted, ignoring command: %s",command)
+ return
+ end
+ if type(command) ~= "string" then
+ -- we only handle strings, maybe some day tables
+ report("command should be a string")
+ return
+ end
+ local program = lpegmatch(p_split,command)
+ if not program or program == "" then
+ report("unable to filter binary from command: %s",command)
+ return
+ end
+ if validbinaries == true then
+ -- everything permitted
+ elseif not validbinaries[program] then
+ report("binary not permitted, ignoring command: %s",command)
+ return
+ elseif suspicious(command) then
+ report("/ \\ or .. found, ignoring command (use sandbox.registerrunner): %s",command)
+ return
+ end
+ return action(command,...)
+end
+
+local function dummyrunner(action,command,...)
+ if type(command) == "table" then
+ command = concat(command," ",command[0] and 0 or 1)
+ end
+ report("ignoring command: %s",command)
+end
+
+sandbox.filehandlerone = filehandlerone
+sandbox.filehandlertwo = filehandlertwo
+sandbox.iohandler = iohandler
+
+do
+
+ local library = optional.library
+ local foreign = optional.foreign
+ local reported = { }
+ local libraryload = library.load
+ local foreignload = foreign.load
+
+ function library.load(name,...)
+ if validlibraries == false then
+ -- all blocked
+ elseif validlibraries == true then
+ -- all permitted
+ return libraryload(name,...)
+ elseif validlibraries[nameonly(name)] then
+ -- 'name' permitted
+ return libraryload(name,...)
+ else
+ -- 'name' not permitted
+ end
+ if not reported[name] then
+ report("using library %a is not permitted",name)
+ reported[name] = true
+ end
+ return nil
+ end
+
+ function foreign.load(name,...)
+ if validlibraries == false then
+ -- all blocked
+ elseif validlibraries == true then
+ -- all permitted
+ return foreignload(name,...)
+ elseif validlibraries[nameonly(name)] then
+ -- 'name' permitted
+ return foreignload(name,...)
+ else
+ -- 'name' not permitted
+ end
+ if not reported[name] then
+ report("using foreign %a is not permitted",name)
+ reported[name] = true
+ end
+ return nil
+ end
+
+ -- we can do this in the engine: a one time flag
+
+ function sandbox.disablelibraries()
+ validlibraries = false
+ library.load = function() end
+ foreign.load = function() end
+ end
+
+ function sandbox.disablerunners()
+ validbinaries = false
+ end
+
+end
+
+-------------------
+
+local overload = sandbox.overload
+local register = sandbox.register
+
+ overload(loadfile, filehandlerone,"loadfile") -- todo
+
+if io then
+ overload(io.open, filehandlerone,"io.open")
+ overload(io.popen, binaryrunner, "io.popen")
+ overload(io.input, iohandler, "io.input")
+ overload(io.output, iohandler, "io.output")
+ overload(io.lines, filehandlerone,"io.lines")
+end
+
+if os then
+ overload(os.execute, binaryrunner, "os.execute")
+ overload(os.spawn, dummyrunner, "os.spawn") -- no longer there
+ overload(os.exec, dummyrunner, "os.exec") -- no longer there
+ overload(os.resultof, binaryrunner, "os.resultof")
+ overload(os.pipeto, binaryrunner, "os.pipeto")
+ overload(os.rename, filehandlertwo,"os.rename")
+ overload(os.remove, filehandlerone,"os.remove")
+end
+
+if lfs then
+ overload(lfs.chdir, filehandlerone,"lfs.chdir")
+ overload(lfs.mkdir, filehandlerone,"lfs.mkdir")
+ overload(lfs.rmdir, filehandlerone,"lfs.rmdir")
+ overload(lfs.isfile, filehandlerone,"lfs.isfile")
+ overload(lfs.isdir, filehandlerone,"lfs.isdir")
+ overload(lfs.attributes, filehandlerone,"lfs.attributes")
+ overload(lfs.dir, filehandlerone,"lfs.dir")
+ overload(lfs.lock_dir, filehandlerone,"lfs.lock_dir")
+ overload(lfs.touch, filehandlerone,"lfs.touch")
+ overload(lfs.link, filehandlertwo,"lfs.link")
+ overload(lfs.setmode, filehandlerone,"lfs.setmode")
+ overload(lfs.readlink, filehandlerone,"lfs.readlink")
+ overload(lfs.shortname, filehandlerone,"lfs.shortname")
+ overload(lfs.symlinkattributes,filehandlerone,"lfs.symlinkattributes")
+end
+
+-- these are used later on
+
+if zip then
+ zip.open = register(zip.open, filehandlerone,"zip.open")
+end
+
+sandbox.registerroot = registerroot
+sandbox.registerbinary = registerbinary
+sandbox.registerlibrary = registerlibrary
+sandbox.validfilename = validfilename
+
+-- not used in a normal mkiv run : os.spawn = os.execute
+-- not used in a normal mkiv run : os.exec = os.exec
+
+-- print(io.open("test.log"))
+-- sandbox.enable()
+-- print(io.open("test.log"))
+-- print(io.open("t:/test.log"))