summaryrefslogtreecommitdiff
path: root/tex/context/base/luat-iop.lua
blob: 52f14683e76034e5331bb5aae48e88f9945c5c48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
if not modules then modules = { } end modules ['luat-iop'] = {
    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"
}

-- this paranoid stuff in web2c ... we cannot hook checks into the
-- input functions because one can always change the callback but
-- we can feed back specific patterns and paths into the next
-- mechanism

-- os.execute os.exec os.spawn io.fopen
-- os.remove lfs.chdir lfs.mkdir
-- io.open zip.open epdf.open mlib.new

-- cache

local topattern, find = string.topattern, string.find

local report_limiter = logs.reporter("system","limiter")

-- the basic methods

local function match(ruleset,name)
    local n = #ruleset
    if n > 0 then
        for i=1,n do
            local r = ruleset[i]
            if find(name,r[1]) then
                return r[2]
            end
        end
        return false
    else
        -- nothing defined (or any)
        return true
    end
end

local function protect(ruleset,proc)
    return function(name,...)
        if name == "" then
         -- report_limiter("no access permitted: <no name>") -- can happen in mplib code
            return nil, "no name given"
        elseif match(ruleset,name) then
            return proc(name,...)
        else
            report_limiter("no access permitted for %a",name)
            return nil, name .. ": no access permitted"
        end
    end
end

function io.limiter(preset)
    preset = preset or { }
    local ruleset = { }
    for i=1,#preset do
        local p = preset[i]
        local what, spec = p[1] or "", p[2] or ""
        if spec == "" then
            -- skip 'm
        elseif what == "tree" then
            resolvers.dowithpath(spec, function(r)
                local spec = resolvers.resolve(r) or ""
                if spec ~= "" then
                    ruleset[#ruleset+1] = { topattern(spec,true), true }
                end
            end)
        elseif what == "permit" then
            ruleset[#ruleset+1] = { topattern(spec,true), true }
        elseif what == "forbid" then
            ruleset[#ruleset+1] = { topattern(spec,true), false }
        end
    end
    if #ruleset > 0 then
        return {
            match   = function(name) return match  (ruleset,name) end,
            protect = function(proc) return protect(ruleset,proc) end,
        }
    else
        return {
            match   = function(name) return true end,
            protect = proc,
        }
    end
end

-- a few handlers

io.i_limiters = { }
io.o_limiters = { }

function io.i_limiter(v)
    local i = io.i_limiters[v]
    if i then
        local i_limiter = io.limiter(i)
        function io.i_limiter()
            return i_limiter
        end
        return i_limiter
    end
end

function io.o_limiter(v)
    local o = io.o_limiters[v]
    if o then
        local o_limiter = io.limiter(o)
        function io.o_limiter()
            return o_limiter
        end
        return o_limiter
    end
end

-- the real thing (somewhat fuzzy as we need to know what gets done)

local i_opener, i_limited = io.open, false
local o_opener, o_limited = io.open, false

local function i_register(v)
    if not i_limited then
        local i_limiter = io.i_limiter(v)
        if i_limiter then
            local protect = i_limiter.protect
            i_opener = protect(i_opener)
            i_limited = true
            report_limiter("input mode set to %a",v)
        end
    end
end

local function o_register(v)
    if not o_limited then
        local o_limiter = io.o_limiter(v)
        if o_limiter then
            local protect = o_limiter.protect
            o_opener = protect(o_opener)
            o_limited = true
            report_limiter("output mode set to %a",v)
        end
    end
end

function io.open(name,method)
    if method and find(method,"[wa]") then
        return o_opener(name,method)
    else
        return i_opener(name,method)
    end
end

directives.register("system.inputmode",  i_register)
directives.register("system.outputmode", o_register)

local i_limited = false
local o_limited = false

local function i_register(v)
    if not i_limited then
        local i_limiter = io.i_limiter(v)
        if i_limiter then
            local protect = i_limiter.protect
            lfs.chdir = protect(lfs.chdir) -- needs checking
            i_limited = true
        end
    end
end

local function o_register(v)
    if not o_limited then
        local o_limiter = io.o_limiter(v)
        if o_limiter then
            local protect = o_limiter.protect
            os.remove = protect(os.remove) -- rather okay
            lfs.chdir = protect(lfs.chdir) -- needs checking
            lfs.mkdir = protect(lfs.mkdir) -- needs checking
            o_limited = true
        end
    end
end

directives.register("system.inputmode",  i_register)
directives.register("system.outputmode", o_register)

-- the definitions

local limiters = resolvers.variable("limiters")

if limiters then
    io.i_limiters = limiters.input  or { }
    io.o_limiters = limiters.output or { }
end