> > This looks quite valuable, and I assume auto loading would work just > like normal? Register an autoloader that will eventually require the > file and call this function? > > It would be nice to provide a simplified api as well, maybe > “CopyCurrentEnvironment()” or something? In most cases, it is > easier/faster to find things to remove vs. adding everything on every > plugin/request every time. > > In saying that, it would be great if there was an api for “sharing” a > base-sandbox pool via shm (or similar to a pool) so that the base vm > doesn’t need to be recreated potentially hundreds of times per > request. >
I didn't want to be too overwhelming on the first post, but since it seems the feedback is positive, here's a more complete list of what I think should be included: // Passthroughs: // Make all user and built-in global functions // available inside the sandbox: SPLSandBox::PassGlobalFunctions(); // Make all built-in (but not user) functions // available inside the sandbox: SPLSandBox::PassBuiltInFunctions(); // Make all built-in (but not user) functions // available inside the sandbox, EXCEPT blacklisted functions: SPLSandBox::PassBuiltInFunctionsExcept(['eval','exit']); (assuming exit becomes a function). // Allow only specific functions to be called (whitelist method): $aWhiteList = ['array_key_exists','in_array']; SPLSandBox::PassFunctions($aWhiteList); // Allow specific classes to be used by sandbox code: $aClassList = ['\MyAPP\PluginAPI']; SPLSandBox::PassClasses($aClassList); // Allow specific constants to be seen by sandbox code: SPLSandBox::PassConstants(['\DB_USERNAME','\DB_PASSWORD']); // Language Construct Callbacks: The callbacks allow the outer code to control and monitor certain language features of the sandboxed code during execution. // Called when the sandbox code tries to include or require something: SPLSandBox::RegisterIncludeHandler(); // Includes a file into the sandbox: SPLSandBox::Include('path/to/file.php'); // Your sandbox autoloader logic could be incorporated here: SPLSandBox::RegisterAutoLoadHandler(); // But, for unit testing with mocks and stubs, // it might be better to use: SPLSandBox::RegisterNewHandler(); The NewHandler callback is called every time sandboxed code tries to instantiate an object with `new`. // Example: Override what `new` returns to code running in the sandbox: function MyNewHandler(string $ClassName, array $aConstructorArgs){ if($ClassName === '\DateTime'){ return new FakeDate(); } return new $ClassName($aConstructorArgs); } // Every time a sandboxed class calls a method, call this first: SPLSandBox::RegisterMethodCallHandler(); Useful for unit testing to monitor if the tested class is calling the methods it should be calling. Ignores visibility rules. Could also allow for infinite recursion detection from the outside. // The companion for static method calls, gets called // every time a method is called on a class statically: SPLSandBox::RegisterStaticMethodCallHandler(); // Each time a sandboxed loop iterates, call this first: // Allows the outer code to put limit breaks on the sandboxed code. SPLSandBox::RegisterLoopHandler(); The callback takes the type of loop, and the variables that make up the loop ($i for for(), $Key => $value for foreach(), etc) // If the sandboxed code calls echo, print, or // causes any output to occur (ie outside of <?php tags: SPLSandBox::RegisterEchoHandler(); Could be used to make sure templates behave as desired, but perhaps even more useful, it lets you *fail* unit tests if any output occurs from a test that shouldn't produce output. ie. Catch echo statements used in testing, or whitespace after a closing ?> tag. // If the sandbox code tries to use `exit` or `die`, // call this function instead: SPLSandBox::RegisterExitHandler(); You'll probably want to destroy the sandbox from the outside (see below), rather than letting sandboxed code halt the test framework or main application. // If sandboxed code throws, it should *not* // be a throw in the outer application space. // Every exception throw triggers this callback, // even if there is a catch block: SPLSandBox::RegisterExceptionHandler(); // When a catch block runs, invoke this callback first: SPLSandBox::RegisterCatchHandler(); Allows unit tests to make sure that exceptions are handled correctly. // For non-Exceptions (warning, notice, deprecated, fatal, // and yes, maybe even parse because we're in a sandbox): SPLSandBox::RegisterErrorHandler(); // Inside your Handlers, you may want to know the file and line // that triggered the callback. SPLSandBox::GetCurrentLine(); SPLSandBox::GetFileName(); SPLSandBox::GetClassName(); SPLSandBox::GetFunctionName(); For example, if you want to catch any echo left behind from debugging, you might also want to find the line and file where the statement is located to remove it. The above methods would be usable inside any of the callbacks. // Inside your Handlers, abort execution: SPLSandBox::Stop(); // Resource Limits, hopefully self explanatory: SPLSandBox::SetMemoryLimit(); SPLSandBox::SetExecutionTimeLimit(); // Mocks, Stubs: // Put a function from the outer application // into the sandbox as the specified name: SPLSandBox::MockFunction('\mocks\fopen','\fopen'); // Put a class from the outer application // into the sandbox as the specified name: SPLSandBox::MockClass('\Mocks\FakeTime','\DateTime'); // Set global variables (and super globals) inside the sandbox: SPLSandBox::MockGlobal('$_GET',$aGetVars); // Set global constants inside the sandbox: SPLSandBox::MockConstant('MY_CONSTANT',$Value); // Invocation // You can run procedural code to setup your test environment. // Runs the array of code lines in the sandbox context: $aProceduralCode = [ "$a = 1;", "$b = 2;", "$c = DoSomething($a, $b);" ]; SPLSandBox::Procedure($aProceduralCode); // You can get a pointer to an object instantiated in the sandbox: $oClass = SPLSandBox::GetInstance('ClassName'); // And use it like you normally would: $oClass->DoSomething(); This class runs entirely in the sandbox. // You cleanup the resource with: SPLSandBox::Destroy();