update v 1.0.7.5

This commit is contained in:
Sujit Prasad
2016-06-13 20:41:55 +05:30
parent aa9786d829
commit 283d97e3ea
5078 changed files with 339851 additions and 175995 deletions

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="MaxMind DB Test Suite">
<directory suffix="Test.php">./tests/MaxMind/Db/Test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/MaxMind/Db/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

33
vendor/maxmind-db/reader/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
*~
*.la
*.lo
*.o
*.old
*.sublime-*
*.sw?
.deps
.gh-pages
/.idea
.libs/
_site
ac*.m4
autom4te.cache/
build/
composer.lock
composer.phar
config.[^mw]*
configure*
core
install-sh
libtool
ltmain.sh
Makefile*
missing
mkinstalldirs
modules/
phpunit.xml
run-tests.php
t.php
/test.php
tmp-php.ini
vendor/

3
vendor/maxmind-db/reader/.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "tests/data"]
path = tests/data
url = git://github.com/maxmind/MaxMind-DB.git

View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -e
set -x
if [ "hhvm" != $TRAVIS_PHP_VERSION ]
then
export CFLAGS="-L$HOME/libmaxminddb/lib"
export CPPFLAGS="-I$HOME/libmaxminddb/include"
cd ext
phpize
./configure --with-maxminddb --enable-maxminddb-debug
make clean
make
NO_INTERACTION=1 make test
cd ..
fi

View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -e
set -x
git submodule update --init --recursive
composer self-update
composer install --dev -n --prefer-source
if [ "hhvm" != "$TRAVIS_PHP_VERSION" ]
then
mkdir -p "$HOME/libmaxminddb"
git clone --recursive git://github.com/maxmind/libmaxminddb
cd libmaxminddb
./bootstrap
./configure --prefix="$HOME/libmaxminddb"
make
make install
fi

View File

@@ -0,0 +1,19 @@
#!/bin/sh
set -e
set -x
mkdir -p build/logs
./vendor/bin/phpunit -c .coveralls-phpunit.xml.dist
if [ "hhvm" != "$TRAVIS_PHP_VERSION" ]
then
echo "mbstring.internal_encoding=utf-8" >> ~/.phpenv/versions/"$(phpenv version-name)"/etc/php.ini
echo "mbstring.func_overload = 7" >> ~/.phpenv/versions/"$(phpenv version-name)"/etc/php.ini
./vendor/bin/phpunit
echo "extension = ext/modules/maxminddb.so" >> ~/.phpenv/versions/"$(phpenv version-name)"/etc/php.ini
./vendor/bin/phpunit
fi
./vendor/bin/phpcs --standard=PSR2 src/

43
vendor/maxmind-db/reader/.travis.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
before_install:
- ./.travis-install-prereqs.sh
install:
- ./.travis-build.sh
- phpenv rehash
script:
- ./.travis-test.sh
after_script:
- php vendor/bin/coveralls
notifications:
email:
recipients:
- dev-ci@maxmind.com
on_success: change
on_failure: always
env:
global:
- secure: "RMIBN2tNKlrGA07coRW4B9m9jCobrYxDkEq3T3jGoGtXgQe/Mr3bI/4zQo7U3bvVTSF90lzkWbxATY45GQXRxWC7Ed2HI2jwUF96CXecdRhKiE9x051HsvXakvbODPLocV7/2LOZqz+eXCUeazLgRaSrIhAqMddFqMQSSM5STlc="
addons:
coverity_scan:
project:
name: "maxmind/MaxMind-DB-Reader-php"
description: "Build submitted via Travis CI"
notification_email: dev-ci@maxmind.com
build_command: "./.travis-build.sh"
branch_pattern: .*coverity.*

