From e1f97c494ae31b9d67e85c6b17a18bb925bc2989 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 21 Feb 2015 16:59:54 +0100 Subject: initial implementation of the Php::dl() function --- include/call.h | 18 ++++-- include/classbase.h | 2 +- zend/eval.cpp | 22 +++++-- zend/extensionpath.h | 73 +++++++++++++++++++++++ zend/includes.h | 3 + zend/module.h | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ zend/symbol.h | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 420 insertions(+), 11 deletions(-) create mode 100644 zend/extensionpath.h create mode 100644 zend/module.h create mode 100644 zend/symbol.h diff --git a/include/call.h b/include/call.h index d807190..53030d2 100644 --- a/include/call.h +++ b/include/call.h @@ -27,17 +27,25 @@ extern bool define(const std::string &name, const Value &value); extern bool defined(const char *constant); extern bool defined(const char *constant, size_t size); extern bool defined(const std::string &constant); -extern Value eval(const std::string &phpCode); -extern Value include(const std::string &filename); -extern Value include_once(const std::string &filename); +extern bool dl(const char *filename); +inline bool dl(const std::string &filename) { return dl(filename.c_str()); } +inline bool dl(const Value &filename) { return dl(filename.rawValue()); } +extern Value eval(const char *phpCode); +inline Value eval(const std::string &phpCode) { return eval(phpCode.c_str()); } +extern Value include(const char *filename); +inline Value include(const std::string &filename) { return include(filename.c_str()); } +extern Value include_once(const char *filename); +inline Value include_once(const std::string &filename) { return include_once(filename.c_str()); } inline bool is_a(const Value &obj, const char *classname, size_t size, bool allow_string = false) { return obj.instanceOf(classname, size, allow_string); } inline bool is_a(const Value &obj, const char *classname, bool allow_string = false) { return is_a(obj, classname, strlen(classname), allow_string); } inline bool is_a(const Value &obj, const std::string &classname, bool allow_string = false) { return is_a(obj, classname.c_str(), classname.size(), allow_string); } inline bool is_subclass_of(const Value &obj, const char *classname, size_t size, bool allow_string = true) { return obj.derivedFrom(classname, size, allow_string); } inline bool is_subclass_of(const Value &obj, const char *classname, bool allow_string = true) { return is_subclass_of(obj, classname, strlen(classname), allow_string); } inline bool is_subclass_of(const Value &obj, const std::string &classname, bool allow_string = true) { return is_subclass_of(obj, classname.c_str(), classname.size(), allow_string); } -extern Value require(const std::string &filename); -extern Value require_once(const std::string &filename); +extern Value require(const char *filename); +inline Value require(const std::string &filename) { return require(filename.c_str()); } +extern Value require_once(const char *filename); +inline Value require_once(const std::string &filename) { return require_once(filename.c_str()); } /** * Call a function in PHP diff --git a/include/classbase.h b/include/classbase.h index f594f75..9e53a62 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -96,7 +96,7 @@ public: * Construct a new instance of the object, or to clone the object * @return Base */ - virtual Base* construct() const { return nullptr; } + virtual Base *construct() const { return nullptr; } virtual Base *clone(Base *orig) const { return nullptr; } /** diff --git a/zend/eval.cpp b/zend/eval.cpp index c332898..7ab4957 100644 --- a/zend/eval.cpp +++ b/zend/eval.cpp @@ -21,7 +21,7 @@ namespace Php { * @param phpCode The PHP code to evaluate * @return Value The result of the evaluation */ -Value eval(const std::string &phpCode) +Value eval(const char *phpCode) { // we have a script for this return Script(phpCode).execute(); @@ -32,7 +32,7 @@ Value eval(const std::string &phpCode) * @param filename * @return Value */ -Value include(const std::string &filename) +Value include(const char *filename) { // we can simply execute a file return File(filename).execute(); @@ -43,7 +43,7 @@ Value include(const std::string &filename) * @param filename * @return Value */ -Value include_once(const std::string &filename) +Value include_once(const char *filename) { // we can simply execute a file return File(filename).once(); @@ -55,7 +55,7 @@ Value include_once(const std::string &filename) * @param filename * @return Value */ -Value require(const std::string &filename) +Value require(const char *filename) { // create the file File file(filename); @@ -76,7 +76,7 @@ Value require(const std::string &filename) * @param filename * @return Value */ -Value require_once(const std::string &filename) +Value require_once(const char *filename) { // create the file File file(filename); @@ -91,6 +91,18 @@ Value require_once(const std::string &filename) return nullptr; } +/** + * Implementation of the dl() function - activate a different PHP extension + * @param filename + */ +bool dl(const char *filename) +{ + // create the module + Module module(filename); + + // start the module + return module.start(); +} /** * End of namespace diff --git a/zend/extensionpath.h b/zend/extensionpath.h new file mode 100644 index 0000000..771ef24 --- /dev/null +++ b/zend/extensionpath.h @@ -0,0 +1,73 @@ +/** + * ExtensionPath.h + * + * Simply utility class that turns a pathname for an extension into a full + * pathname that also takes the global extension_dir setting into account. + * + * @author Emiel Bruijntjes + * @copyright 2015 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ExtensionPath +{ +private: + /** + * The actual value + * @var std::string + */ + std::string _path; + +public: + /** + * Constructor + * @param path + */ + ExtensionPath(const char *path) + { + // was an absole path given? + if (path[0] && path[0] == '/') + { + // for absolute pathnames no extension_dir has to be checked + _path = path; + } + else + { + // start with the extension dir + _path = PG(extension_dir); + + // append slash + if (_path[_path.size()-1] != '/') _path.push_back('/'); + + // and append path passed to the constructor + _path.append(path); + } + } + + /** + * Destructor + */ + virtual ~ExtensionPath() {} + + /** + * Cast to string + * @return std::string + */ + operator const std::string & () const + { + return _path; + } +}; + +/** + * End of nmaespace + */ +} + diff --git a/zend/includes.h b/zend/includes.h index 529efa5..aa33c71 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -126,6 +126,9 @@ #include "functor.h" #include "constantimpl.h" #include "delayedfree.h" +#include "extensionpath.h" +#include "symbol.h" +#include "module.h" #ifndef ZVAL_COPY_VALUE #define ZVAL_COPY_VALUE(z, v) \ diff --git a/zend/module.h b/zend/module.h new file mode 100644 index 0000000..0373e11 --- /dev/null +++ b/zend/module.h @@ -0,0 +1,153 @@ +/** + * Module.h + * + * In PHP scripts you can use the dl() function to open additional extensions. + * This builtin dl() function call is protected with all sorts of security measures, + * like checking whether a filename contains slashes or otherwise potentially + * dangerous things. This is all understandable, because you do not want a kid + * from an ~user directory to be able to make changes to the globalle running + * apache process, and for example make changes to catch data from other websites + * running on the same host. + * + * However, people who are in a position to write C++ and deploy code however, + * already _ARE_ in a position to do dangerous things. For them we do not want + * these safety checks. In fact, we want to offer raw access, so that they can + * open modules - no matter what. So we had to make our own copy of the dl() + * function, and because we're here in C++ context, we do it a little nicer + * than the original C++ code. This module class is a utility class used by + * our own dl() implementation. + * + * @author Emiel Bruijntjes + * @copyright 2015 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Module +{ +private: + /** + * The handle of the module + * @var void* + */ + void *_handle; + + /** + * The module entry + * @var zend_module_entry + */ + zend_module_entry *_entry = nullptr; + +public: + /** + * Constructor + * @param module Name of the module + */ + Module(const char *module) + { + // the path we're going to load + ExtensionPath path(module); + + // load the module + _handle = DL_LOAD(module); + + // handle should be valid + if (!_handle) return; + + // we have to call the get_module() function + Symbol get_module(_handle, "get_module"); + + // was the get_module() function found + if (!get_module) return; + + // retrieve the module entry + _entry = get_module(); + } + + /** + * Destructor + */ + virtual ~Module() + { + // if the handle is still valid, we have to unload it + if (_handle) DL_UNLOAD(_handle); + } + + /** + * Check if the module is valid + * @return bool + */ + bool valid() const + { + // module-entry must exist + if (!_handle || !_entry) return false; + + // check api compatibility + if (_entry->zend_api != ZEND_MODULE_API_NO) return false; + + // and other stuff + return strcmp(_entry->build_id, ZEND_MODULE_BUILD_ID) == 0; + + // all is ok + return true; + } + + /** + * Start the module + * @return bool + */ + bool start() + { + // this is not possible if the module is invalid in the first place + if (!valid()) return false; + + // the Zend engine sets a number of properties in the entry class, we do that here too + _entry->type = MODULE_TEMPORARY; + _entry->module_number = zend_next_free_module(); + _entry->handle = _handle; + + // we need the tsrm_ls variable + // @todo does loading an extension even work in a multi-threading setup? + TSRMLS_FETCH(); + + // register the module, this apparently returns a copied entry pointer + auto *entry = zend_register_module_ex(_entry TSRMLS_CC); + + // forget the entry, so that a new call to start() will fail too + _entry = nullptr; + + // leap out on failure + if (entry == NULL) return false; + + // startup the module + if (zend_startup_module_ex(entry TSRMLS_CC) == FAILURE) return false; + + // was a startup-function defined? if not + if (!entry->request_startup_func) + { + // call the startup function + if (entry->request_startup_func(MODULE_TEMPORARY, entry->module_number TSRMLS_CC) == FAILURE) return false; + } + + // all is ok, we can forget about the handle now, so that is won't get destructed + _handle = nullptr; + + // enable full-table-cleanup, because inside the Zend-engine this is done too + EG(full_tables_cleanup) = 1; + + // we're ready + return true; + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/symbol.h b/zend/symbol.h new file mode 100644 index 0000000..66488dc --- /dev/null +++ b/zend/symbol.h @@ -0,0 +1,160 @@ +/** + * Symbol.h + * + * C++ utility class that wraps around a dlsym() call, and that allows us + * to do dlsym() calls with template parameters instead of ugly casts that + * are necessary in C to turn a void* into a function pointer + * + * @author Emiel Bruijntjes + * @copyright 2015 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Make the Symbol class a templated class + */ +template class Symbol {}; + +/** + * So that we can write a partial specialisation + * using a function prototype, indicating the + * prototype usable for the given callback + */ +template +class Symbol +{ +private: + /** + * Store pointer to the actual dynamically loaded + * function. This is done in a union because the + * result from a dlsym call is a void pointer which + * cannot be legally cast into a function pointer. + * + * We therefore always set the first type (which is + * void* making it legal) and, because all members + * in a union share the same address, we can then + * read the second type and actually call it. + * + * @var Callable + */ + union Callable { + + /** + * Property for getting and setting the return + * value from dlsym. This is always a void* + * @var void + */ + void *ptr; + + /** + * Property for executing the mapped function + * + * @param mixed,... function parameters + * @return mixed + * + * @var function + */ + T (*func)(Arguments...); + + + /** + * Constructor + */ + Callable() : ptr(nullptr) {} + + /** + * We may be moved + * + * @param callable the callable we are moving + */ + Callable(Callable&& callable) : + ptr(callable.ptr) + { + // the other callable no longer has a handle + callable.ptr = nullptr; + } + + /** + * Constructor + * + * @param function the mapped function + */ + Callable(void *function) : ptr(function) {} + + } _method; + +public: + /** + * Constructor + */ + Symbol() = default; + + /** + * Constructor + * @param handle The library handle to load the function from + * @param name Name of the function + */ + Symbol(void *handle, const char *name) : + _method(dlsym(handle, name)) {} + + /** + * Move constructor + * @param other + */ + Symbol(Symbol &&other) = default; + + /** + * Destructor + */ + virtual ~Symbol() {} + + /** + * Is this a valid function or not? + * @return bool + */ + bool valid() const + { + return _method.ptr != nullptr; + } + + /** + * The library object can also be used as in a boolean context, + * for that there is an implementation of a casting operator, and + * the negate operator + * @return bool + */ + operator bool () const { return valid(); } + bool operator ! () const { return !valid(); } + + /** + * Test whether we are a valid object + * @param nullptr test if we are null + */ + bool operator==(std::nullptr_t /* nullptr */) const { return !valid(); } + bool operator!=(std::nullptr_t /* nullptr */) const { return valid(); } + + /** + * Invoke the function + * + * @param mixed,... + * @throws std::bad_function_call + */ + T operator()(Arguments... parameters) const + { + // check whether we have a valid function + if (_method.ptr == nullptr) throw std::bad_function_call(); + + // execute the method given all the parameters + return _method.func(std::forward(parameters)...); + } +}; + +/** + * End of namespace + */ +} + -- cgit v1.2.3