From 35224bce8acaeffd921e8a8fc86ba7a44ae72cc4 Mon Sep 17 00:00:00 2001 From: Pekka Laiho Date: Sat, 5 Dec 2020 11:24:31 +0700 Subject: [PATCH] add support for experimental safe-mode which disables some functionality --- src/Evaller.php | 13 ++++-- src/Lib/Core.php | 110 +++++++++++++++++++++++++------------------- src/LispFactory.php | 22 ++++----- 3 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/Evaller.php b/src/Evaller.php index 9da791d..24fb572 100644 --- a/src/Evaller.php +++ b/src/Evaller.php @@ -6,14 +6,16 @@ class Evaller protected Tokenizer $tokenizer; protected Reader $reader; protected Printer $printer; + protected bool $safemode; protected bool $debug = false; - public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer) + public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, bool $safemode) { $this->tokenizer = $tokenizer; $this->reader = $reader; $this->printer = $printer; + $this->safemode = $safemode; } public function eval($ast, Env $env) @@ -116,7 +118,7 @@ class Evaller $ast = $astData[$astLength - 1]; continue; // tco - } elseif ($symbolName == 'env') { + } elseif (!$this->safemode && $symbolName == 'env') { if ($astLength >= 2) { if (!($astData[1] instanceof Symbol)) { throw new MadLispException("first argument to env is not symbol"); @@ -126,7 +128,7 @@ class Evaller } else { return $env; } - } elseif ($symbolName == 'eval') { + } elseif (!$this->safemode && $symbolName == 'eval') { if ($astLength == 1) { return null; } @@ -207,10 +209,13 @@ class Evaller $ast = $astData[2]; $env = $newEnv; continue; // tco - } elseif ($symbolName == 'load') { + } elseif (!$this->safemode && $symbolName == 'load') { // Load is here because we want to load into // current $env which is hard otherwise. + // This is disabled now for safe-mode, but some + // use (maybe restricted) might need to be allowed. + if ($astLength != 2) { throw new MadLispException("load requires exactly 1 argument"); } diff --git a/src/Lib/Core.php b/src/Lib/Core.php index 2307184..ae402b1 100644 --- a/src/Lib/Core.php +++ b/src/Lib/Core.php @@ -20,30 +20,36 @@ class Core implements ILib protected Reader $reader; protected Printer $printer; protected Evaller $evaller; + protected bool $safemode; - public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, Evaller $evaller) + public function __construct(Tokenizer $tokenizer, Reader $reader, Printer $printer, Evaller $evaller, bool $safemode) { $this->tokenizer = $tokenizer; $this->reader = $reader; $this->printer = $printer; $this->evaller = $evaller; + $this->safemode = $safemode; } public function register(Env $env): void { // Register special constants - $env->set('__FILE__', null); - $env->set('__DIR__', null); + if (!$this->safemode) { + $env->set('__FILE__', null); + $env->set('__DIR__', null); + } - $env->set('debug', new CoreFunc('debug', 'Toggle debug mode.', 0, 0, - function () { - $val = !$this->evaller->getDebug(); - $this->evaller->setDebug($val); - return $val; - } - )); + if (!$this->safemode) { + $env->set('debug', new CoreFunc('debug', 'Toggle debug mode.', 0, 0, + function () { + $val = !$this->evaller->getDebug(); + $this->evaller->setDebug($val); + return $val; + } + )); + } - $env->set('doc', new CoreFunc('doc', 'Get or set documentation for a function.', 1, 2, + $env->set('doc', new CoreFunc('doc', 'Get or set documentation string for a function.', 1, 2, function (Func $a, ?string $str = null) { if (func_num_args() == 1) { return $a->getDoc(); @@ -63,53 +69,63 @@ class Core implements ILib } )); - $env->set('meta', new CoreFunc('meta', 'Read meta information of an entity.', 2, 2, - function ($obj, $attribute) { - if ($obj instanceof Env) { - if ($attribute == 'name') { - return $obj->getFullName(); - } elseif ($attribute == 'parent') { - return $obj->getParent(); + if (!$this->safemode) { + $env->set('meta', new CoreFunc('meta', 'Read meta information of an entity.', 2, 2, + function ($obj, $attribute) { + if ($obj instanceof Env) { + if ($attribute == 'name') { + return $obj->getFullName(); + } elseif ($attribute == 'parent') { + return $obj->getParent(); + } else { + throw new MadLispException('unknown attribute for meta'); + } + } elseif ($obj instanceof UserFunc) { + if ($attribute == 'args') { + return $obj->getBindings(); + } elseif ($attribute == 'body') { + return $obj->getAst(); + } elseif ($attribute == 'code') { + return new MList([new Symbol('fn'), $obj->getBindings(), $obj->getAst()]); + } else { + throw new MadLispException('unknown attribute for meta'); + } } else { - throw new MadLispException('unknown attribute for meta'); + throw new MadLispException('unknown entity for meta'); } - } elseif ($obj instanceof UserFunc) { - if ($attribute == 'args') { - return $obj->getBindings(); - } elseif ($attribute == 'body') { - return $obj->getAst(); - } elseif ($attribute == 'code') { - return new MList([new Symbol('fn'), $obj->getBindings(), $obj->getAst()]); - } else { - throw new MadLispException('unknown attribute for meta'); - } - } else { - throw new MadLispException('unknown entity for meta'); } - } - )); + )); + } - $env->set('read', new CoreFunc('read', 'Read string as code.', 1, 1, - fn (string $a) => $this->reader->read($this->tokenizer->tokenize($a)) - )); + if (!$this->safemode) { + $env->set('read', new CoreFunc('read', 'Read string as code.', 1, 1, + fn (string $a) => $this->reader->read($this->tokenizer->tokenize($a)) + )); + } - $env->set('print', new CoreFunc('print', 'Print argument. Give second argument as true to show strings in readable format.', 1, 2, - function ($a, bool $readable = false) { - $this->printer->print($a, $readable); - return null; - } - )); + if (!$this->safemode) { + $env->set('print', new CoreFunc('print', 'Print argument. Give second argument as true to show strings in readable format.', 1, 2, + function ($a, bool $readable = false) { + $this->printer->print($a, $readable); + return null; + } + )); + } + // This is allowed in safe-mode, because the evaluation should be wrapped in a try-catch in embedded use. $env->set('error', new CoreFunc('error', 'Throw an exception using argument (string) as message.', 1, 1, function (string $error) { + // We should probably use another exception type to distinguish user-thrown errors from built-in errors. throw new MadLispException($error); } )); - $env->set('exit', new CoreFunc('exit', 'Terminate the script with given exit code.', 0, 1, - function (int $status = 0) { - exit($status); - } - )); + if (!$this->safemode) { + $env->set('exit', new CoreFunc('exit', 'Terminate the script with given exit code.', 0, 1, + function (int $status = 0) { + exit($status); + } + )); + } } } diff --git a/src/LispFactory.php b/src/LispFactory.php index 484863e..d566251 100644 --- a/src/LispFactory.php +++ b/src/LispFactory.php @@ -3,25 +3,22 @@ namespace MadLisp; class LispFactory { - public function make(array $coreLibs = [], array $userLibs = []): Lisp + public function make(bool $safemode = false): Lisp { $tokenizer = new Tokenizer(); $reader = new Reader(); $printer = new Printer(); - $eval = new Evaller($tokenizer, $reader, $printer); + $eval = new Evaller($tokenizer, $reader, $printer, $safemode); // Root environment $env = new Env('root'); // Register core functions - (new Lib\Core($tokenizer, $reader, $printer, $eval))->register($env); + (new Lib\Core($tokenizer, $reader, $printer, $eval, $safemode))->register($env); // Register core libraries (new Lib\Collections())->register($env); (new Lib\Compare())->register($env); - (new Lib\Database())->register($env); - (new Lib\Http())->register($env); - (new Lib\IO())->register($env); (new Lib\Json())->register($env); (new Lib\Math())->register($env); (new Lib\Regex())->register($env); @@ -29,19 +26,16 @@ class LispFactory (new Lib\Time())->register($env); (new Lib\Types())->register($env); - // Register additional libs for root env - foreach ($coreLibs as $lib) { - $lib->register($env); + // Register unsafe libraries if not in safemode + if (!$safemode) { + (new Lib\Database())->register($env); + (new Lib\Http())->register($env); + (new Lib\IO())->register($env); } // User environment $env = new Env('user', $env); - // Register additional libs for user env - foreach ($userLibs as $lib) { - $lib->register($env); - } - return new Lisp($tokenizer, $reader, $eval, $printer, $env); } }