luatex-core.lua /size: 12 Kb    last modification: 2024-01-16 09:03
1-- luatex-core security and io overloads ..
2
3-- if not modules then modules = { } end modules ['luatex-core'] = {
4--     version   = 1.112,
5--     comment   = 'companion to luatex',
6--     author    = 'Hans Hagen & Luigi Scarso',
7--     copyright = 'LuaTeX Development Team',
8-- }
9
10LUATEXCOREVERSION = 1.161 -- we reflect the luatex version where changes happened
11
12-- This file overloads some Lua functions. The readline variants provide the same
13-- functionality as LuaTeX <= 1.04 and doing it this way permits us to keep the
14-- original io libraries clean. Performance is probably even a bit better now.
15
16-- We test for functions already being defined so that we don't overload ones that
17-- are provided in the startup script.
18
19local saferoption = status.safer_option
20local shellescape = status.shell_escape -- 0 (disabled) 1 (anything) 2 (restricted)
21local kpseused    = status.kpse_used    -- 0 1
22
23if kpseused == 1 then
24
25    local type = type
26    local gsub = string.gsub
27    local find = string.find
28
29    local mt                    = getmetatable(io.stderr)
30    local mt_lines              = mt.lines
31
32    local kpse_checkpermission  = kpse.check_permission
33    local kpse_recordinputfile  = kpse.record_input_file
34    local kpse_recordoutputfile = kpse.record_output_file
35
36    local kpse_outputnameok     = kpse.out_name_ok
37    local kpse_inputnameok      = kpse.in_name_ok
38
39    local io_open               = io.open
40    local io_popen              = kpse.popen or io.popen -- should have been be in the kpse namespace
41    local io_lines              = io.lines
42
43    local fio_readline          = fio.readline
44
45    local write_nl              = texio.write_nl
46
47    io.saved_lines              = io_lines -- always readonly
48    mt.saved_lines              = mt_lines -- always readonly
49
50    -- The version below is different from the luatex one (which is probably made for
51    -- LaTeX) because read should not be the default (e.g. when how == 'q'). Also in
52    -- that version record doesn't always work for write. How about append?
53
54    local function validinput(name,how)
55        if type(how) ~= "string" or how == "" then
56            how = "r"
57        end
58        return not find(how,"w") and kpse_inputnameok(name) and io_open(name,how)
59    end
60
61    local function validoutput(name,how)
62        return type(how) == "string" and find(how,"w") and kpse_outputnameok(name) and io_open(name,how)
63    end
64
65    local function luatex_io_open(name,how)
66        local handle = validinput(name,how)
67        if handle then
68            kpse_recordinputfile(name,"r")
69        else
70            handle = validoutput(name,how)
71            if handle then
72                kpse_recordoutputfile(name,"w")
73            end
74        end
75        return handle
76    end
77
78    local function luatex_io_open_readonly(name,how)
79        local handle = validinput(name,how)
80        if handle then
81            kpse_recordinputfile(name,"r")
82        end
83        return handle
84    end
85
86 -- local function luatex_io_popen(name,...)
87 --     local okay, found = kpse_checkpermission(name)
88 --     if okay and found then
89 --         return io_popen(found,...)
90 --     end
91 -- end
92
93    -- For some reason the gc doesn't kick in so we need to close explicitly
94    -- so that the handle is flushed.
95
96    local error, type = error, type
97
98    local function luatex_io_lines(name,how)
99        if type(name) == "string" then
100            local handle = validinput(name,how)
101            if handle then
102                kpse_recordinputfile(name,"r")
103                return function()
104                    local l = fio_readline(f)
105                    if not l then
106                        f:close()
107                    end
108                    return l
109                end
110            else
111                -- for those who like it this way:
112                error("patched 'io.lines' can't open '" .. name .. "'")
113            end
114        else
115            return io_lines()
116        end
117    end
118
119    local function luatex_io_readline(f)
120        return function()
121            return fio_readline(f)
122        end
123    end
124
125    io.lines = luatex_io_lines
126    mt.lines = luatex_io_readline
127
128    io.open  = luatex_io_open
129 -- io.popen = luatex_io_popen
130    io.popen = io_popen
131
132else
133
134    -- we assume management elsewhere
135
136end
137
138-- maybe also only when in kpse mode
139
140if saferoption == 1 then
141
142    local write_nl = texio.write_nl
143    local format   = string.format
144
145    local function installdummy(str,f)
146        local reported = false
147        return function(...)
148            if not reported then
149                write_nl(format("safer option set, function %q is %s",
150                    str,f and "limited" or "disabled"))
151                reported = true
152            end
153            if f then
154                return f(...)
155            end
156        end
157    end
158
159    local function installlimit(str,f)
160        local reported = false
161    end
162
163    os.execute   = installdummy("os.execute")
164    os.spawn     = installdummy("os.spawn")
165    os.exec      = installdummy("os.exec")
166    os.setenv    = installdummy("os.setenv")
167    os.tempdir   = installdummy("os.tempdir")
168
169    io.popen     = installdummy("io.popen")
170    io.open      = installdummy("io.open",luatex_io_open_readonly)
171
172    os.kpsepopen = io.popen -- because it's in the os namespace ... brr
173
174    os.rename    = installdummy("os.rename")
175    os.remove    = installdummy("os.remove")
176
177    io.tmpfile   = installdummy("io.tmpfile")
178    io.output    = installdummy("io.output")
179
180    lfs.chdir    = installdummy("lfs.chdir")
181    lfs.lock     = installdummy("lfs.lock")
182    lfs.touch    = installdummy("lfs.touch")
183    lfs.rmdir    = installdummy("lfs.rmdir")
184    lfs.mkdir    = installdummy("lfs.mkdir")
185
186    debug = nil
187    package.loaded.debug = nil
188
189    -- os.[execute|os.spawn|os.exec] already are shellescape aware)
190
191end
192
193-- maybe also only when in kpse mode
194
195if saferoption == 1 or shellescape ~= 1 then
196
197    package.loadlib      = function() end
198    package.searchers[4] = nil
199    package.searchers[3] = nil
200
201    if os.setenv then
202        os.setenv = function(...) end -- Great, this will fail for some usage.
203    end
204
205    ffi = require('ffi')
206
207    if ffi then
208        for k, v in next, ffi do
209            if k ~= 'gc' then
210                ffi[k] = nil
211            end
212        end
213    end
214
215    ffi = nil
216
217    -- A patch by LS for LaTeX per April 2022 but I'm, not sure if that breaks ConTeXt MKIV. I have
218    -- no time nor motivation to test that right now so we'll see where it breaks. We have different
219    -- loaders anyway and we don't test plain.
220
221    package.loaded .ffi = nil   -- Isn't that still nil then?
222    package.preload.ffi = error -- Do errors always go there?
223
224end
225
226if md5 then
227
228    local sum    = md5.sum
229    local gsub   = string.gsub
230    local format = string.format
231    local byte   = string.byte
232
233    if not md5.sumhexa then
234        function md5.sumhexa(k)
235            return (gsub(sum(k), ".", function(c)
236                return format("%02x",byte(c))
237            end))
238        end
239    end
240
241    if not md5.sumHEXA then
242        function md5.sumHEXA(k)
243            return (gsub(sum(k), ".", function(c)
244                return format("%02X",byte(c))
245            end))
246        end
247    end
248
249end
250
251-- compatibility: this might go away
252
253if not unpack then
254    unpack = table.unpack
255end
256
257if not package.loaders then
258    package.loaders = package.searchers
259end
260
261if not loadstring then
262    loadstring = load
263end
264
265-- compatibility: this might stay
266
267if bit32 then
268
269    -- lua 5.2: we're okay
270
271elseif utf8 then
272
273    -- lua 5.3:  bitwise.lua, v 1.24 2014/12/26 17:20:53 roberto
274
275    bit32 = load ( [[
276local select = select -- instead of: arg = { ... }
277
278bit32 = {
279  bnot = function (a)
280    return ~a & 0xFFFFFFFF
281  end,
282  band = function (x, y, z, ...)
283    if not z then
284      return ((x or -1) & (y or -1)) & 0xFFFFFFFF
285    else
286      local res = x & y & z
287      for i=1,select("#",...) do
288        res = res & select(i,...)
289      end
290      return res & 0xFFFFFFFF
291    end
292  end,
293  bor = function (x, y, z, ...)
294    if not z then
295      return ((x or 0) | (y or 0)) & 0xFFFFFFFF
296    else
297      local res = x | y | z
298      for i=1,select("#",...) do
299        res = res | select(i,...)
300      end
301      return res & 0xFFFFFFFF
302    end
303  end,
304  bxor = function (x, y, z, ...)
305    if not z then
306      return ((x or 0) ~ (y or 0)) & 0xFFFFFFFF
307    else
308      local res = x ~ y ~ z
309      for i=1,select("#",...) do
310        res = res ~ select(i,...)
311      end
312      return res & 0xFFFFFFFF
313    end
314  end,
315  btest = function (x, y, z, ...)
316    if not z then
317      return (((x or -1) & (y or -1)) & 0xFFFFFFFF) ~= 0
318    else
319      local res = x & y & z
320      for i=1,select("#",...) do
321          res = res & select(i,...)
322      end
323      return (res & 0xFFFFFFFF) ~= 0
324    end
325  end,
326  lshift = function (a, b)
327    return ((a & 0xFFFFFFFF) << b) & 0xFFFFFFFF
328  end,
329  rshift = function (a, b)
330    return ((a & 0xFFFFFFFF) >> b) & 0xFFFFFFFF
331  end,
332  arshift = function (a, b)
333    a = a & 0xFFFFFFFF
334    if b <= 0 or (a & 0x80000000) == 0 then
335      return (a >> b) & 0xFFFFFFFF
336    else
337      return ((a >> b) | ~(0xFFFFFFFF >> b)) & 0xFFFFFFFF
338    end
339  end,
340  lrotate = function (a ,b)
341    b = b & 31
342    a = a & 0xFFFFFFFF
343    a = (a << b) | (a >> (32 - b))
344    return a & 0xFFFFFFFF
345  end,
346  rrotate = function (a, b)
347    b = -b & 31
348    a = a & 0xFFFFFFFF
349    a = (a << b) | (a >> (32 - b))
350    return a & 0xFFFFFFFF
351  end,
352  extract = function (a, f, w)
353    return (a >> f) & ~(-1 << (w or 1))
354  end,
355  replace = function (a, v, f, w)
356    local mask = ~(-1 << (w or 1))
357    return ((a & ~(mask << f)) | ((v & mask) << f)) & 0xFFFFFFFF
358  end,
359}
360        ]] )
361
362elseif bit then
363
364    -- luajit (for now)
365
366    bit32 = load ( [[
367local band, bnot, rshift, lshift = bit.band, bit.bnot, bit.rshift, bit.lshift
368
369bit32 = {
370  arshift = bit.arshift,
371  band    = band,
372  bnot    = bnot,
373  bor     = bit.bor,
374  bxor    = bit.bxor,
375  btest   = function(...)
376    return band(...) ~= 0
377  end,
378  extract = function(a,f,w)
379    return band(rshift(a,f),2^(w or 1)-1)
380  end,
381  lrotate = bit.rol,
382  lshift  = lshift,
383  replace = function(a,v,f,w)
384    local mask = 2^(w or 1)-1
385    return band(a,bnot(lshift(mask,f)))+lshift(band(v,mask),f)
386  end,
387  rrotate = bit.ror,
388  rshift  = rshift,
389}
390        ]] )
391
392else
393
394    -- hope for the best or fail
395
396    bit32 = require("bit32")
397
398end
399
400-- this is needed for getting require("socket") right
401
402do
403
404    local loaded = package.loaded
405
406    if not loaded.socket then loaded.socket = loaded["socket.core"] end
407    if not loaded.mime   then loaded.mime   = loaded["mime.core"]   end
408
409    if not loaded.lfs then loaded.lfs = lfs end
410
411end
412
413do
414
415    local lfsattributes     = lfs.attributes
416    local symlinkattributes = lfs.symlinkattributes
417
418    -- these can now be done using lfs (was dead slow before)
419
420    if not lfs.isfile then
421        function lfs.isfile(name)
422            local m = lfsattributes(name,"mode")
423            return m == "file" or m == "link"
424        end
425    end
426
427    if not lfs.isdir then
428        function lfs.isdir(name)
429            local m = lfsattributes(name,"mode")
430            return m == "directory"
431        end
432    end
433
434    -- shortnames have also be sort of dropped from kpse
435
436    if not lfs.shortname then
437        function lfs.shortname(name)
438            return name
439        end
440    end
441
442    -- now there is a target field, so ...
443
444    if not lfs.readlink then
445        function lfs.readlink(name)
446            return symlinkattributes(name,"target") or nil
447        end
448    end
449
450end
451
452-- start omit
453
454if utilities and utilities.merger and utilities.merger.compact then
455
456    local byte, format, gmatch, gsub = string.byte, string.format, string.gmatch, string.gsub
457    local concat = table.concat
458
459    local data = io.loaddata('luatex-core.lua')
460
461    data = gsub(data,'%-%-%s*start%s*omit.-%-%-%s*stop%s*omit%s*','')
462    data = gsub(data,'\r\n','\n')
463
464    local t = { }
465    local r = { }
466    local n = 0
467    local s = utilities.merger.compact(data) -- no comments and less spaces
468
469    t[#t+1] = '/* generated from and by luatex-core.lua */'
470    t[#t+1] = ''
471    t[#t+1] = '#include "lua.h"'
472    t[#t+1] = '#include "lauxlib.h"'
473    t[#t+1] = ''
474    t[#t+1] = 'int load_luatex_core_lua (lua_State * L);'
475    t[#t+1] = ''
476    t[#t+1] = 'int load_luatex_core_lua (lua_State * L)'
477    t[#t+1] = '{'
478    t[#t+1] = '  static unsigned char luatex_core_lua[] = {'
479    for c in gmatch(data,'.') do
480        if n == 16 then
481            n = 1
482            t[#t+1] = '    ' .. concat(r,', ') .. ','
483        else
484            n = n + 1
485        end
486        r[n] = format('0x%02x',byte(c))
487    end
488    n = n + 1
489    r[n] = '0x00'
490    t[#t+1] = '    ' .. concat(r,', ',1,n)
491    t[#t+1] = '  };'
492 -- t[#t+1] = format('unsigned int luatex_core_lua_len = 0x%x;',#d+1)
493    t[#t+1] = '  return luaL_dostring(L, (const char*) luatex_core_lua);'
494    t[#t+1] = '}'
495
496    io.savedata('luatex-core.c',concat(t,'\n'))
497    io.savedata('luatex-core-stripped.lua',s)
498
499end
500
501-- stop omit
502