summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-02-21 16:59:54 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-02-21 16:59:54 +0100
commite1f97c494ae31b9d67e85c6b17a18bb925bc2989 (patch)
treef7c240f082aad70f35de024bdc229504a0cc5f0a
parent9889100041bf14d8bc1b97f1cab4070de36e3e8f (diff)
initial implementation of the Php::dl() function
-rw-r--r--include/call.h18
-rw-r--r--include/classbase.h2
-rw-r--r--zend/eval.cpp22
-rw-r--r--zend/extensionpath.h73
-rw-r--r--zend/includes.h3
-rw-r--r--zend/module.h153
-rw-r--r--zend/symbol.h160
7 files changed, 420 insertions, 11 deletions
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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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<zend_module_entry*()> 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 <emiel.bruijntjes@copernica.com>
+ * @copyright 2015 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Make the Symbol class a templated class
+ */
+template <class T> class Symbol {};
+
+/**
+ * So that we can write a partial specialisation
+ * using a function prototype, indicating the
+ * prototype usable for the given callback
+ */
+template <class T, class ...Arguments>
+class Symbol<T(Arguments...)>
+{
+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<Arguments>(parameters)...);
+ }
+};
+
+/**
+ * End of namespace
+ */
+}
+