Astra Security

This commit is contained in:
Nathanwoodburn
2021-08-30 15:06:36 +10:00
parent 9dc1cdddca
commit 9f437f9588
547 changed files with 65968 additions and 0 deletions

View 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');

View 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"
}
}
}

View 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

View 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

View 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

View 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);
}
}

View File

@@ -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));
}
}

View File

@@ -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
{
}

View File

@@ -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;
}
}

View File

@@ -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'
);
}
}