Astra Security
This commit is contained in:
BIN
libraries/plugins/MaxMind-DB-Reader/GeoLite2-Country.mmdb
Normal file
BIN
libraries/plugins/MaxMind-DB-Reader/GeoLite2-Country.mmdb
Normal file
Binary file not shown.
45
libraries/plugins/MaxMind-DB-Reader/autoload.php
Normal file
45
libraries/plugins/MaxMind-DB-Reader/autoload.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PSR-4 autoloader implementation for the MaxMind\DB namespace.
|
||||
* First we define the 'mmdb_autoload' function, and then we register
|
||||
* it with 'spl_autoload_register' so that PHP knows to use it.
|
||||
*
|
||||
* @param mixed $class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Automatically include the file that defines <code>class</code>.
|
||||
*
|
||||
* @param string $class
|
||||
* the name of the class to load
|
||||
*/
|
||||
function mmdb_autoload($class)
|
||||
{
|
||||
/*
|
||||
* A project-specific mapping between the namespaces and where
|
||||
* they're located. By convention, we include the trailing
|
||||
* slashes. The one-element array here simply makes things easy
|
||||
* to extend in the future if (for example) the test classes
|
||||
* begin to use one another.
|
||||
*/
|
||||
$namespace_map = ['MaxMind\\Db\\' => __DIR__ . '/src/MaxMind/Db/'];
|
||||
|
||||
foreach ($namespace_map as $prefix => $dir) {
|
||||
/* First swap out the namespace prefix with a directory... */
|
||||
$path = str_replace($prefix, $dir, $class);
|
||||
|
||||
/* replace the namespace separator with a directory separator... */
|
||||
$path = str_replace('\\', '/', $path);
|
||||
|
||||
/* and finally, add the PHP file extension to the result. */
|
||||
$path = $path . '.php';
|
||||
|
||||
/* $path should now contain the path to a PHP file defining $class */
|
||||
if (file_exists($path)) {
|
||||
include $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register('mmdb_autoload');
|
||||
34
libraries/plugins/MaxMind-DB-Reader/composer.json
Normal file
34
libraries/plugins/MaxMind-DB-Reader/composer.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
"description": "MaxMind DB Reader API",
|
||||
"keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"],
|
||||
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
|
||||
"type": "library",
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gregory J. Oschwald",
|
||||
"email": "goschwald@maxmind.com",
|
||||
"homepage": "http://www.maxmind.com/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||
"ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "2.*",
|
||||
"phpunit/phpunit": "4.* || 5.*",
|
||||
"satooshi/php-coveralls": "1.0.*",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MaxMind\\Db\\": "src/MaxMind/Db"
|
||||
}
|
||||
}
|
||||
}
|
||||
40
libraries/plugins/MaxMind-DB-Reader/ext/config.m4
Normal file
40
libraries/plugins/MaxMind-DB-Reader/ext/config.m4
Normal file
@@ -0,0 +1,40 @@
|
||||
PHP_ARG_WITH(maxminddb,
|
||||
[Whether to enable the MaxMind DB Reader extension],
|
||||
[ --with-maxminddb Enable MaxMind DB Reader extension support])
|
||||
|
||||
PHP_ARG_ENABLE(maxminddb-debug, for MaxMind DB debug support,
|
||||
[ --enable-maxminddb-debug Enable enable MaxMind DB deubg support], no, no)
|
||||
|
||||
if test $PHP_MAXMINDDB != "no"; then
|
||||
|
||||
AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
|
||||
|
||||
AC_MSG_CHECKING(for libmaxminddb)
|
||||
if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists libmaxminddb; then
|
||||
dnl retrieve build options from pkg-config
|
||||
if $PKG_CONFIG libmaxminddb --atleast-version 1.0.0; then
|
||||
LIBMAXMINDDB_INC=`$PKG_CONFIG libmaxminddb --cflags`
|
||||
LIBMAXMINDDB_LIB=`$PKG_CONFIG libmaxminddb --libs`
|
||||
LIBMAXMINDDB_VER=`$PKG_CONFIG libmaxminddb --modversion`
|
||||
AC_MSG_RESULT(found version $LIBMAXMINDDB_VER)
|
||||
else
|
||||
AC_MSG_ERROR(system libmaxminddb must be upgraded to version >= 1.0.0)
|
||||
fi
|
||||
PHP_EVAL_LIBLINE($LIBMAXMINDDB_LIB, MAXMINDDB_SHARED_LIBADD)
|
||||
PHP_EVAL_INCLINE($LIBMAXMINDDB_INC)
|
||||
else
|
||||
AC_MSG_RESULT(pkg-config information missing)
|
||||
AC_MSG_WARN(will use libmaxmxinddb from compiler default path)
|
||||
|
||||
PHP_CHECK_LIBRARY(maxminddb, MMDB_open)
|
||||
PHP_ADD_LIBRARY(maxminddb, 1, MAXMINDDB_SHARED_LIBADD)
|
||||
fi
|
||||
|
||||
if test $PHP_MAXMINDDB_DEBUG != "no"; then
|
||||
CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Werror"
|
||||
fi
|
||||
|
||||
PHP_SUBST(MAXMINDDB_SHARED_LIBADD)
|
||||
|
||||
PHP_NEW_EXTENSION(maxminddb, maxminddb.c, $ext_shared)
|
||||
fi
|
||||
623
libraries/plugins/MaxMind-DB-Reader/ext/maxminddb.c
Normal file
623
libraries/plugins/MaxMind-DB-Reader/ext/maxminddb.c
Normal file
@@ -0,0 +1,623 @@
|
||||
/* MaxMind, Inc., licenses this file to you under the Apache License, Version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "php_maxminddb.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <php.h>
|
||||
#include <zend.h>
|
||||
#include "Zend/zend_exceptions.h"
|
||||
#include "ext/standard/info.h"
|
||||
#include <maxminddb.h>
|
||||
|
||||
#ifdef ZTS
|
||||
#include <TSRM.h>
|
||||
#endif
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
|
||||
#define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db")
|
||||
#define PHP_MAXMINDDB_READER_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader")
|
||||
#define PHP_MAXMINDDB_READER_EX_NS \
|
||||
ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, \
|
||||
"InvalidDatabaseException")
|
||||
|
||||
#ifdef ZEND_ENGINE_3
|
||||
#define Z_MAXMINDDB_P(zv) php_maxminddb_fetch_object(Z_OBJ_P(zv))
|
||||
#define _ZVAL_STRING ZVAL_STRING
|
||||
#define _ZVAL_STRINGL ZVAL_STRINGL
|
||||
typedef size_t strsize_t;
|
||||
typedef zend_object free_obj_t;
|
||||
#else
|
||||
#define Z_MAXMINDDB_P(zv) (maxminddb_obj *) zend_object_store_get_object(zv TSRMLS_CC)
|
||||
#define _ZVAL_STRING(a, b) ZVAL_STRING(a, b, 1)
|
||||
#define _ZVAL_STRINGL(a, b, c) ZVAL_STRINGL(a, b, c, 1)
|
||||
typedef int strsize_t;
|
||||
typedef void free_obj_t;
|
||||
#endif
|
||||
|
||||
#ifdef ZEND_ENGINE_3
|
||||
typedef struct _maxminddb_obj {
|
||||
MMDB_s *mmdb;
|
||||
zend_object std;
|
||||
} maxminddb_obj;
|
||||
#else
|
||||
typedef struct _maxminddb_obj {
|
||||
zend_object std;
|
||||
MMDB_s *mmdb;
|
||||
} maxminddb_obj;
|
||||
#endif
|
||||
|
||||
PHP_FUNCTION(maxminddb);
|
||||
|
||||
static const MMDB_entry_data_list_s *handle_entry_data_list(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value
|
||||
TSRMLS_DC);
|
||||
static const MMDB_entry_data_list_s *handle_array(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static const MMDB_entry_data_list_s *handle_map(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static zend_class_entry * lookup_class(const char *name TSRMLS_DC);
|
||||
|
||||
#define CHECK_ALLOCATED(val) \
|
||||
if (!val ) { \
|
||||
zend_error(E_ERROR, "Out of memory"); \
|
||||
return; \
|
||||
} \
|
||||
|
||||
#define THROW_EXCEPTION(name, ... ) \
|
||||
{ \
|
||||
zend_class_entry *exception_ce = lookup_class(name TSRMLS_CC); \
|
||||
zend_throw_exception_ex(exception_ce, 0 TSRMLS_CC, __VA_ARGS__); \
|
||||
} \
|
||||
|
||||
|
||||
#if PHP_VERSION_ID < 50399
|
||||
#define object_properties_init(zo, class_type) \
|
||||
{ \
|
||||
zval *tmp; \
|
||||
zend_hash_copy((*zo).properties, \
|
||||
&class_type->default_properties, \
|
||||
(copy_ctor_func_t)zval_add_ref, \
|
||||
(void *)&tmp, \
|
||||
sizeof(zval *)); \
|
||||
}
|
||||
#endif
|
||||
|
||||
static zend_object_handlers maxminddb_obj_handlers;
|
||||
static zend_class_entry *maxminddb_ce;
|
||||
|
||||
static inline maxminddb_obj *php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC){
|
||||
#ifdef ZEND_ENGINE_3
|
||||
return (maxminddb_obj *)((char*)(obj) - XtOffsetOf(maxminddb_obj, std));
|
||||
#else
|
||||
return (maxminddb_obj *)obj;
|
||||
#endif
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_construct, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, db_file)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, __construct){
|
||||
char *db_file = NULL;
|
||||
strsize_t name_len;
|
||||
zval * _this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
|
||||
&_this_zval, maxminddb_ce, &db_file, &name_len) == FAILURE) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"The constructor takes exactly one argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 != php_check_open_basedir(db_file TSRMLS_CC) || 0 != access(db_file, R_OK)) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"The file \"%s\" does not exist or is not readable.",
|
||||
db_file);
|
||||
return;
|
||||
}
|
||||
|
||||
MMDB_s *mmdb = (MMDB_s *)emalloc(sizeof(MMDB_s));
|
||||
uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
|
||||
|
||||
if (MMDB_SUCCESS != status) {
|
||||
THROW_EXCEPTION(
|
||||
PHP_MAXMINDDB_READER_EX_NS,
|
||||
"Error opening database file (%s). Is this a valid MaxMind DB file?",
|
||||
db_file);
|
||||
efree(mmdb);
|
||||
return;
|
||||
}
|
||||
|
||||
maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(getThis());
|
||||
mmdb_obj->mmdb = mmdb;
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_get, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, ip_address)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, get){
|
||||
char *ip_address = NULL;
|
||||
strsize_t name_len;
|
||||
zval * _this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
|
||||
&_this_zval, maxminddb_ce, &ip_address, &name_len) == FAILURE) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"Method takes exactly one argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
const maxminddb_obj *mmdb_obj =
|
||||
(maxminddb_obj *)Z_MAXMINDDB_P(getThis());
|
||||
|
||||
MMDB_s *mmdb = mmdb_obj->mmdb;
|
||||
|
||||
if (NULL == mmdb) {
|
||||
THROW_EXCEPTION("BadMethodCallException",
|
||||
"Attempt to read from a closed MaxMind DB.");
|
||||
return;
|
||||
}
|
||||
|
||||
int gai_error = 0;
|
||||
int mmdb_error = MMDB_SUCCESS;
|
||||
MMDB_lookup_result_s result =
|
||||
MMDB_lookup_string(mmdb, ip_address, &gai_error,
|
||||
&mmdb_error);
|
||||
|
||||
if (MMDB_SUCCESS != gai_error) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"The value \"%s\" is not a valid IP address.",
|
||||
ip_address);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MMDB_SUCCESS != mmdb_error) {
|
||||
char *exception_name;
|
||||
if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
|
||||
exception_name = "InvalidArgumentException";
|
||||
} else {
|
||||
exception_name = PHP_MAXMINDDB_READER_EX_NS;
|
||||
}
|
||||
THROW_EXCEPTION(exception_name,
|
||||
"Error looking up %s. %s",
|
||||
ip_address, MMDB_strerror(mmdb_error));
|
||||
return;
|
||||
}
|
||||
|
||||
MMDB_entry_data_list_s *entry_data_list = NULL;
|
||||
|
||||
if (!result.found_entry) {
|
||||
RETURN_NULL();
|
||||
}
|
||||
|
||||
int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
|
||||
|
||||
if (MMDB_SUCCESS != status) {
|
||||
THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
|
||||
"Error while looking up data for %s. %s",
|
||||
ip_address, MMDB_strerror(status));
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
return;
|
||||
} else if (NULL == entry_data_list) {
|
||||
THROW_EXCEPTION(
|
||||
PHP_MAXMINDDB_READER_EX_NS,
|
||||
"Error while looking up data for %s. Your database may be corrupt or you have found a bug in libmaxminddb.",
|
||||
ip_address);
|
||||
return;
|
||||
}
|
||||
|
||||
handle_entry_data_list(entry_data_list, return_value TSRMLS_CC);
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxmindbreader_void, 0, 0, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, metadata){
|
||||
if (ZEND_NUM_ARGS() != 0) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"Method takes no arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
const maxminddb_obj *const mmdb_obj =
|
||||
(maxminddb_obj *)Z_MAXMINDDB_P(getThis());
|
||||
|
||||
if (NULL == mmdb_obj->mmdb) {
|
||||
THROW_EXCEPTION("BadMethodCallException",
|
||||
"Attempt to read from a closed MaxMind DB.");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *const name = ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata");
|
||||
zend_class_entry *metadata_ce = lookup_class(name TSRMLS_CC);
|
||||
|
||||
object_init_ex(return_value, metadata_ce);
|
||||
|
||||
#ifdef ZEND_ENGINE_3
|
||||
zval _metadata_array;
|
||||
zval *metadata_array = &_metadata_array;
|
||||
ZVAL_NULL(metadata_array);
|
||||
#else
|
||||
zval *metadata_array;
|
||||
ALLOC_INIT_ZVAL(metadata_array);
|
||||
#endif
|
||||
|
||||
MMDB_entry_data_list_s *entry_data_list;
|
||||
MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
|
||||
|
||||
handle_entry_data_list(entry_data_list, metadata_array TSRMLS_CC);
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
#ifdef ZEND_ENGINE_3
|
||||
zend_call_method_with_1_params(return_value, metadata_ce,
|
||||
&metadata_ce->constructor,
|
||||
ZEND_CONSTRUCTOR_FUNC_NAME,
|
||||
NULL,
|
||||
metadata_array);
|
||||
zval_ptr_dtor(metadata_array);
|
||||
#else
|
||||
zend_call_method_with_1_params(&return_value, metadata_ce,
|
||||
&metadata_ce->constructor,
|
||||
ZEND_CONSTRUCTOR_FUNC_NAME,
|
||||
NULL,
|
||||
metadata_array);
|
||||
zval_ptr_dtor(&metadata_array);
|
||||
#endif
|
||||
}
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, close){
|
||||
if (ZEND_NUM_ARGS() != 0) {
|
||||
THROW_EXCEPTION("InvalidArgumentException",
|
||||
"Method takes no arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
maxminddb_obj *mmdb_obj =
|
||||
(maxminddb_obj *)Z_MAXMINDDB_P(getThis());
|
||||
|
||||
if (NULL == mmdb_obj->mmdb) {
|
||||
THROW_EXCEPTION("BadMethodCallException",
|
||||
"Attempt to close a closed MaxMind DB.");
|
||||
return;
|
||||
}
|
||||
MMDB_close(mmdb_obj->mmdb);
|
||||
efree(mmdb_obj->mmdb);
|
||||
mmdb_obj->mmdb = NULL;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *handle_entry_data_list(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value
|
||||
TSRMLS_DC)
|
||||
{
|
||||
switch (entry_data_list->entry_data.type) {
|
||||
case MMDB_DATA_TYPE_MAP:
|
||||
return handle_map(entry_data_list, z_value TSRMLS_CC);
|
||||
case MMDB_DATA_TYPE_ARRAY:
|
||||
return handle_array(entry_data_list, z_value TSRMLS_CC);
|
||||
case MMDB_DATA_TYPE_UTF8_STRING:
|
||||
_ZVAL_STRINGL(z_value,
|
||||
(char *)entry_data_list->entry_data.utf8_string,
|
||||
entry_data_list->entry_data.data_size);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BYTES:
|
||||
_ZVAL_STRINGL(z_value, (char *)entry_data_list->entry_data.bytes,
|
||||
entry_data_list->entry_data.data_size);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_DOUBLE:
|
||||
ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_FLOAT:
|
||||
ZVAL_DOUBLE(z_value, entry_data_list->entry_data.float_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT16:
|
||||
ZVAL_LONG(z_value, entry_data_list->entry_data.uint16);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT32:
|
||||
handle_uint32(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BOOLEAN:
|
||||
ZVAL_BOOL(z_value, entry_data_list->entry_data.boolean);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT64:
|
||||
handle_uint64(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT128:
|
||||
handle_uint128(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_INT32:
|
||||
ZVAL_LONG(z_value, entry_data_list->entry_data.int32);
|
||||
break;
|
||||
default:
|
||||
THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
|
||||
"Invalid data type arguments: %d",
|
||||
entry_data_list->entry_data.type);
|
||||
return NULL;
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *handle_map(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC)
|
||||
{
|
||||
array_init(z_value);
|
||||
const uint32_t map_size = entry_data_list->entry_data.data_size;
|
||||
|
||||
uint i;
|
||||
for (i = 0; i < map_size && entry_data_list; i++ ) {
|
||||
entry_data_list = entry_data_list->next;
|
||||
|
||||
char *key =
|
||||
estrndup((char *)entry_data_list->entry_data.utf8_string,
|
||||
entry_data_list->entry_data.data_size);
|
||||
if (NULL == key) {
|
||||
THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
|
||||
"Invalid data type arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry_data_list = entry_data_list->next;
|
||||
#ifdef ZEND_ENGINE_3
|
||||
zval _new_value;
|
||||
zval * new_value = &_new_value;
|
||||
ZVAL_NULL(new_value);
|
||||
#else
|
||||
zval *new_value;
|
||||
ALLOC_INIT_ZVAL(new_value);
|
||||
#endif
|
||||
entry_data_list = handle_entry_data_list(entry_data_list,
|
||||
new_value TSRMLS_CC);
|
||||
add_assoc_zval(z_value, key, new_value);
|
||||
efree(key);
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *handle_array(
|
||||
const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC)
|
||||
{
|
||||
const uint32_t size = entry_data_list->entry_data.data_size;
|
||||
|
||||
array_init(z_value);
|
||||
|
||||
uint i;
|
||||
for (i = 0; i < size && entry_data_list; i++) {
|
||||
entry_data_list = entry_data_list->next;
|
||||
#ifdef ZEND_ENGINE_3
|
||||
zval _new_value;
|
||||
zval * new_value = &_new_value;
|
||||
ZVAL_NULL(new_value);
|
||||
#else
|
||||
zval *new_value;
|
||||
ALLOC_INIT_ZVAL(new_value);
|
||||
#endif
|
||||
entry_data_list = handle_entry_data_list(entry_data_list,
|
||||
new_value TSRMLS_CC);
|
||||
add_next_index_zval(z_value, new_value);
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC)
|
||||
{
|
||||
uint64_t high = 0;
|
||||
uint64_t low = 0;
|
||||
#if MMDB_UINT128_IS_BYTE_ARRAY
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
high = (high << 8) | entry_data_list->entry_data.uint128[i];
|
||||
}
|
||||
|
||||
for (i = 8; i < 16; i++) {
|
||||
low = (low << 8) | entry_data_list->entry_data.uint128[i];
|
||||
}
|
||||
#else
|
||||
high = entry_data_list->entry_data.uint128 >> 64;
|
||||
low = (uint64_t)entry_data_list->entry_data.uint128;
|
||||
#endif
|
||||
|
||||
char *num_str;
|
||||
spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low);
|
||||
CHECK_ALLOCATED(num_str);
|
||||
|
||||
_ZVAL_STRING(z_value, num_str);
|
||||
efree(num_str);
|
||||
}
|
||||
|
||||
static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC)
|
||||
{
|
||||
uint32_t val = entry_data_list->entry_data.uint32;
|
||||
|
||||
#if LONG_MAX >= UINT32_MAX
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
#else
|
||||
if (val <= LONG_MAX) {
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
}
|
||||
|
||||
char *int_str;
|
||||
spprintf(&int_str, 0, "%" PRIu32, val);
|
||||
CHECK_ALLOCATED(int_str);
|
||||
|
||||
_ZVAL_STRING(z_value, int_str);
|
||||
efree(int_str);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC)
|
||||
{
|
||||
uint64_t val = entry_data_list->entry_data.uint64;
|
||||
|
||||
#if LONG_MAX >= UINT64_MAX
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
#else
|
||||
if (val <= LONG_MAX) {
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
}
|
||||
|
||||
char *int_str;
|
||||
spprintf(&int_str, 0, "%" PRIu64, val);
|
||||
CHECK_ALLOCATED(int_str);
|
||||
|
||||
_ZVAL_STRING(z_value, int_str);
|
||||
efree(int_str);
|
||||
#endif
|
||||
}
|
||||
|
||||
static zend_class_entry *lookup_class(const char *name TSRMLS_DC)
|
||||
{
|
||||
#ifdef ZEND_ENGINE_3
|
||||
zend_string *n = zend_string_init(name, strlen(name), 0);
|
||||
zend_class_entry *ce = zend_lookup_class(n);
|
||||
zend_string_release(n);
|
||||
if( NULL == ce ) {
|
||||
zend_error(E_ERROR, "Class %s not found", name);
|
||||
}
|
||||
return ce;
|
||||
#else
|
||||
zend_class_entry **ce;
|
||||
if (FAILURE ==
|
||||
zend_lookup_class(name, strlen(name),
|
||||
&ce TSRMLS_CC)) {
|
||||
zend_error(E_ERROR, "Class %s not found", name);
|
||||
}
|
||||
return *ce;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC)
|
||||
{
|
||||
maxminddb_obj *obj = php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC);
|
||||
if (obj->mmdb != NULL) {
|
||||
MMDB_close(obj->mmdb);
|
||||
efree(obj->mmdb);
|
||||
}
|
||||
|
||||
zend_object_std_dtor(&obj->std TSRMLS_CC);
|
||||
#ifndef ZEND_ENGINE_3
|
||||
efree(object);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ZEND_ENGINE_3
|
||||
static zend_object *maxminddb_create_handler(
|
||||
zend_class_entry *type TSRMLS_DC)
|
||||
{
|
||||
maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
|
||||
zend_object_std_init(&obj->std, type TSRMLS_CC);
|
||||
object_properties_init(&(obj->std), type);
|
||||
|
||||
obj->std.handlers = &maxminddb_obj_handlers;
|
||||
|
||||
return &obj->std;
|
||||
}
|
||||
#else
|
||||
static zend_object_value maxminddb_create_handler(
|
||||
zend_class_entry *type TSRMLS_DC)
|
||||
{
|
||||
zend_object_value retval;
|
||||
|
||||
maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
|
||||
zend_object_std_init(&obj->std, type TSRMLS_CC);
|
||||
object_properties_init(&(obj->std), type);
|
||||
|
||||
retval.handle = zend_objects_store_put(obj, NULL,
|
||||
maxminddb_free_storage,
|
||||
NULL TSRMLS_CC);
|
||||
retval.handlers = &maxminddb_obj_handlers;
|
||||
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
static zend_function_entry maxminddb_methods[] = {
|
||||
PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxmindbreader_construct,
|
||||
ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
|
||||
PHP_ME(MaxMind_Db_Reader, close, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(MaxMind_Db_Reader, get, arginfo_maxmindbreader_get, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(MaxMind_Db_Reader, metadata, arginfo_maxmindbreader_void, ZEND_ACC_PUBLIC)
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
PHP_MINIT_FUNCTION(maxminddb){
|
||||
zend_class_entry ce;
|
||||
|
||||
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods);
|
||||
maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC);
|
||||
maxminddb_ce->create_object = maxminddb_create_handler;
|
||||
memcpy(&maxminddb_obj_handlers,
|
||||
zend_get_std_object_handlers(), sizeof(zend_object_handlers));
|
||||
maxminddb_obj_handlers.clone_obj = NULL;
|
||||
#ifdef ZEND_ENGINE_3
|
||||
maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std);
|
||||
maxminddb_obj_handlers.free_obj = maxminddb_free_storage;
|
||||
#endif
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static PHP_MINFO_FUNCTION(maxminddb)
|
||||
{
|
||||
php_info_print_table_start();
|
||||
|
||||
php_info_print_table_row(2, "MaxMind DB Reader", "enabled");
|
||||
php_info_print_table_row(2, "maxminddb extension version", PHP_MAXMINDDB_VERSION);
|
||||
php_info_print_table_row(2, "libmaxminddb library version", MMDB_lib_version());
|
||||
|
||||
php_info_print_table_end();
|
||||
}
|
||||
|
||||
zend_module_entry maxminddb_module_entry = {
|
||||
STANDARD_MODULE_HEADER,
|
||||
PHP_MAXMINDDB_EXTNAME,
|
||||
NULL,
|
||||
PHP_MINIT(maxminddb),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
PHP_MINFO(maxminddb),
|
||||
PHP_MAXMINDDB_VERSION,
|
||||
STANDARD_MODULE_PROPERTIES
|
||||
};
|
||||
|
||||
#ifdef COMPILE_DL_MAXMINDDB
|
||||
ZEND_GET_MODULE(maxminddb)
|
||||
#endif
|
||||
24
libraries/plugins/MaxMind-DB-Reader/ext/php_maxminddb.h
Normal file
24
libraries/plugins/MaxMind-DB-Reader/ext/php_maxminddb.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/* MaxMind, Inc., licenses this file to you under the Apache License, Version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <zend_interfaces.h>
|
||||
|
||||
#ifndef PHP_MAXMINDDB_H
|
||||
#define PHP_MAXMINDDB_H 1
|
||||
#define PHP_MAXMINDDB_VERSION "1.4.1"
|
||||
#define PHP_MAXMINDDB_EXTNAME "maxminddb"
|
||||
|
||||
extern zend_module_entry maxminddb_module_entry;
|
||||
#define phpext_maxminddb_ptr &maxminddb_module_entry
|
||||
|
||||
#endif
|
||||
309
libraries/plugins/MaxMind-DB-Reader/src/MaxMind/Db/Reader.php
Normal file
309
libraries/plugins/MaxMind-DB-Reader/src/MaxMind/Db/Reader.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace MaxMind\Db;
|
||||
|
||||
use MaxMind\Db\Reader\Decoder;
|
||||
use MaxMind\Db\Reader\InvalidDatabaseException;
|
||||
use MaxMind\Db\Reader\Metadata;
|
||||
use MaxMind\Db\Reader\Util;
|
||||
|
||||
/**
|
||||
* Instances of this class provide a reader for the MaxMind DB format. IP
|
||||
* addresses can be looked up using the <code>get</code> method.
|
||||
*/
|
||||
class Reader
|
||||
{
|
||||
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
|
||||
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
|
||||
private static $METADATA_START_MARKER_LENGTH = 14;
|
||||
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB
|
||||
|
||||
private $decoder;
|
||||
private $fileHandle;
|
||||
private $fileSize;
|
||||
private $ipV4Start;
|
||||
private $metadata;
|
||||
|
||||
/**
|
||||
* Constructs a Reader for the MaxMind DB format. The file passed to it must
|
||||
* be a valid MaxMind DB file such as a GeoIp2 database file.
|
||||
*
|
||||
* @param string $database
|
||||
* the MaxMind DB file to use
|
||||
*
|
||||
* @throws \InvalidArgumentException for invalid database path or unknown arguments
|
||||
* @throws \MaxMind\Db\Reader\InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*/
|
||||
public function __construct($database)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The constructor takes exactly one argument.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_readable($database)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"The file \"$database\" does not exist or is not readable."
|
||||
);
|
||||
}
|
||||
$this->fileHandle = @fopen($database, 'rb');
|
||||
if ($this->fileHandle === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Error opening \"$database\"."
|
||||
);
|
||||
}
|
||||
$this->fileSize = @filesize($database);
|
||||
if ($this->fileSize === false) {
|
||||
throw new \UnexpectedValueException(
|
||||
"Error determining the size of \"$database\"."
|
||||
);
|
||||
}
|
||||
|
||||
$start = $this->findMetadataStart($database);
|
||||
$metadataDecoder = new Decoder($this->fileHandle, $start);
|
||||
list($metadataArray) = $metadataDecoder->decode($start);
|
||||
$this->metadata = new Metadata($metadataArray);
|
||||
$this->decoder = new Decoder(
|
||||
$this->fileHandle,
|
||||
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the <code>address</code> in the MaxMind DB.
|
||||
*
|
||||
* @param string $ipAddress
|
||||
* the IP address to look up
|
||||
*
|
||||
* @throws \BadMethodCallException if this method is called on a closed database
|
||||
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*
|
||||
* @return array the record for the IP address
|
||||
*/
|
||||
public function get($ipAddress)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Method takes exactly one argument.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"The value \"$ipAddress\" is not a valid IP address."
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->metadata->ipVersion === 4 && strrpos($ipAddress, ':')) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Error looking up $ipAddress. You attempted to look up an"
|
||||
. ' IPv6 address in an IPv4-only database.'
|
||||
);
|
||||
}
|
||||
$pointer = $this->findAddressInTree($ipAddress);
|
||||
if ($pointer === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->resolveDataPointer($pointer);
|
||||
}
|
||||
|
||||
private function findAddressInTree($ipAddress)
|
||||
{
|
||||
// XXX - could simplify. Done as a byte array to ease porting
|
||||
$rawAddress = array_merge(unpack('C*', inet_pton($ipAddress)));
|
||||
|
||||
$bitCount = \count($rawAddress) * 8;
|
||||
|
||||
// The first node of the tree is always node 0, at the beginning of the
|
||||
// value
|
||||
$node = $this->startNode($bitCount);
|
||||
|
||||
for ($i = 0; $i < $bitCount; ++$i) {
|
||||
if ($node >= $this->metadata->nodeCount) {
|
||||
break;
|
||||
}
|
||||
$tempBit = 0xFF & $rawAddress[$i >> 3];
|
||||
$bit = 1 & ($tempBit >> 7 - ($i % 8));
|
||||
|
||||
$node = $this->readNode($node, $bit);
|
||||
}
|
||||
if ($node === $this->metadata->nodeCount) {
|
||||
// Record is empty
|
||||
return 0;
|
||||
} elseif ($node > $this->metadata->nodeCount) {
|
||||
// Record is a data pointer
|
||||
return $node;
|
||||
}
|
||||
throw new InvalidDatabaseException('Something bad happened');
|
||||
}
|
||||
|
||||
private function startNode($length)
|
||||
{
|
||||
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
|
||||
// is the case, we can skip over the first 96 nodes.
|
||||
if ($this->metadata->ipVersion === 6 && $length === 32) {
|
||||
return $this->ipV4StartNode();
|
||||
}
|
||||
// The first node of the tree is always node 0, at the beginning of the
|
||||
// value
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function ipV4StartNode()
|
||||
{
|
||||
// This is a defensive check. There is no reason to call this when you
|
||||
// have an IPv4 tree.
|
||||
if ($this->metadata->ipVersion === 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->ipV4Start) {
|
||||
return $this->ipV4Start;
|
||||
}
|
||||
$node = 0;
|
||||
|
||||
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) {
|
||||
$node = $this->readNode($node, 0);
|
||||
}
|
||||
$this->ipV4Start = $node;
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function readNode($nodeNumber, $index)
|
||||
{
|
||||
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
|
||||
|
||||
// XXX - probably could condense this.
|
||||
switch ($this->metadata->recordSize) {
|
||||
case 24:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
|
||||
list(, $node) = unpack('N', "\x00" . $bytes);
|
||||
|
||||
return $node;
|
||||
case 28:
|
||||
$middleByte = Util::read($this->fileHandle, $baseOffset + 3, 1);
|
||||
list(, $middle) = unpack('C', $middleByte);
|
||||
if ($index === 0) {
|
||||
$middle = (0xF0 & $middle) >> 4;
|
||||
} else {
|
||||
$middle = 0x0F & $middle;
|
||||
}
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 3);
|
||||
list(, $node) = unpack('N', \chr($middle) . $bytes);
|
||||
|
||||
return $node;
|
||||
case 32:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
|
||||
list(, $node) = unpack('N', $bytes);
|
||||
|
||||
return $node;
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown record size: '
|
||||
. $this->metadata->recordSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveDataPointer($pointer)
|
||||
{
|
||||
$resolved = $pointer - $this->metadata->nodeCount
|
||||
+ $this->metadata->searchTreeSize;
|
||||
if ($resolved > $this->fileSize) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's search tree is corrupt"
|
||||
);
|
||||
}
|
||||
|
||||
list($data) = $this->decoder->decode($resolved);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an extremely naive but reasonably readable implementation. There
|
||||
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
|
||||
* an issue, but I suspect it won't be.
|
||||
*/
|
||||
private function findMetadataStart($filename)
|
||||
{
|
||||
$handle = $this->fileHandle;
|
||||
$fstat = fstat($handle);
|
||||
$fileSize = $fstat['size'];
|
||||
$marker = self::$METADATA_START_MARKER;
|
||||
$markerLength = self::$METADATA_START_MARKER_LENGTH;
|
||||
$metadataMaxLengthExcludingMarker
|
||||
= min(self::$METADATA_MAX_SIZE, $fileSize) - $markerLength;
|
||||
|
||||
for ($i = 0; $i <= $metadataMaxLengthExcludingMarker; ++$i) {
|
||||
for ($j = 0; $j < $markerLength; ++$j) {
|
||||
fseek($handle, $fileSize - $i - $j - 1);
|
||||
$matchBit = fgetc($handle);
|
||||
if ($matchBit !== $marker[$markerLength - $j - 1]) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
return $fileSize - $i;
|
||||
}
|
||||
throw new InvalidDatabaseException(
|
||||
"Error opening database file ($filename). " .
|
||||
'Is this a valid MaxMind DB file?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException if arguments are passed to the method
|
||||
* @throws \BadMethodCallException if the database has been closed
|
||||
*
|
||||
* @return Metadata object for the database
|
||||
*/
|
||||
public function metadata()
|
||||
{
|
||||
if (\func_num_args()) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Method takes no arguments.'
|
||||
);
|
||||
}
|
||||
|
||||
// Not technically required, but this makes it consistent with
|
||||
// C extension and it allows us to change our implementation later.
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the MaxMind DB and returns resources to the system.
|
||||
*
|
||||
* @throws \Exception
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to close a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
fclose($this->fileHandle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
// @codingStandardsIgnoreLine
|
||||
// We subtract 1 from the log to protect against precision loss.
|
||||
\define(__NAMESPACE__ . '\_MM_MAX_INT_BYTES', (log(PHP_INT_MAX, 2) - 1) / 8);
|
||||
|
||||
class Decoder
|
||||
{
|
||||
private $fileStream;
|
||||
private $pointerBase;
|
||||
private $pointerBaseByteSize;
|
||||
// This is only used for unit testing
|
||||
private $pointerTestHack;
|
||||
private $switchByteOrder;
|
||||
|
||||
const _EXTENDED = 0;
|
||||
const _POINTER = 1;
|
||||
const _UTF8_STRING = 2;
|
||||
const _DOUBLE = 3;
|
||||
const _BYTES = 4;
|
||||
const _UINT16 = 5;
|
||||
const _UINT32 = 6;
|
||||
const _MAP = 7;
|
||||
const _INT32 = 8;
|
||||
const _UINT64 = 9;
|
||||
const _UINT128 = 10;
|
||||
const _ARRAY = 11;
|
||||
const _CONTAINER = 12;
|
||||
const _END_MARKER = 13;
|
||||
const _BOOLEAN = 14;
|
||||
const _FLOAT = 15;
|
||||
|
||||
public function __construct(
|
||||
$fileStream,
|
||||
$pointerBase = 0,
|
||||
$pointerTestHack = false
|
||||
) {
|
||||
$this->fileStream = $fileStream;
|
||||
$this->pointerBase = $pointerBase;
|
||||
|
||||
$this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0;
|
||||
$this->pointerTestHack = $pointerTestHack;
|
||||
|
||||
$this->switchByteOrder = $this->isPlatformLittleEndian();
|
||||
}
|
||||
|
||||
public function decode($offset)
|
||||
{
|
||||
list(, $ctrlByte) = unpack(
|
||||
'C',
|
||||
Util::read($this->fileStream, $offset, 1)
|
||||
);
|
||||
++$offset;
|
||||
|
||||
$type = $ctrlByte >> 5;
|
||||
|
||||
// Pointers are a special case, we don't read the next $size bytes, we
|
||||
// use the size to determine the length of the pointer and then follow
|
||||
// it.
|
||||
if ($type === self::_POINTER) {
|
||||
list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
|
||||
|
||||
// for unit testing
|
||||
if ($this->pointerTestHack) {
|
||||
return [$pointer];
|
||||
}
|
||||
|
||||
list($result) = $this->decode($pointer);
|
||||
|
||||
return [$result, $offset];
|
||||
}
|
||||
|
||||
if ($type === self::_EXTENDED) {
|
||||
list(, $nextByte) = unpack(
|
||||
'C',
|
||||
Util::read($this->fileStream, $offset, 1)
|
||||
);
|
||||
|
||||
$type = $nextByte + 7;
|
||||
|
||||
if ($type < 8) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Something went horribly wrong in the decoder. An extended type '
|
||||
. 'resolved to a type number < 8 ('
|
||||
. $type
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
++$offset;
|
||||
}
|
||||
|
||||
list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
|
||||
|
||||
return $this->decodeByType($type, $offset, $size);
|
||||
}
|
||||
|
||||
private function decodeByType($type, $offset, $size)
|
||||
{
|
||||
switch ($type) {
|
||||
case self::_MAP:
|
||||
return $this->decodeMap($size, $offset);
|
||||
case self::_ARRAY:
|
||||
return $this->decodeArray($size, $offset);
|
||||
case self::_BOOLEAN:
|
||||
return [$this->decodeBoolean($size), $offset];
|
||||
}
|
||||
|
||||
$newOffset = $offset + $size;
|
||||
$bytes = Util::read($this->fileStream, $offset, $size);
|
||||
switch ($type) {
|
||||
case self::_BYTES:
|
||||
case self::_UTF8_STRING:
|
||||
return [$bytes, $newOffset];
|
||||
case self::_DOUBLE:
|
||||
$this->verifySize(8, $size);
|
||||
|
||||
return [$this->decodeDouble($bytes), $newOffset];
|
||||
case self::_FLOAT:
|
||||
$this->verifySize(4, $size);
|
||||
|
||||
return [$this->decodeFloat($bytes), $newOffset];
|
||||
case self::_INT32:
|
||||
return [$this->decodeInt32($bytes, $size), $newOffset];
|
||||
case self::_UINT16:
|
||||
case self::_UINT32:
|
||||
case self::_UINT64:
|
||||
case self::_UINT128:
|
||||
return [$this->decodeUint($bytes, $size), $newOffset];
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown or unexpected type: ' . $type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifySize($expected, $actual)
|
||||
{
|
||||
if ($expected !== $actual) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function decodeArray($size, $offset)
|
||||
{
|
||||
$array = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
list($value, $offset) = $this->decode($offset);
|
||||
array_push($array, $value);
|
||||
}
|
||||
|
||||
return [$array, $offset];
|
||||
}
|
||||
|
||||
private function decodeBoolean($size)
|
||||
{
|
||||
return $size === 0 ? false : true;
|
||||
}
|
||||
|
||||
private function decodeDouble($bits)
|
||||
{
|
||||
// This assumes IEEE 754 doubles, but most (all?) modern platforms
|
||||
// use them.
|
||||
//
|
||||
// We are not using the "E" format as that was only added in
|
||||
// 7.0.15 and 7.1.1. As such, we must switch byte order on
|
||||
// little endian machines.
|
||||
list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits));
|
||||
|
||||
return $double;
|
||||
}
|
||||
|
||||
private function decodeFloat($bits)
|
||||
{
|
||||
// This assumes IEEE 754 floats, but most (all?) modern platforms
|
||||
// use them.
|
||||
//
|
||||
// We are not using the "G" format as that was only added in
|
||||
// 7.0.15 and 7.1.1. As such, we must switch byte order on
|
||||
// little endian machines.
|
||||
list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits));
|
||||
|
||||
return $float;
|
||||
}
|
||||
|
||||
private function decodeInt32($bytes, $size)
|
||||
{
|
||||
switch ($size) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
$bytes = str_pad($bytes, 4, "\x00", STR_PAD_LEFT);
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
|
||||
list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
|
||||
|
||||
return $int;
|
||||
}
|
||||
|
||||
private function decodeMap($size, $offset)
|
||||
{
|
||||
$map = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
list($key, $offset) = $this->decode($offset);
|
||||
list($value, $offset) = $this->decode($offset);
|
||||
$map[$key] = $value;
|
||||
}
|
||||
|
||||
return [$map, $offset];
|
||||
}
|
||||
|
||||
private function decodePointer($ctrlByte, $offset)
|
||||
{
|
||||
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
|
||||
|
||||
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
|
||||
$offset = $offset + $pointerSize;
|
||||
|
||||
switch ($pointerSize) {
|
||||
case 1:
|
||||
$packed = (pack('C', $ctrlByte & 0x7)) . $buffer;
|
||||
list(, $pointer) = unpack('n', $packed);
|
||||
$pointer += $this->pointerBase;
|
||||
break;
|
||||
case 2:
|
||||
$packed = "\x00" . (pack('C', $ctrlByte & 0x7)) . $buffer;
|
||||
list(, $pointer) = unpack('N', $packed);
|
||||
$pointer += $this->pointerBase + 2048;
|
||||
break;
|
||||
case 3:
|
||||
$packed = (pack('C', $ctrlByte & 0x7)) . $buffer;
|
||||
|
||||
// It is safe to use 'N' here, even on 32 bit machines as the
|
||||
// first bit is 0.
|
||||
list(, $pointer) = unpack('N', $packed);
|
||||
$pointer += $this->pointerBase + 526336;
|
||||
break;
|
||||
case 4:
|
||||
// We cannot use unpack here as we might overflow on 32 bit
|
||||
// machines
|
||||
$pointerOffset = $this->decodeUint($buffer, $pointerSize);
|
||||
|
||||
$byteLength = $pointerSize + $this->pointerBaseByteSize;
|
||||
|
||||
if ($byteLength <= _MM_MAX_INT_BYTES) {
|
||||
$pointer = $pointerOffset + $this->pointerBase;
|
||||
} elseif (\extension_loaded('gmp')) {
|
||||
$pointer = gmp_strval(gmp_add($pointerOffset, $this->pointerBase));
|
||||
} elseif (\extension_loaded('bcmath')) {
|
||||
$pointer = bcadd($pointerOffset, $this->pointerBase);
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'The gmp or bcmath extension must be installed to read this database.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [$pointer, $offset];
|
||||
}
|
||||
|
||||
private function decodeUint($bytes, $byteLength)
|
||||
{
|
||||
if ($byteLength === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$integer = 0;
|
||||
|
||||
for ($i = 0; $i < $byteLength; ++$i) {
|
||||
$part = \ord($bytes[$i]);
|
||||
|
||||
// We only use gmp or bcmath if the final value is too big
|
||||
if ($byteLength <= _MM_MAX_INT_BYTES) {
|
||||
$integer = ($integer << 8) + $part;
|
||||
} elseif (\extension_loaded('gmp')) {
|
||||
$integer = gmp_strval(gmp_add(gmp_mul($integer, 256), $part));
|
||||
} elseif (\extension_loaded('bcmath')) {
|
||||
$integer = bcadd(bcmul($integer, 256), $part);
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'The gmp or bcmath extension must be installed to read this database.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $integer;
|
||||
}
|
||||
|
||||
private function sizeFromCtrlByte($ctrlByte, $offset)
|
||||
{
|
||||
$size = $ctrlByte & 0x1f;
|
||||
|
||||
if ($size < 29) {
|
||||
return [$size, $offset];
|
||||
}
|
||||
|
||||
$bytesToRead = $size - 28;
|
||||
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
|
||||
|
||||
if ($size === 29) {
|
||||
$size = 29 + \ord($bytes);
|
||||
} elseif ($size === 30) {
|
||||
list(, $adjust) = unpack('n', $bytes);
|
||||
$size = 285 + $adjust;
|
||||
} elseif ($size > 30) {
|
||||
list(, $adjust) = unpack('N', "\x00" . $bytes);
|
||||
$size = ($adjust & (0x0FFFFFFF >> (32 - (8 * $bytesToRead))))
|
||||
+ 65821;
|
||||
}
|
||||
|
||||
return [$size, $offset + $bytesToRead];
|
||||
}
|
||||
|
||||
private function maybeSwitchByteOrder($bytes)
|
||||
{
|
||||
return $this->switchByteOrder ? strrev($bytes) : $bytes;
|
||||
}
|
||||
|
||||
private function isPlatformLittleEndian()
|
||||
{
|
||||
$testint = 0x00FF;
|
||||
$packed = pack('S', $testint);
|
||||
|
||||
return $testint === current(unpack('v', $packed));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
/**
|
||||
* This class should be thrown when unexpected data is found in the database.
|
||||
*/
|
||||
class InvalidDatabaseException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
/**
|
||||
* This class provides the metadata for the MaxMind DB file.
|
||||
*
|
||||
* @property int nodeCount This is an unsigned 32-bit integer indicating
|
||||
* the number of nodes in the search tree.
|
||||
* @property int recordSize This is an unsigned 16-bit integer. It
|
||||
* indicates the number of bits in a record in the search tree. Note that each
|
||||
* node consists of two records.
|
||||
* @property int ipVersion This is an unsigned 16-bit integer which is
|
||||
* always 4 or 6. It indicates whether the database contains IPv4 or IPv6
|
||||
* address data.
|
||||
* @property string databaseType This is a string that indicates the structure
|
||||
* of each data record associated with an IP address. The actual definition of
|
||||
* these structures is left up to the database creator.
|
||||
* @property array languages An array of strings, each of which is a language
|
||||
* code. A given record may contain data items that have been localized to
|
||||
* some or all of these languages. This may be undefined.
|
||||
* @property int binaryFormatMajorVersion This is an unsigned 16-bit
|
||||
* integer indicating the major version number for the database's binary
|
||||
* format.
|
||||
* @property int binaryFormatMinorVersion This is an unsigned 16-bit
|
||||
* integer indicating the minor version number for the database's binary format.
|
||||
* @property int buildEpoch This is an unsigned 64-bit integer that
|
||||
* contains the database build timestamp as a Unix epoch value.
|
||||
* @property array description This key will always point to a map
|
||||
* (associative array). The keys of that map will be language codes, and the
|
||||
* values will be a description in that language as a UTF-8 string. May be
|
||||
* undefined for some databases.
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
private $binaryFormatMajorVersion;
|
||||
private $binaryFormatMinorVersion;
|
||||
private $buildEpoch;
|
||||
private $databaseType;
|
||||
private $description;
|
||||
private $ipVersion;
|
||||
private $languages;
|
||||
private $nodeByteSize;
|
||||
private $nodeCount;
|
||||
private $recordSize;
|
||||
private $searchTreeSize;
|
||||
|
||||
public function __construct($metadata)
|
||||
{
|
||||
$this->binaryFormatMajorVersion =
|
||||
$metadata['binary_format_major_version'];
|
||||
$this->binaryFormatMinorVersion =
|
||||
$metadata['binary_format_minor_version'];
|
||||
$this->buildEpoch = $metadata['build_epoch'];
|
||||
$this->databaseType = $metadata['database_type'];
|
||||
$this->languages = $metadata['languages'];
|
||||
$this->description = $metadata['description'];
|
||||
$this->ipVersion = $metadata['ip_version'];
|
||||
$this->nodeCount = $metadata['node_count'];
|
||||
$this->recordSize = $metadata['record_size'];
|
||||
$this->nodeByteSize = $this->recordSize / 4;
|
||||
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
|
||||
}
|
||||
|
||||
public function __get($var)
|
||||
{
|
||||
return $this->$var;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
class Util
|
||||
{
|
||||
public static function read($stream, $offset, $numberOfBytes)
|
||||
{
|
||||
if ($numberOfBytes === 0) {
|
||||
return '';
|
||||
}
|
||||
if (fseek($stream, $offset) === 0) {
|
||||
$value = fread($stream, $numberOfBytes);
|
||||
|
||||
// We check that the number of bytes read is equal to the number
|
||||
// asked for. We use ftell as getting the length of $value is
|
||||
// much slower.
|
||||
if (ftell($stream) - $offset === $numberOfBytes) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
throw new InvalidDatabaseException(
|
||||
'The MaxMind DB file contains bad data'
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user