Skip to content

Commit

Permalink
Added configuration functions to disable and sandbox string lambdas
Browse files Browse the repository at this point in the history
  • Loading branch information
Henkoglobin committed Jun 30, 2024
1 parent e34bf7a commit a66d599
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 16 deletions.
62 changes: 54 additions & 8 deletions src/lazylualinq.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,65 @@ setmetatable(linq, {

local LAMBDA_PATTERN = [[^%s*%(?(.-)%)?%s*=>%s*(.-)%s*$]]
local RETURN_PATTERN = [[%b()]]
local EMPTY_SEQUENCE
local EMPTY_SEQUENCE

local tablemt = { __index = table }

local function newTable()
return setmetatable({}, tablemt)
end

local loadString
if loadstring and setfenv then
-- Lua 5.1 - load the chunk with loadstring, set the environment with setfenv

local _loadstring = loadstring
local _setfenv = setfenv

loadString = function(chunk, env)
local func = _loadstring(chunk)
_setfenv(func, env)
return func
end
elseif load then
-- Lua 5.2 and up - load the chunk and set its environment with load

local _load = load

loadString = function(chunk, env)
return _load(chunk, chunk, "t", env)
end
else
-- Any other version (or we're sandboxed) - loading is not available.

loadString = function()
error("Neither loadstring (+setfenv) nor load are defined!")
end
end

local loadStringEnv = _G

function linq.withLoadString(newLoadString)
loadString = newLoadString
return linq
end

function linq.disableLambdas()
loadString = function()
error("Lambdas have been disabled")
end
return linq
end

function linq.withLambdaEnv(env)
loadStringEnv = env
return linq
end

function linq.isLinq(obj)
return getmetatable(obj) and getmetatable(obj).__index == linq
end

local loadstring = loadstring or load or function()
error("Neither loadstring nor load are defined!")
end

function linq.lambda(expr)
local args, rets = expr:match(LAMBDA_PATTERN)
Expand All @@ -37,12 +81,14 @@ function linq.lambda(expr)
rets = rets:sub(2, #rets - 1)
end

chunk, err = loadstring(
([[return function(%s) return %s end]]):format(args, rets)
chunk, err = loadString(
([[return function(%s) return %s end]]):format(args, rets),
loadStringEnv
)
else
chunk, err = loadstring(
([[return function(v, k) return %s end]]):format(expr)
chunk, err = loadString(
([[return function(v, k) return %s end]]):format(expr),
loadStringEnv
)
end

Expand Down
10 changes: 10 additions & 0 deletions test/_helpers.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local function getLuaVersion()
local major, minor = _VERSION:match("Lua (%d)%.(%d)")
local major, minor = tonumber(major), tonumber(minor)

return major, minor
end

return {
getLuaVersion = getLuaVersion,
}
31 changes: 31 additions & 0 deletions test/config/disableLambdas.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
describe("#configuration function disableLambdas", function()
it("disallows the use of lambdas", function()
local linq = require("lazylualinq").disableLambdas()
local seq = linq {1, 2, 3}

assert.has_error(function()
seq:select("v => v * 2")
end, "Lambdas have been disabled")
end)

it("disables linq.lambda", function()
local linq = require("lazylualinq").disableLambdas()

assert.has_error(function()
linq.lambda("v => v * 2")
end, "Lambdas have been disabled")
end)

-- It could be nice to allow this (though we'd likely want lambdas disabled by default then).
-- For the moment, though, this is how it is.
-- If we allow multiple different configurations, though, we'd need the module to return a kind of 'module factory'
-- (which would force users to do something like `local linq = require("lazylualinq").build()`)
it("disables lambdas globally", function()
local linq = require("lazylualinq").disableLambdas()
local safeLinq = require("lazylualinq")

assert.has_error(function()
safeLinq.lambda("v => v * 2")
end, "Lambdas have been disabled")
end)
end)
21 changes: 21 additions & 0 deletions test/config/withLambdaEnv.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local helpers = require("test._helpers")

describe("#configuration function withLambdaEnv", function()
it("allows access to the global environment if not called", function()
local linq = require("lazylualinq")
local func = linq.lambda("os")
local ret = func()

assert.is_equal(os, ret)
end)

it("can be used to sandbox lambdas", function()
local linq = require("lazylualinq").withLambdaEnv({})
local func = linq.lambda("os")

local ret = func()

-- os is not available within the lambda
assert.is_nil(ret)
end)
end)
7 changes: 4 additions & 3 deletions test/constructors/lambda.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local helpers = require("test._helpers")

describe("#lambda", function()
local major, minor = _VERSION:match("Lua (%d)%.(%d)")
local major, minor = tonumber(major), tonumber(minor)
local major, minor = helpers.getLuaVersion()
local extendedDebugInfoAvailable = major > 5 or (major == 5 and minor >= 2)

insulate("requires load or loadstring", function()
Expand Down Expand Up @@ -117,4 +118,4 @@ describe("#lambda", function()
assert.is_false(info.isvararg)
end)
end)
end)
end)
5 changes: 3 additions & 2 deletions test/meta/__len.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
local helpers = require("test._helpers")

describe("metafunction '__len'", function()
local linq = require("lazylualinq")

local major, minor = _VERSION:match("Lua (%d)%.(%d)")
local major, minor = tonumber(major), tonumber(minor)
local major, minor = helpers.getLuaVersion()
local lenMetamethodAvailable = major > 5 or (major == 5 and minor >= 2)

it("returns the length of the sequence", function()
Expand Down
7 changes: 4 additions & 3 deletions test/meta/__pairs.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
local helpers = require("test._helpers")

describe("metamethod '__pairs'", function()
local linq = require("lazylualinq")

local major, minor = _VERSION:match("Lua (%d)%.(%d)")
local major, minor = tonumber(major), tonumber(minor)
local major, minor = helpers.getLuaVersion()
local pairsMetamethodAvailable = major > 5 or (major == 5 and minor >= 2)

it("allows iterating with for ... in pairs()", function()
Expand Down Expand Up @@ -84,4 +85,4 @@ describe("metamethod '__pairs'", function()
-- Assert that we actually iterated all results
assert.is_same(2, count)
end)
end)
end)

0 comments on commit a66d599

Please sign in to comment.