V8Js ==== V8Js is a PHP extension for Google's V8 Javascript engine. The extension allows you to execute Javascript code in a secure sandbox from PHP. The executed code can be restricted using a time limit and/or memory limit. This provides the possibility to execute untrusted code with confidence. Minimum requirements -------------------- - V8 Javascript Engine library (libv8) master (trunk) V8 is Google's open source Javascript engine. V8 is written in C++ and is used in Google Chrome, the open source browser from Google. V8 implements ECMAScript as specified in ECMA-262, 5th edition. This extension makes use of V8 isolates to ensure separation between multiple V8Js instances and uses the new isolate-based mechanism to throw exceptions, hence the need for 3.24.10 or above. For a detailed overview of which V8 version V8Js can be successfully built against, see the [Jenkins V8Js build matrix](https://jenkins.brokenpipe.de/job/docker-v8js-matrix/). - PHP 5.3.3+ This embedded implementation of the V8 engine uses thread locking so it should work with ZTS enabled. However, this has not been tested yet. COMPILING LATEST VERSION ======================== Instead of compiling manually you might want to pull from the [V8Js docker repository](https://registry.hub.docker.com/u/stesie/v8js/). You also might want to try the Debian & Ubuntu packages available from the Jenkins site at https://jenkins.brokenpipe.de/ Building on Microsoft Windows is more complicated, see README.Win32.md file for a quick run through. Compile latest v8 ----------------- ``` cd /tmp git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH=`pwd`/depot_tools:"$PATH" git clone https://github.com/v8/v8.git cd v8 make dependencies make native library=shared -j8 sudo mkdir -p /usr/lib /usr/include sudo cp out/native/lib.target/lib*.so /usr/lib/ echo -e "create /usr/lib/libv8_libplatform.a\naddlib out/native/obj.target/tools/gyp/libv8_libplatform.a\nsave\nend" | sudo ar -M sudo cp -R include/* /usr/include ``` If you don't want to overwrite the system copy of v8, replace `/usr` in the above commands with `/tmp/v8-install` and then add `--with-v8js=/tmp/v8-install` to the php-v8js `./configure` command below. `libv8_libplatform.a` should not be copied directly since it's a thin archive, i.e. it contains only pointers to the build objects, which otherwise must not be deleted. The simple mri-script converts the thin archive to a normal archive. Compile php-v8js itself ----------------------- ``` cd /tmp git clone https://github.com/preillyme/v8js.git cd v8js phpize ./configure make sudo make install ``` PHP API ======= ```php class V8Js { /* Constants */ const string V8_VERSION; const int FLAG_NONE; const int FLAG_FORCE_ARRAY; const int DEBUG_AUTO_BREAK_NEVER; const int DEBUG_AUTO_BREAK_ONCE; const int DEBUG_AUTO_BREAK_ALWAYS; /* Methods */ // Initializes and starts V8 engine and Returns new V8Js object with it's own V8 context. public __construct ( [ string $object_name = "PHP" [, array $variables = NULL [, array $extensions = NULL [, bool $report_uncaught_exceptions = TRUE ] ] ] ) // Provide a function or method to be used to load required modules. This can be any valid PHP callable. // The loader function will receive the normalised module path and should return Javascript code to be executed. public setModuleLoader ( callable $loader ) // Compiles and executes script in object's context with optional identifier string. // A time limit (milliseconds) and/or memory limit (bytes) can be provided to restrict execution. These options will throw a V8JsTimeLimitException or V8JsMemoryLimitException. public mixed V8Js::executeString( string $script [, string $identifier [, int $flags = V8Js::FLAG_NONE [, int $time_limit = 0 [, int $memory_limit = 0]]]]) // Compiles a script in object's context with optional identifier string. public mixed V8Js::compileString( string $script [, string $identifier ]) // Executes a precompiled script in object's context. // A time limit (milliseconds) and/or memory limit (bytes) can be provided to restrict execution. These options will throw a V8JsTimeLimitException or V8JsMemoryLimitException. public mixed V8Js::executeScript( resource $script [, int $flags = V8Js::FLAG_NONE [, int $time_limit = 0 [, int $memory_limit = 0]]]) // Returns uncaught pending exception or null if there is no pending exception. public V8JsScriptException V8Js::getPendingException( ) // Starts V8 debug agent for use with Google Chrome Developer Tools (Eclipse Plugin) public bool startDebugAgent( [ string $agent_name = "V8Js" [, $port = 9222 [, $auto_break = V8Js::DEBUG_AUTO_BREAK_NEVER ] ] ] ) /** Static methods **/ // Registers persistent context independent global Javascript extension. // NOTE! These extensions exist until PHP is shutdown and they need to be registered before V8 is initialized. // For best performance V8 is initialized only once per process thus this call has to be done before any V8Js objects are created! public static bool V8Js::registerExtension( string $extension_name, string $code [, array $dependenciess [, bool $auto_enable = FALSE ] ] ) // Returns extensions successfully registered with V8Js::registerExtension(). public static array V8Js::getExtensions( ) } final class V8JsScriptException extends Exception { /* Properties */ protected string JsFileName = NULL; protected int JsLineNumber = NULL; protected string JsSourceLine = NULL; protected string JsTrace = NULL; /* Methods */ final public string getJsFileName( ) final public int getJsLineNumber( ) final public string getJsSourceLine( ) final public string getJsTrace( ) } final class V8JsTimeLimitException extends Exception { } final class V8JsMemoryLimitException extends Exception { } ``` Javascript API ============== ```js // Print a string. print(string); // Dump the contents of a variable. var_dump(value); // Terminate Javascript execution immediately. exit(); // CommonJS Module support to require external code. // This makes use of the PHP module loader provided via V8Js::setModuleLoader (see PHP API above). require("path/to/module"); ``` The JavaScript `in` operator, when applied to a wrapped PHP object, works the same as the PHP `isset()` function. Similarly, when applied to a wrapped PHP object, JavaScript `delete` works like PHP `unset`. ```php foo = new Foo; // This prints "no" $v8->executeString('print( "bar" in PHP.foo ? "yes" : "no" );'); ?> ``` PHP has separate namespaces for properties and methods, while JavaScript has just one. Usually this isn't an issue, but if you need to you can use a leading `$` to specify a property, or `__call` to specifically invoke a method. ```php bar, "\n"; // This prints "I'm a function!" $foo->bar("function"); $v8 = new V8Js(); $v8->foo = new Foo; // This prints 'bar' $v8->executeString('print(PHP.foo.$bar, "\n");'); // This prints "I'm a function!" $v8->executeString('PHP.foo.__call("bar", ["function"]);'); ?> ```