80
vendor/maxmind-db/reader/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,80 @@
CHANGELOG
=========
1.1.0 (2016-01-04)
------------------
* The MaxMind DB extension now supports PHP 7. Pull request by John Boehr.
GitHub #27.
1.0.3 (2015-03-13)
------------------
* All uses of `strlen` were removed. This should prevent issues in situations
where the function is overloaded or otherwise broken.
1.0.2 (2015-01-19)
------------------
* Previously the MaxMind DB extension would cause a segfault if the Reader
object's destructor was called without first having called the constructor.
(Reported by Matthias Saou & Juan Peri. GitHub #20.)
1.0.1 (2015-01-12)
------------------
* In the last several releases, the version number in the extension was
incorrect. This release is being done to correct it. No other code changes
are included.
1.0.0 (2014-09-22)
------------------
* First production release.
* In the pure PHP reader, a string length test after `fread()` was replaced
with the difference between the start pointer and the end pointer. This
provided a 15% speed increase.
0.3.3 (2014-09-15)
------------------
* Clarified behavior of 128-bit type in documentation.
* Updated phpunit and fixed some test breakage from the newer version.
0.3.2 (2014-09-10)
------------------
* Fixed invalid reference to global class RuntimeException from namespaced
code. Fixed by Steven Don. GitHub issue #15.
* Additional documentation of `Metadata` class as well as misc. documentation
cleanup.
0.3.1 (2014-05-01)
------------------
* The API now works when `mbstring.func_overload` is set.
* BCMath is no longer required. If the decoder encounters a big integer,
it will try to use GMP and then BCMath. If both of those fail, it will
throw an exception. No databases released by MaxMind currently use big
integers.
* The API now officially supports HHVM when using the pure PHP reader.
0.3.0 (2014-02-19)
------------------
* This API is now licensed under the Apache License, Version 2.0.
* The code for the C extension was cleaned up, fixing several potential
issues.
0.2.0 (2013-10-21)
------------------
* Added optional C extension for using libmaxminddb in place of the pure PHP
reader.
* Significantly improved error handling in pure PHP reader.
* Improved performance for IPv4 lookups in an IPv6 database.
0.1.0 (2013-07-16)
------------------
* Initial release

202
vendor/maxmind-db/reader/LICENSE vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.

140
vendor/maxmind-db/reader/README.md vendored Normal file
View File

@@ -0,0 +1,140 @@
# MaxMind DB Reader PHP API #
## Description ##
This is the PHP API for reading MaxMind DB files. MaxMind DB is a binary file
format that stores data indexed by IP address subnets (IPv4 or IPv6).
## Installation ##
We recommend installing this package with [Composer](http://getcomposer.org/).
### Download Composer ###
To download Composer, run in the root directory of your project:
```bash
curl -sS https://getcomposer.org/installer | php
```
You should now have the file `composer.phar` in your project directory.
### Install Dependencies ###
Run in your project root:
```
php composer.phar require maxmind-db/reader:~1.0
```
You should now have the files `composer.json` and `composer.lock` as well as
the directory `vendor` in your project directory. If you use a version control
system, `composer.json` should be added to it.
### Require Autoloader ###
After installing the dependencies, you need to require the Composer autoloader
from your code:
```php
require 'vendor/autoload.php';
```
## Usage ##
## Example ##
```php
<?php
require_once 'vendor/autoload.php';
use MaxMind\Db\Reader;
$ipAddress = '24.24.24.24';
$databaseFile = 'GeoIP2-City.mmdb';
$reader = new Reader($databaseFile);
print_r($reader->get($ipAddress));
$reader->close()
```
## Optional PHP C Extension ##
MaxMind provides an optional C extension that is a drop-in replacement for for
`MaxMind\Db\Reader`. In order to use this extension, you must install the
Reader API as described above and install the extension as described below. If
you are using an autoloader, no changes to your code should be necessary.
### Installing Extension ###
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as
described in its [README.md
file](https://github.com/maxmind/libmaxminddb/blob/master/README.md#installing-from-a-tarball).
After successfully installing libmaxmindb, run the following commands from the
top-level directory of this distribution:
```
cd ext
phpize
./configure
make
make test
sudo make install
```
You then must load your extension. The recommend method is to add the
following to your `php.ini` file:
```
extension=maxminddb.so
```
Note: You may need to install the PHP development package on your OS such as
php5-dev for Debian-based systems or php-devel for RedHat/Fedora-based ones.
## 128-bit Integer Support ##
The MaxMind DB format includes 128-bit unsigned integer as a type. Although
no MaxMind-distributed database currently makes use of this type, both the
pure PHP reader and the C extension support this type. The pure PHP reader
requires gmp or bcmath to read databases with 128-bit unsigned integers.
The integer is currently returned as a hexadecimal string (prefixed with "0x")
by the C extension and a decimal string (no prefix) by the pure PHP reader.
Any change to make the reader implementations always return either a
hexadecimal or decimal representation of the integer will NOT be considered a
breaking change.
## Support ##
Please report all issues with this code using the [GitHub issue tracker]
(https://github.com/maxmind/MaxMind-DB-Reader-php/issues).
If you are having an issue with a MaxMind service that is not specific to the
client API, please see [our support page](http://www.maxmind.com/en/support).
## Requirements ##
This library requires PHP 5.3 or greater. Older versions of PHP are not
supported. The pure PHP reader included with this library is works and is
tested with HHVM.
The GMP or BCMath extension may be required to read some databases
using the pure PHP API.
## Contributing ##
Patches and pull requests are encouraged. All code should follow the PSR-1 and
PSR-2 style guidelines. Please include unit tests whenever possible.
## Versioning ##
The MaxMind DB Reader PHP API uses [Semantic Versioning](http://semver.org/).
## Copyright and License ##
This software is Copyright (c) 2014 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0.

28
vendor/maxmind-db/reader/composer.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"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.3.1"
},
"require-dev": {
"phpunit/phpunit": "4.2.*",
"satooshi/php-coveralls": "dev-master",
"squizlabs/php_codesniffer": "2.*"
},
"autoload": {
"psr-0": {
"MaxMind": "src/"
}
}
}

View File

@@ -0,0 +1,35 @@
#!/bin/sh
set -e
VERSION=$(perl -MFile::Slurp::Tiny=read_file -MDateTime <<EOF
use v5.16;
my \$log = read_file(q{CHANGELOG.md});
\$log =~ /\n(\d+\.\d+\.\d+) \((\d{4}-\d{2}-\d{2})\)\n/;
die 'Release time is not today!' unless DateTime->now->ymd eq \$2;
say \$1;
EOF
)
perl -pi -e "s/(?<=#define PHP_MAXMINDDB_VERSION \")\d+\.\d+\.\d+(?=\")/$VERSION/" ext/php_maxminddb.h
git diff
if [ -n "$(git status --porcelain)" ]; then
git commit -m "Bumped version to $VERSION" -a
fi
TAG="v$VERSION"
echo "Creating tag $TAG"
git tag -a -m "Release for $VERSION" "$TAG"
read -p "Push to origin? (y/n) " SHOULD_PUSH
if [ "$SHOULD_PUSH" != "y" ]; then
echo "Aborting"
exit 1
fi
git push
git push --tags

View File

@@ -0,0 +1,21 @@
<?php
require_once '../vendor/autoload.php';
use MaxMind\Db\Reader;
$reader = new Reader('GeoIP2-City.mmdb');
$count = 10000;
$startTime = microtime(true);
for ($i = 0; $i < $count; $i++) {
$ip = long2ip(rand(0, pow(2, 32) -1));
$t = $reader->get($ip);
if ($i % 1000 == 0) {
print($i . ' ' . $ip . "\n");
// print_r($t);
}
}
$endTime = microtime(true);
$duration = $endTime - $startTime;
print('Requests per second: ' . $count / $duration . "\n");

View File

@@ -0,0 +1,83 @@
#
# uncrustify config file for the linux kernel
#
# - modified to use spaces instead of tabs
# - added "sp_before_ptr_star = force"
indent_with_tabs = 0
indent_columns = 4
indent_label = 2 # pos: absolute col, neg: relative column
indent_case_brace = 4
code_width = 80
#
# inter-symbol newlines
#
nl_enum_brace = remove # "enum {" vs "enum \n {"
nl_union_brace = remove # "union {" vs "union \n {"
nl_struct_brace = remove # "struct {" vs "struct \n {"
nl_do_brace = remove # "do {" vs "do \n {"
nl_if_brace = remove # "if () {" vs "if () \n {"
nl_for_brace = remove # "for () {" vs "for () \n {"
nl_else_brace = remove # "else {" vs "else \n {"
nl_while_brace = remove # "while () {" vs "while () \n {"
nl_switch_brace = remove # "switch () {" vs "switch () \n {"
nl_brace_while = remove # "} while" vs "} \n while" - cuddle while
nl_brace_else = remove # "} else" vs "} \n else" - cuddle else
nl_func_var_def_blk = 0 # don't add newlines after a block of var declarations
nl_fcall_brace = remove # "list_for_each() {" vs "list_for_each()\n{"
nl_fdef_brace = force # "int foo() {" vs "int foo()\n{"
nl_multi_line_define = true
#
# Source code modifications
#
mod_paren_on_return = remove # "return 1;" vs "return (1);"
mod_full_brace_if = force # "if (a) a--;" vs "if (a) { a--; }"
mod_full_brace_for = force # "for () a--;" vs "for () { a--; }"
mod_full_brace_do = force # "do a--; while ();" vs "do { a--; } while ();"
mod_full_brace_while = force # "while (a) a--;" vs "while (a) { a--; }"
mod_full_brace_nl = 3 # don't remove if more than 3 newlines
#
# inter-character spacing options
#
# sp_return_paren = force # "return (1);" vs "return(1);"
sp_sizeof_paren = remove # "sizeof (int)" vs "sizeof(int)"
sp_before_sparen = force # "if (" vs "if("
sp_after_sparen = force # "if () {" vs "if (){"
sp_after_cast = remove # "(int) a" vs "(int)a"
sp_inside_braces = force # "{ 1 }" vs "{1}"
sp_inside_braces_struct = force # "{ 1 }" vs "{1}"
sp_inside_braces_enum = force # "{ 1 }" vs "{1}"
sp_assign = force
sp_arith = force
sp_bool = force
sp_compare = force
sp_assign = force
sp_after_comma = force
sp_func_def_paren = remove # "int foo (){" vs "int foo(){"
sp_func_call_paren = remove # "foo (" vs "foo("
sp_func_proto_paren = remove # "int foo ();" vs "int foo();"
sp_before_ptr_star = force # "char *foo" vs "char* foo
sp_between_ptr_star = remove # "char * *foo" vs "char **foo"
#
# Aligning stuff
#
align_with_tabs = FALSE # use tabs to align
align_on_tabstop = FALSE # align on tabstops
align_var_def_star_style = 1 # void *foo;
align_var_struct_span = 0
align_enum_equ_span = 4 # '=' in enum definition
align_struct_init_span = 3 # align stuff in a structure init '= { }'
align_right_cmt_span = 3
align_nl_cont = true
align_struct_init_span = 1

19
vendor/maxmind-db/reader/ext/config.m4 vendored Normal file
View File

@@ -0,0 +1,19 @@
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
PHP_CHECK_LIBRARY(maxminddb, MMDB_open)
if test $PHP_MAXMINDDB_DEBUG != "no"; then
CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Werror"
fi
PHP_ADD_LIBRARY(maxminddb, 1, MAXMINDDB_SHARED_LIBADD)
PHP_SUBST(MAXMINDDB_SHARED_LIBADD)
PHP_NEW_EXTENSION(maxminddb, maxminddb.c, $ext_shared)
fi

565
vendor/maxmind-db/reader/ext/maxminddb.c vendored Normal file
View File

@@ -0,0 +1,565 @@
/* 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 <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 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
}
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 != 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;
}
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);
}
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:
ZVAL_LONG(z_value, entry_data_list->entry_data.uint32);
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_uint64(const MMDB_entry_data_list_s *entry_data_list,
zval *z_value TSRMLS_DC)
{
// We return it as a string because PHP uses signed longs
char *int_str;
spprintf(&int_str, 0, "%" PRIu64,
entry_data_list->entry_data.uint64);
CHECK_ALLOCATED(int_str);
_ZVAL_STRING(z_value, int_str);
efree(int_str);
}
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, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(MaxMind_Db_Reader, close, NULL, ZEND_ACC_PUBLIC)
PHP_ME(MaxMind_Db_Reader, get, NULL, ZEND_ACC_PUBLIC)
PHP_ME(MaxMind_Db_Reader, metadata, NULL, 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;
maxminddb_ce->ce_flags |= ZEND_ACC_FINAL;
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;
}
zend_module_entry maxminddb_module_entry = {
STANDARD_MODULE_HEADER,
PHP_MAXMINDDB_EXTNAME,
NULL,
PHP_MINIT(maxminddb),
NULL,
NULL,
NULL,
NULL,
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.1.0"
#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,10 @@
--TEST--
Check for maxminddb presence
--SKIPIF--
<?php if (!extension_loaded("maxminddb")) print "skip"; ?>
--FILE--
<?php
echo "maxminddb extension is available";
?>
--EXPECT--
maxminddb extension is available

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="MaxMind DB Test Suite">
<directory suffix="Test.php">./tests/MaxMind/Db/Test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/MaxMind/Db/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,296 @@
<?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 $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.
* @return array the record for the IP address.
* @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.
*/
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 != 0) {
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;
for ($i = 0; $i < $fileSize - $markerLength + 1; $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,309 @@
<?php
namespace MaxMind\Db\Reader;
use MaxMind\Db\Reader\InvalidDatabaseException;
use MaxMind\Db\Reader\Util;
class Decoder
{
private $fileStream;
private $pointerBase;
// This is only used for unit testing
private $pointerTestHack;
private $switchByteOrder;
private $types = array(
0 => 'extended',
1 => 'pointer',
2 => 'utf8_string',
3 => 'double',
4 => 'bytes',
5 => 'uint16',
6 => 'uint32',
7 => 'map',
8 => 'int32',
9 => 'uint64',
10 => 'uint128',
11 => 'array',
12 => 'container',
13 => 'end_marker',
14 => 'boolean',
15 => 'float',
);
public function __construct(
$fileStream,
$pointerBase = 0,
$pointerTestHack = false
) {
$this->fileStream = $fileStream;
$this->pointerBase = $pointerBase;
$this->pointerTestHack = $pointerTestHack;
$this->switchByteOrder = $this->isPlatformLittleEndian();
}
public function decode($offset)
{
list(, $ctrlByte) = unpack(
'C',
Util::read($this->fileStream, $offset, 1)
);
$offset++;
$type = $this->types[$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 == 'pointer') {
list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
// for unit testing
if ($this->pointerTestHack) {
return array($pointer);
}
list($result) = $this->decode($pointer);
return array($result, $offset);
}
if ($type == 'extended') {
list(, $nextByte) = unpack(
'C',
Util::read($this->fileStream, $offset, 1)
);
$typeNum = $nextByte + 7;
if ($typeNum < 8) {
throw new InvalidDatabaseException(
"Something went horribly wrong in the decoder. An extended type "
. "resolved to a type number < 8 ("
. $this->types[$typeNum]
. ")"
);
}
$type = $this->types[$typeNum];
$offset++;
}
list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
return $this->decodeByType($type, $offset, $size);
}
private function decodeByType($type, $offset, $size)
{
switch ($type) {
case 'map':
return $this->decodeMap($size, $offset);
case 'array':
return $this->decodeArray($size, $offset);
case 'boolean':
return array($this->decodeBoolean($size), $offset);
}
$newOffset = $offset + $size;
$bytes = Util::read($this->fileStream, $offset, $size);
switch ($type) {
case 'utf8_string':
return array($this->decodeString($bytes), $newOffset);
case 'double':
$this->verifySize(8, $size);
return array($this->decodeDouble($bytes), $newOffset);
case 'float':
$this->verifySize(4, $size);
return array($this->decodeFloat($bytes), $newOffset);
case 'bytes':
return array($bytes, $newOffset);
case 'uint16':
case 'uint32':
return array($this->decodeUint($bytes), $newOffset);
case 'int32':
return array($this->decodeInt32($bytes), $newOffset);
case 'uint64':
case 'uint128':
return array($this->decodeBigUint($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 = array();
for ($i = 0; $i < $size; $i++) {
list($value, $offset) = $this->decode($offset);
array_push($array, $value);
}
return array($array, $offset);
}
private function decodeBoolean($size)
{
return $size == 0 ? false : true;
}
private function decodeDouble($bits)
{
// XXX - Assumes IEEE 754 double on platform
list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits));
return $double;
}
private function decodeFloat($bits)
{
// XXX - Assumes IEEE 754 floats on platform
list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits));
return $float;
}
private function decodeInt32($bytes)
{
$bytes = $this->zeroPadLeft($bytes, 4);
list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
return $int;
}
private function decodeMap($size, $offset)
{
$map = array();
for ($i = 0; $i < $size; $i++) {
list($key, $offset) = $this->decode($offset);
list($value, $offset) = $this->decode($offset);
$map[$key] = $value;
}
return array($map, $offset);
}
private $pointerValueOffset = array(
1 => 0,
2 => 2048,
3 => 526336,
4 => 0,
);
private function decodePointer($ctrlByte, $offset)
{
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
$offset = $offset + $pointerSize;
$packed = $pointerSize == 4
? $buffer
: (pack('C', $ctrlByte & 0x7)) . $buffer;
$unpacked = $this->decodeUint($packed);
$pointer = $unpacked + $this->pointerBase
+ $this->pointerValueOffset[$pointerSize];
return array($pointer, $offset);
}
private function decodeUint($bytes)
{
list(, $int) = unpack('N', $this->zeroPadLeft($bytes, 4));
return $int;
}
private function decodeBigUint($bytes, $byteLength)
{
$maxUintBytes = log(PHP_INT_MAX, 2) / 8;
if ($byteLength == 0) {
return 0;
}
$numberOfLongs = ceil($byteLength / 4);
$paddedLength = $numberOfLongs * 4;
$paddedBytes = $this->zeroPadLeft($bytes, $paddedLength);
$unpacked = array_merge(unpack("N$numberOfLongs", $paddedBytes));
$integer = 0;
// 2^32
$twoTo32 = '4294967296';
foreach ($unpacked as $part) {
// We only use gmp or bcmath if the final value is too big
if ($byteLength <= $maxUintBytes) {
$integer = ($integer << 32) + $part;
} elseif (extension_loaded('gmp')) {
$integer = gmp_strval(gmp_add(gmp_mul($integer, $twoTo32), $part));
} elseif (extension_loaded('bcmath')) {
$integer = bcadd(bcmul($integer, $twoTo32), $part);
} else {
throw new \RuntimeException(
'The gmp or bcmath extension must be installed to read this database.'
);
}
}
return $integer;
}
private function decodeString($bytes)
{
// XXX - NOOP. As far as I know, the end user has to explicitly set the
// encoding in PHP. Strings are just bytes.
return $bytes;
}
private function sizeFromCtrlByte($ctrlByte, $offset)
{
$size = $ctrlByte & 0x1f;
$bytesToRead = $size < 29 ? 0 : $size - 28;
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
$decoded = $this->decodeUint($bytes);
if ($size == 29) {
$size = 29 + $decoded;
} elseif ($size == 30) {
$size = 285 + $decoded;
} elseif ($size > 30) {
$size = ($decoded & (0x0FFFFFFF >> (32 - (8 * $bytesToRead))))
+ 65821;
}
return array($size, $offset + $bytesToRead);
}
private function zeroPadLeft($content, $desiredLength)
{
return str_pad($content, $desiredLength, "\x00", STR_PAD_LEFT);
}
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,77 @@
<?php
namespace MaxMind\Db\Reader;
/**
* This class provides the metadata for the MaxMind DB file.
*
* @property integer nodeCount This is an unsigned 32-bit integer indicating
* the number of nodes in the search tree.
*
* @property integer 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 integer 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 integer binaryFormatMajorVersion This is an unsigned 16-bit
* integer indicating the major version number for the database's binary
* format.
*
* @property integer binaryFormatMinorVersion This is an unsigned 16-bit
* integer indicating the minor version number for the database's binary format.
*
* @property integer 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,28 @@
<?php
namespace MaxMind\Db\Reader;
use MaxMind\Db\Reader\InvalidDatabaseException;
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"
);
}
}

View File

@@ -0,0 +1,369 @@
<?php
namespace MaxMind\Db\Test\Reader;
use MaxMind\Db\Reader\Decoder;
class DecoderTest extends \PHPUnit_Framework_TestCase
{
private $arrays = array(
array(
'expected' => array(),
'input' => array(0x0, 0x4),
'name' => 'empty',
),
array(
'expected' => array('Foo'),
'input' => array(0x1, 0x4, /* Foo */
0x43, 0x46, 0x6f, 0x6f),
'name' => 'one element',
),
array(
'expected' => array('Foo', '人'),
'input' => array(
0x2, 0x4,
/* Foo */
0x43, 0x46, 0x6f, 0x6f,
/* 人 */
0x43, 0xe4, 0xba, 0xba
),
'name' => 'two elements',
),
);
private $booleans = array(
false => array(0x0, 0x7),
true => array(0x1, 0x7),
);
private $doubles = array(
'0.0' => array(0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0),
'0.5' => array(0x68, 0x3F, 0xE0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0),
'3.14159265359' => array(0x68, 0x40, 0x9, 0x21, 0xFB, 0x54, 0x44,
0x2E, 0xEA),
'123.0' => array(0x68, 0x40, 0x5E, 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0),
'1073741824.12457' => array(0x68, 0x41, 0xD0, 0x0, 0x0, 0x0, 0x7,
0xF8, 0xF4),
'-0.5' => array(0x68, 0xBF, 0xE0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0),
'-3.14159265359' => array(0x68, 0xC0, 0x9, 0x21, 0xFB, 0x54, 0x44,
0x2E, 0xEA),
'-1073741824.12457' => array(0x68, 0xC1, 0xD0, 0x0, 0x0, 0x0, 0x7,
0xF8, 0xF4),
);
private $floats = array(
'0.0' => array(0x4, 0x8, 0x0, 0x0, 0x0, 0x0),
'1.0' => array(0x4, 0x8, 0x3F, 0x80, 0x0, 0x0),
'1.1' => array(0x4, 0x8, 0x3F, 0x8C, 0xCC, 0xCD),
'3.14' => array(0x4, 0x8, 0x40, 0x48, 0xF5, 0xC3),
'9999.99' => array(0x4, 0x8, 0x46, 0x1C, 0x3F, 0xF6),
'-1.0' => array(0x4, 0x8, 0xBF, 0x80, 0x0, 0x0),
'-1.1' => array(0x4, 0x8, 0xBF, 0x8C, 0xCC, 0xCD),
'-3.14' => array(0x4, 0x8, 0xC0, 0x48, 0xF5, 0xC3),
'-9999.99' => array(0x4, 0x8, 0xC6, 0x1C, 0x3F, 0xF6)
);
// PHP can't have arrays/objects as keys. Maybe redo all of the tests
// this way so that we can use one test runner
private $maps = array(
array(
'expected' => array(),
'input' => array(0xe0),
'name' => 'empty',
),
array(
'expected' => array('en' => 'Foo'),
'input' => array(0xe1, /* en */
0x42, 0x65, 0x6e,
/* Foo */
0x43, 0x46, 0x6f, 0x6f),
'name' => 'one key',
),
array(
'expected' => array('en' => 'Foo', 'zh' => '人'),
'input' => array(
0xe2,
/* en */
0x42, 0x65, 0x6e,
/* Foo */
0x43, 0x46, 0x6f, 0x6f,
/* zh */
0x42, 0x7a, 0x68,
/* 人 */
0x43, 0xe4, 0xba, 0xba
),
'name' => 'two keys',
),
array(
'expected' => array('name' => array('en' => 'Foo', 'zh' => '人')),
'input' => array(
0xe1,
/* name */
0x44, 0x6e, 0x61, 0x6d, 0x65, 0xe2,
/* en */
0x42, 0x65, 0x6e,
/* Foo */
0x43, 0x46, 0x6f, 0x6f,
/* zh */
0x42, 0x7a, 0x68,
/* 人 */
0x43, 0xe4, 0xba, 0xba
),
'name' => 'nested',
),
array(
'expected' => array('languages' => array('en', 'zh')),
'input' => array(
0xe1,
/* languages */
0x49, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61,
0x67, 0x65, 0x73,
/* array */
0x2, 0x4,
/* en */
0x42, 0x65, 0x6e,
/* zh */
0x42, 0x7a, 0x68
),
'name' => 'map with array in it'
),
);
private $pointers = array(
0 => array(0x20, 0x0),
5 => array(0x20, 0x5),
10 => array(0x20, 0xa),
1023 => array(0x23, 0xff,),
3017 => array(0x28, 0x3, 0xc9),
524283 => array(0x2f, 0xf7, 0xfb),
526335 => array(0x2f, 0xff, 0xff),
134217726 => array(0x37, 0xf7, 0xf7, 0xfe),
134744063 => array(0x37, 0xff, 0xff, 0xff),
2147483647 => array(0x38, 0x7f, 0xff, 0xff, 0xff),
4294967295 => array(0x38, 0xff, 0xff, 0xff, 0xff),
);
private $uint16 = array(
0 => array(0xa0),
255 => array(0xa1, 0xff),
500 => array(0xa2, 0x1, 0xf4),
10872 => array(0xa2, 0x2a, 0x78),
65535 => array(0xa2, 0xff, 0xff),
);
private $int32 = array(
'0' => array(0x0, 0x1),
'-1' => array(0x4, 0x1, 0xff, 0xff, 0xff, 0xff),
'255' => array(0x1, 0x1, 0xff),
'-255' => array(0x4, 0x1, 0xff, 0xff, 0xff, 0x1),
'500' => array(0x2, 0x1, 0x1, 0xf4),
'-500' => array(0x4, 0x1, 0xff, 0xff, 0xfe, 0xc),
'65535' => array(0x2, 0x1, 0xff, 0xff),
'-65535' => array(0x4, 0x1, 0xff, 0xff, 0x0, 0x1),
'16777215' => array(0x3, 0x1, 0xff, 0xff, 0xff),
'-16777215' => array(0x4, 0x1, 0xff, 0x0, 0x0, 0x1),
'2147483647' => array(0x4, 0x1, 0x7f, 0xff, 0xff, 0xff),
'-2147483647' => array(0x4, 0x1, 0x80, 0x0, 0x0, 0x1),
);
private function strings()
{
$strings = array(
'' => array(0x40),
1 => array(0x41, 0x31),
'人' => array(0x43, 0xE4, 0xBA, 0xBA),
'123' => array(0x43, 0x31, 0x32, 0x33),
'123456789012345678901234567' => array(0x5b, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37),
'1234567890123456789012345678' => array(0x5c, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38),
'12345678901234567890123456789' => array(0x5d, 0x0, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39),
'123456789012345678901234567890' => array(0x5d, 0x1, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x30),
);
$strings[str_repeat('x', 500)] =
array_pad(
array(0x5e, 0x0, 0xd7),
503,
0x78
);
$strings[str_repeat('x', 2000)] =
array_pad(
array(0x5e, 0x6, 0xb3),
2003,
0x78
);
$strings[str_repeat('x', 70000)] =
array_pad(
array(0x5f, 0x0, 0x10, 0x53),
70004,
0x78
);
return $strings;
}
private $uint32 = array(
0 => array(0xc0),
255 => array(0xc1, 0xff),
500 => array(0xc2, 0x1, 0xf4),
10872 => array(0xc2, 0x2a, 0x78),
65535 => array(0xc2, 0xff, 0xff),
16777215 => array(0xc3, 0xff, 0xff, 0xff),
4294967295 => array(0xc4, 0xff, 0xff, 0xff, 0xff),
);
private function bytes()
{
// ugly deep clone
$bytes = unserialize(serialize($this->strings()));
foreach ($bytes as $key => $byte_array) {
$byte_array[0] ^= 0xc0;
$bytes[$key] = $byte_array;
}
return $bytes;
}
public function generateLargeUint($bits)
{
$ctrlByte = $bits == 64 ? 0x2 : 0x3;
$uints = array(
0 => array(0x0, $ctrlByte),
500 => array(0x2, $ctrlByte, 0x1, 0xf4),
10872 => array(0x2, $ctrlByte, 0x2a, 0x78),
);
for ($power = 1; $power <= $bits / 8; $power++) {
$expected = bcsub(bcpow(2, 8 * $power), 1);
$input = array($power, $ctrlByte);
for ($i = 2; $i < 2 + $power; $i++) {
$input[$i] = 0xff;
}
$uints[$expected] = $input;
}
return $uints;
}
public function testArrays()
{
$this->validateTypeDecodingList('array', $this->arrays);
}
public function testBooleans()
{
$this->validateTypeDecoding('boolean', $this->booleans);
}
public function testBytes()
{
$this->validateTypeDecoding('byte', $this->bytes());
}
public function testDoubles()
{
$this->validateTypeDecoding('double', $this->doubles);
}
public function testFloats()
{
$this->validateTypeDecoding('float', $this->floats);
}
public function testInt32()
{
$this->validateTypeDecoding('int32', $this->int32);
}
public function testMaps()
{
$this->validateTypeDecodingList('map', $this->maps);
}
public function testPointers()
{
$this->validateTypeDecoding('pointers', $this->pointers);
}
public function testStrings()
{
$this->validateTypeDecoding('utf8_string', $this->strings());
}
public function testUint16()
{
$this->validateTypeDecoding('uint16', $this->uint16);
}
public function testUint32()
{
$this->validateTypeDecoding('uint32', $this->uint32);
}
public function testUint64()
{
$this->validateTypeDecoding('uint64', $this->generateLargeUint(64));
}
public function testUint128()
{
$this->validateTypeDecoding('uint128', $this->generateLargeUint(128));
}
private function validateTypeDecoding($type, $tests)
{
foreach ($tests as $expected => $input) {
$this->checkDecoding($type, $input, $expected);
}
}
private function validateTypeDecodingList($type, $tests)
{
foreach ($tests as $test) {
$this->checkDecoding(
$type,
$test['input'],
$test['expected'],
$test['name']
);
}
}
private function checkDecoding($type, $input, $expected, $name = null)
{
$name = $name || $expected;
$description = "decoded $type - $name";
$handle = fopen('php://memory', 'rw');
foreach ($input as $byte) {
fwrite($handle, pack('C', $byte));
}
fseek($handle, 0);
$decoder = new Decoder($handle, 0, true);
list($actual) = $decoder->decode(0);
if ($type == 'float') {
$actual = round($actual, 2);
}
$this->assertEquals($expected, $actual, $description);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace MaxMind\Db\Test\Reader;
use MaxMind\Db\Reader\Decoder;
class PointerTest extends \PHPUnit_Framework_TestCase
{
public function testWithPointers()
{
$handle = fopen('tests/data/test-data/maps-with-pointers.raw', 'r');
$decoder = new Decoder($handle, 0);
$this->assertEquals(
array(array('long_key' => 'long_value1'), 22),
$decoder->decode(0)
);
$this->assertEquals(
array(array('long_key' => 'long_value2'), 37),
$decoder->decode(22)
);
$this->assertEquals(
array(array('long_key2' => 'long_value1'), 50),
$decoder->decode(37)
);
$this->assertEquals(
array(array('long_key2' => 'long_value2'), 55),
$decoder->decode(50)
);
$this->assertEquals(
array(array('long_key' => 'long_value1'), 57),
$decoder->decode(55)
);
$this->assertEquals(
array(array('long_key2' => 'long_value2'), 59),
$decoder->decode(57)
);
}
}

View File

@@ -0,0 +1,391 @@
<?php
namespace MaxMind\Db\Test\Reader;
use MaxMind\Db\Reader;
class ReaderTest extends \PHPUnit_Framework_TestCase
{
public function testReader()
{
foreach (array(24, 28, 32) as $recordSize) {
foreach (array(4, 6) as $ipVersion) {
$fileName = 'tests/data/test-data/MaxMind-DB-test-ipv'
. $ipVersion . '-' . $recordSize . '.mmdb';
$reader = new Reader($fileName);
$this->checkMetadata($reader, $ipVersion, $recordSize);
if ($ipVersion == 4) {
$this->checkIpV4($reader, $fileName);
} else {
$this->checkIpV6($reader, $fileName);
}
}
}
}
public function testDecoder()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-decoder.mmdb');
$record = $reader->get('::1.1.1.0');
$this->assertEquals(true, $record['boolean']);
$this->assertEquals(pack('N', 42), $record['bytes']);
$this->assertEquals('unicode! ☯ - ♫', $record['utf8_string']);
$this->assertEquals(array(1, 2, 3), $record['array']);
$this->assertEquals(
array(
'mapX' => array(
'arrayX' => array(7, 8, 9),
'utf8_stringX' => 'hello'
),
),
$record['map']
);
$this->assertEquals(42.123456, $record['double']);
$this->assertEquals(1.1, $record['float'], 'float', 0.000001);
$this->assertEquals(-268435456, $record['int32']);
$this->assertEquals(100, $record['uint16']);
$this->assertEquals(268435456, $record['uint32']);
$this->assertEquals('1152921504606846976', $record['uint64']);
$uint128 = $record['uint128'];
// For the C extension, which returns a hexadecimal
if (extension_loaded('gmp')) {
$uint128 = gmp_strval($uint128);
} else {
$this->markTestIncomplete('Requires gmp extension to check value of uint128');
}
$this->assertEquals(
'1329227995784915872903807060280344576',
$uint128
);
}
public function testZeros()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-decoder.mmdb');
$record = $reader->get('::');
$this->assertEquals(false, $record['boolean']);
$this->assertEquals('', $record['bytes']);
$this->assertEquals('', $record['utf8_string']);
$this->assertEquals(array(), $record['array']);
$this->assertEquals(array(), $record['map']);
$this->assertEquals(0, $record['double']);
$this->assertEquals(0, $record['float'], 'float', 0.000001);
$this->assertEquals(0, $record['int32']);
$this->assertEquals(0, $record['uint16']);
$this->assertEquals(0, $record['uint32']);
$this->assertEquals(0, $record['uint64']);
$uint128 = $record['uint128'];
if (extension_loaded('gmp')) {
$uint128 = gmp_strval($uint128);
} else {
$this->markTestIncomplete('Requires gmp extension to check value of uint128');
}
$this->assertEquals('0', $uint128);
}
public function testNoIpV4SearchTree()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb'
);
$this->assertEquals('::0/64', $reader->get('1.1.1.1'));
$this->assertEquals('::0/64', $reader->get('192.1.1.1'));
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Error looking up 2001::. You attempted to look up an IPv6 address in an IPv4-only database
*/
public function testV6AddressV4Database()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-ipv4-24.mmdb');
$reader->get('2001::');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage The value "not_ip" is not a valid IP address.
*/
public function testIpValidation()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-decoder.mmdb');
$reader->get('not_ip');
}
/**
* @expectedException MaxMind\Db\Reader\InvalidDatabaseException
* @expectedExceptionMessage The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)
*/
public function testBrokenDatabase()
{
$reader = new Reader('tests/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb');
$reader->get('2001:220::');
}
/**
* @expectedException MaxMind\Db\Reader\InvalidDatabaseException
* @expectedExceptionMessage The MaxMind DB file's search tree is corrupt
*/
public function testBrokenSearchTreePointer()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb');
$reader->get('1.1.1.32');
}
/**
* @expectedException MaxMind\Db\Reader\InvalidDatabaseException
* @expectedExceptionMessage contains bad data
*/
public function testBrokenDataPointer()
{
$reader = new Reader('tests/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb');
$reader->get('1.1.1.16');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage The file "file-does-not-exist.mmdb" does not exist or is not readable.
*/
public function testMissingDatabase()
{
new Reader('file-does-not-exist.mmdb');
}
/**
* @expectedException MaxMind\Db\Reader\InvalidDatabaseException
* @expectedExceptionMessage Error opening database file (README.md). Is this a valid MaxMind DB file?
*/
public function testNonDatabase()
{
new Reader('README.md');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage The constructor takes exactly one argument.
*/
public function testTooManyConstructorArgs()
{
new Reader('README.md', 1);
}
/**
* @expectedException InvalidArgumentException
*
* This test only matters for the extension.
*/
public function testNoConstructorArgs()
{
if (extension_loaded('maxminddb')) {
new Reader();
} else {
throw new \InvalidArgumentException();
}
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Method takes exactly one argument.
*/
public function testTooManyGetAgs()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->get('1.1.1.1', 'blah');
}
/**
* @expectedException InvalidArgumentException
*
* This test only matters for the extension.
*/
public function testNoGetArgs()
{
if (extension_loaded('maxminddb')) {
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->get();
} else {
throw new \InvalidArgumentException();
}
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Method takes no arguments.
*/
public function testMetadataAgs()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->metadata('blah');
}
public function testClose()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->close();
}
/**
* @expectedException BadMethodCallException
* @expectedExceptionMessage Attempt to close a closed MaxMind DB.
*/
public function testDoubleClose()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->close();
$reader->close();
}
/**
* @expectedException BadMethodCallException
* @expectedExceptionMessage Attempt to read from a closed MaxMind DB.
*/
public function testClosedGet()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->close();
$reader->get('1.1.1.1');
}
/**
* @expectedException BadMethodCallException
* @expectedExceptionMessage Attempt to read from a closed MaxMind DB.
*/
public function testClosedMetadata()
{
$reader = new Reader(
'tests/data/test-data/MaxMind-DB-test-decoder.mmdb'
);
$reader->close();
$reader->metadata();
}
private function checkMetadata($reader, $ipVersion, $recordSize)
{
$metadata = $reader->metadata();
$this->assertEquals(
2,
$metadata->binaryFormatMajorVersion,
'major version'
);
$this->assertEquals(0, $metadata->binaryFormatMinorVersion);
$this->assertGreaterThan(1373571901, $metadata->buildEpoch);
$this->assertEquals('Test', $metadata->databaseType);
$this->assertEquals(
array('en' => 'Test Database', 'zh' => 'Test Database Chinese'),
$metadata->description
);
$this->assertEquals($ipVersion, $metadata->ipVersion);
$this->assertEquals(array('en', 'zh'), $metadata->languages);
$this->assertEquals($recordSize / 4, $metadata->nodeByteSize);
$this->assertGreaterThan(36, $metadata->nodeCount);
$this->assertEquals($recordSize, $metadata->recordSize);
$this->assertGreaterThan(200, $metadata->searchTreeSize);
}
private function checkIpV4(Reader $reader, $fileName)
{
for ($i = 0; $i <= 5; $i++) {
$address = '1.1.1.' . pow(2, $i);
$this->assertEquals(
array('ip' => $address),
$reader->get($address),
'found expected data record for '
. $address . ' in ' . $fileName
);
}
$pairs = array(
'1.1.1.3' => '1.1.1.2',
'1.1.1.5' => '1.1.1.4',
'1.1.1.7' => '1.1.1.4',
'1.1.1.9' => '1.1.1.8',
'1.1.1.15' => '1.1.1.8',
'1.1.1.17' => '1.1.1.16',
'1.1.1.31' => '1.1.1.16'
);
foreach ($pairs as $keyAddress => $valueAddress) {
$data = array('ip' => $valueAddress);
$this->assertEquals(
$data,
$reader->get($keyAddress),
'found expected data record for ' . $keyAddress . ' in '
. $fileName
);
}
foreach (array('1.1.1.33', '255.254.253.123') as $ip) {
$this->assertNull($reader->get($ip));
}
}
// XXX - logic could be combined with above
private function checkIpV6(Reader $reader, $fileName)
{
$subnets = array('::1:ffff:ffff', '::2:0:0',
'::2:0:40', '::2:0:50', '::2:0:58');
foreach ($subnets as $address) {
$this->assertEquals(
array('ip' => $address),
$reader->get($address),
'found expected data record for ' . $address . ' in '
. $fileName
);
}
$pairs = array(
'::2:0:1' => '::2:0:0',
'::2:0:33' => '::2:0:0',
'::2:0:39' => '::2:0:0',
'::2:0:41' => '::2:0:40',
'::2:0:49' => '::2:0:40',
'::2:0:52' => '::2:0:50',
'::2:0:57' => '::2:0:50',
'::2:0:59' => '::2:0:58'
);
foreach ($pairs as $keyAddress => $valueAddress) {
$this->assertEquals(
array('ip' => $valueAddress),
$reader->get($keyAddress),
'found expected data record for ' . $keyAddress . ' in '
. $fileName
);
}
foreach (array('1.1.1.33', '255.254.253.123', '89fa::') as $ip) {
$this->assertNull($reader->get($ip));
}
}
}

View File

@@ -0,0 +1,7 @@
<?php
if (!$loader = @include __DIR__.'/../vendor/autoload.php') {
die('Project dependencies missing');
}
$loader->add('MaxMind\Db\Test', __DIR__);