first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} \.php$
RewriteRule .* - [F,L,NC]
</IfModule>
<IfModule !mod_rewrite.c>
<FilesMatch "\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</FilesMatch>
</IfModule>

View File

@@ -0,0 +1,9 @@
<?php
// autoload.php @generated by Composer
if (!class_exists('ComposerAutoloaderInit6d00a11c4faa7bdc4bb08ac266cdf951')) {
require_once __DIR__ . '/composer/autoload_real.php';
}
return ComposerAutoloaderInit6d00a11c4faa7bdc4bb08ac266cdf951::getLoader();

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,14 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
'MaxMind\\Exception\\' => array($vendorDir . '/maxmind/web-service-common/src/Exception'),
'MaxMind\\Db\\' => array($vendorDir . '/maxmind-db/reader/src/MaxMind/Db'),
'GeoIp2\\' => array($vendorDir . '/geoip2/geoip2/src'),
'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
);

View File

@@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit6d00a11c4faa7bdc4bb08ac266cdf951
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit6d00a11c4faa7bdc4bb08ac266cdf951', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit6d00a11c4faa7bdc4bb08ac266cdf951', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit6d00a11c4faa7bdc4bb08ac266cdf951::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,57 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit6d00a11c4faa7bdc4bb08ac266cdf951
{
public static $prefixLengthsPsr4 = array (
'M' =>
array (
'MaxMind\\WebService\\' => 19,
'MaxMind\\Exception\\' => 18,
'MaxMind\\Db\\' => 11,
),
'G' =>
array (
'GeoIp2\\' => 7,
),
'C' =>
array (
'Composer\\CaBundle\\' => 18,
),
);
public static $prefixDirsPsr4 = array (
'MaxMind\\WebService\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/WebService',
),
'MaxMind\\Exception\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/Exception',
),
'MaxMind\\Db\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind-db/reader/src/MaxMind/Db',
),
'GeoIp2\\' =>
array (
0 => __DIR__ . '/..' . '/geoip2/geoip2/src',
),
'Composer\\CaBundle\\' =>
array (
0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit6d00a11c4faa7bdc4bb08ac266cdf951::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit6d00a11c4faa7bdc4bb08ac266cdf951::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,19 @@
Copyright (C) 2016 Composer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,308 @@
<?php
/*
* This file is part of composer/ca-bundle.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\CaBundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;
/**
* @author Chris Smith <chris@cs278.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CaBundle
{
private static $caPath;
private static $caFileValidity = array();
private static $useOpensslParse;
/**
* Returns the system CA bundle path, or a path to the bundled one
*
* This method was adapted from Sslurp.
* https://github.com/EvanDotPro/Sslurp
*
* (c) Evan Coury <me@evancoury.com>
*
* For the full copyright and license information, please see below:
*
* Copyright (c) 2013, Evan Coury
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
* @return string path to a CA bundle file or directory
*/
public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
{
if (self::$caPath !== null) {
return self::$caPath;
}
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$envCertFile = getenv('SSL_CERT_FILE');
if ($envCertFile && is_readable($envCertFile) && static::validateCaFile($envCertFile, $logger)) {
return self::$caPath = $envCertFile;
}
// If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$envCertDir = getenv('SSL_CERT_DIR');
if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) {
return self::$caPath = $envCertDir;
}
$configured = ini_get('openssl.cafile');
if ($configured && strlen($configured) > 0 && is_readable($configured) && static::validateCaFile($configured, $logger)) {
return self::$caPath = $configured;
}
$configured = ini_get('openssl.capath');
if ($configured && is_dir($configured) && is_readable($configured)) {
return self::$caPath = $configured;
}
$caBundlePaths = array(
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
'/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
'/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
'/usr/ssl/certs/ca-bundle.crt', // Cygwin
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
'/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
'/etc/ssl/cert.pem', // OpenBSD
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
'/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
);
foreach ($caBundlePaths as $caBundle) {
if (@is_readable($caBundle) && static::validateCaFile($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
}
foreach ($caBundlePaths as $caBundle) {
$caBundle = dirname($caBundle);
if (@is_dir($caBundle) && glob($caBundle.'/*')) {
return self::$caPath = $caBundle;
}
}
return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
}
/**
* Returns the path to the bundled CA file
*
* In case you don't want to trust the user or the system, you can use this directly
*
* @return string path to a CA bundle file
*/
public static function getBundledCaBundlePath()
{
$caBundleFile = __DIR__.'/../res/cacert.pem';
// cURL does not understand 'phar://' paths
// see https://github.com/composer/ca-bundle/issues/10
if (0 === strpos($caBundleFile, 'phar://')) {
file_put_contents(
$tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-'),
file_get_contents($caBundleFile)
);
register_shutdown_function(function() use ($tempCaBundleFile) {
@unlink($tempCaBundleFile);
});
$caBundleFile = $tempCaBundleFile;
}
return $caBundleFile;
}
/**
* Validates a CA file using opensl_x509_parse only if it is safe to use
*
* @param string $filename
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
*
* @return bool
*/
public static function validateCaFile($filename, LoggerInterface $logger = null)
{
static $warned = false;
if (isset(self::$caFileValidity[$filename])) {
return self::$caFileValidity[$filename];
}
$contents = file_get_contents($filename);
// assume the CA is valid if php is vulnerable to
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
if (!static::isOpensslParseSafe()) {
if (!$warned && $logger) {
$logger->warning(sprintf(
'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
PHP_VERSION
));
$warned = true;
}
$isValid = !empty($contents);
} else {
$isValid = (bool) openssl_x509_parse($contents);
}
if ($logger) {
$logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
}
return self::$caFileValidity[$filename] = $isValid;
}
/**
* Test if it is safe to use the PHP function openssl_x509_parse().
*
* This checks if OpenSSL extensions is vulnerable to remote code execution
* via the exploit documented as CVE-2013-6420.
*
* @return bool
*/
public static function isOpensslParseSafe()
{
if (null !== self::$useOpensslParse) {
return self::$useOpensslParse;
}
if (PHP_VERSION_ID >= 50600) {
return self::$useOpensslParse = true;
}
// Vulnerable:
// PHP 5.3.0 - PHP 5.3.27
// PHP 5.4.0 - PHP 5.4.22
// PHP 5.5.0 - PHP 5.5.6
if (
(PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
|| (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
|| (PHP_VERSION_ID < 50600 && PHP_VERSION_ID >= 50507)
) {
// This version of PHP has the fix for CVE-2013-6420 applied.
return self::$useOpensslParse = true;
}
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
// Windows is probably insecure in this case.
return self::$useOpensslParse = false;
}
$compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
$regex = '{^'.preg_quote($prefix).'([0-9]+)$}';
if (preg_match($regex, PHP_VERSION, $m)) {
return ((int) $m[1]) >= $fixedVersion;
}
return false;
};
// Hard coded list of PHP distributions with the fix backported.
if (
$compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
|| $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
|| $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
) {
return self::$useOpensslParse = true;
}
// Symfony Process component is missing so we assume it is unsafe at this point
if (!class_exists('Symfony\Component\Process\PhpProcess')) {
return self::$useOpensslParse = false;
}
// This is where things get crazy, because distros backport security
// fixes the chances are on NIX systems the fix has been applied but
// it's not possible to verify that from the PHP version.
//
// To verify exec a new PHP process and run the issue testcase with
// known safe input that replicates the bug.
// Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
// changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
$cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
$script = <<<'EOT'
error_reporting(-1);
$info = openssl_x509_parse(base64_decode('%s'));
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);
EOT;
$script = '<'."?php\n".sprintf($script, $cert);
try {
$process = new PhpProcess($script);
$process->mustRun();
} catch (\Exception $e) {
// In the case of any exceptions just accept it is not possible to
// determine the safety of openssl_x509_parse and bail out.
return self::$useOpensslParse = false;
}
$output = preg_split('{\r?\n}', trim($process->getOutput()));
$errorOutput = trim($process->getErrorOutput());
if (
count($output) === 3
&& $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
&& $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
&& $output[2] === 'int(-1)'
&& preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
) {
// This PHP has the fix backported probably by a distro security team.
return self::$useOpensslParse = true;
}
return self::$useOpensslParse = false;
}
/**
* Resets the static caches
*/
public static function reset()
{
self::$caFileValidity = array();
self::$caPath = null;
self::$useOpensslParse = null;
}
}

View File

@@ -0,0 +1,237 @@
[
{
"name": "composer/ca-bundle",
"version": "1.1.4",
"version_normalized": "1.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
"reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0"
},
"time": "2019-01-28T09:30:10+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
]
},
{
"name": "geoip2/geoip2",
"version": "v2.9.0",
"version_normalized": "2.9.0.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/GeoIP2-php.git",
"reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/a807fbf65212eef5d8d2db1a1b31082b53633d77",
"reference": "a807fbf65212eef5d8d2db1a1b31082b53633d77",
"shasum": ""
},
"require": {
"maxmind-db/reader": "~1.0",
"maxmind/web-service-common": "~0.5",
"php": ">=5.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.*",
"squizlabs/php_codesniffer": "3.*"
},
"time": "2018-04-10T15:32:59+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"GeoIp2\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "http://www.maxmind.com/"
}
],
"description": "MaxMind GeoIP2 PHP API",
"homepage": "https://github.com/maxmind/GeoIP2-php",
"keywords": [
"IP",
"geoip",
"geoip2",
"geolocation",
"maxmind"
]
},
{
"name": "maxmind-db/reader",
"version": "v1.4.1",
"version_normalized": "1.4.1.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
"reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/eb83d0ee1c1f9b8a340206302136bc81ee02ae74",
"reference": "eb83d0ee1c1f9b8a340206302136bc81ee02ae74",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.* || 5.*",
"satooshi/php-coveralls": "1.0.*",
"squizlabs/php_codesniffer": "3.*"
},
"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"
},
"time": "2019-01-04T19:55:56+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"MaxMind\\Db\\": "src/MaxMind/Db"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "http://www.maxmind.com/"
}
],
"description": "MaxMind DB Reader API",
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
"keywords": [
"database",
"geoip",
"geoip2",
"geolocation",
"maxmind"
]
},
{
"name": "maxmind/web-service-common",
"version": "v0.5.0",
"version_normalized": "0.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/web-service-common-php.git",
"reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/61a9836fa3bb1743ab89752bae5005d71e78c73b",
"reference": "61a9836fa3bb1743ab89752bae5005d71e78c73b",
"shasum": ""
},
"require": {
"composer/ca-bundle": "^1.0.3",
"ext-curl": "*",
"ext-json": "*",
"php": ">=5.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "2.*",
"phpunit/phpunit": "4.*",
"squizlabs/php_codesniffer": "3.*"
},
"time": "2018-02-12T22:31:54+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"MaxMind\\Exception\\": "src/Exception",
"MaxMind\\WebService\\": "src/WebService"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Gregory Oschwald",
"email": "goschwald@maxmind.com"
}
],
"description": "Internal MaxMind Web Service API",
"homepage": "https://github.com/maxmind/web-service-common-php"
},
{
"name": "wordfence/wf-waf",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/wordfence/wf-waf.git",
"reference": "origin/master"
},
"dist": {
"type": "zip",
"url": "https://github.com/wordfence/wf-waf/zipball/master",
"reference": "origin/master"
},
"type": "library",
"installation-source": "source"
}
]

View File

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

View File

@@ -0,0 +1,35 @@
<?php
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true,
'concat_space' => [ 'spacing' => 'one'],
'heredoc_to_nowdoc' => true,
'increment_style' => false,
'list_syntax' => ['syntax' => 'long'],
'no_extra_consecutive_blank_lines' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block'],
'no_short_echo_tag' => true,
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_imports' => true,
'pre_increment' => false,
'php_unit_strict' => true,
'php_unit_test_class_requires_covers' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_no_alias_tag' => false,
'phpdoc_order' => true,
'semicolon_after_instruction' => true,
'strict_comparison' => true,
'strict_param' => true,
'yoda_style' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__)
)
;

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.

View File

@@ -0,0 +1,283 @@
<?php
namespace GeoIp2\Database;
use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\ProviderInterface;
use MaxMind\Db\Reader as DbReader;
use MaxMind\Db\Reader\InvalidDatabaseException;
/**
* Instances of this class provide a reader for the GeoIP2 database format.
* IP addresses can be looked up using the database specific methods.
*
* ## Usage ##
*
* The basic API for this class is the same for every database. First, you
* create a reader object, specifying a file name. You then call the method
* corresponding to the specific database, passing it the IP address you want
* to look up.
*
* If the request succeeds, the method call will return a model class for
* the method you called. This model in turn contains multiple record classes,
* each of which represents part of the data returned by the database. If
* the database does not contain the requested information, the attributes
* on the record class will have a `null` value.
*
* If the address is not in the database, an
* {@link \GeoIp2\Exception\AddressNotFoundException} exception will be
* thrown. If an invalid IP address is passed to one of the methods, a
* SPL {@link \InvalidArgumentException} will be thrown. If the database is
* corrupt or invalid, a {@link \MaxMind\Db\Reader\InvalidDatabaseException}
* will be thrown.
*/
class Reader implements ProviderInterface
{
private $dbReader;
private $locales;
/**
* Constructor.
*
* @param string $filename the path to the GeoIP2 database file
* @param array $locales list of locale codes to use in name property
* from most preferred to least preferred
*
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function __construct(
$filename,
$locales = ['en']
) {
$this->dbReader = new DbReader($filename);
$this->locales = $locales;
}
/**
* This method returns a GeoIP2 City model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\City
*/
public function city($ipAddress)
{
return $this->modelFor('City', 'City', $ipAddress);
}
/**
* This method returns a GeoIP2 Country model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\Country
*/
public function country($ipAddress)
{
return $this->modelFor('Country', 'Country', $ipAddress);
}
/**
* This method returns a GeoIP2 Anonymous IP model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\AnonymousIp
*/
public function anonymousIp($ipAddress)
{
return $this->flatModelFor(
'AnonymousIp',
'GeoIP2-Anonymous-IP',
$ipAddress
);
}
/**
* This method returns a GeoLite2 ASN model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\Asn
*/
public function asn($ipAddress)
{
return $this->flatModelFor(
'Asn',
'GeoLite2-ASN',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Connection Type model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\ConnectionType
*/
public function connectionType($ipAddress)
{
return $this->flatModelFor(
'ConnectionType',
'GeoIP2-Connection-Type',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Domain model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\Domain
*/
public function domain($ipAddress)
{
return $this->flatModelFor(
'Domain',
'GeoIP2-Domain',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Enterprise model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\Enterprise
*/
public function enterprise($ipAddress)
{
return $this->modelFor('Enterprise', 'Enterprise', $ipAddress);
}
/**
* This method returns a GeoIP2 ISP model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*
* @return \GeoIp2\Model\Isp
*/
public function isp($ipAddress)
{
return $this->flatModelFor(
'Isp',
'GeoIP2-ISP',
$ipAddress
);
}
private function modelFor($class, $type, $ipAddress)
{
$record = $this->getRecord($class, $type, $ipAddress);
$record['traits']['ip_address'] = $ipAddress;
$class = 'GeoIp2\\Model\\' . $class;
return new $class($record, $this->locales);
}
private function flatModelFor($class, $type, $ipAddress)
{
$record = $this->getRecord($class, $type, $ipAddress);
$record['ip_address'] = $ipAddress;
$class = 'GeoIp2\\Model\\' . $class;
return new $class($record);
}
private function getRecord($class, $type, $ipAddress)
{
if (strpos($this->metadata()->databaseType, $type) === false) {
$method = lcfirst($class);
throw new \BadMethodCallException(
"The $method method cannot be used to open a "
. $this->metadata()->databaseType . ' database'
);
}
$record = $this->dbReader->get($ipAddress);
if ($record === null) {
throw new AddressNotFoundException(
"The address $ipAddress is not in the database."
);
}
if (!is_array($record)) {
// This can happen on corrupt databases. Generally,
// MaxMind\Db\Reader will throw a
// MaxMind\Db\Reader\InvalidDatabaseException, but occasionally
// the lookup may result in a record that looks valid but is not
// an array. This mostly happens when the user is ignoring all
// exceptions and the more frequent InvalidDatabaseException
// exceptions go unnoticed.
throw new InvalidDatabaseException(
"Expected an array when looking up $ipAddress but received: "
. gettype($record)
);
}
return $record;
}
/**
* @throws \InvalidArgumentException if arguments are passed to the method
* @throws \BadMethodCallException if the database has been closed
*
* @return \MaxMind\Db\Reader\Metadata object for the database
*/
public function metadata()
{
return $this->dbReader->metadata();
}
/**
* Closes the GeoIP2 database and returns the resources to the system.
*/
public function close()
{
$this->dbReader->close();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class AddressNotFoundException extends GeoIp2Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class AuthenticationException extends GeoIp2Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class GeoIp2Exception extends \Exception
{
}

View File

@@ -0,0 +1,24 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents an HTTP transport error.
*/
class HttpException extends GeoIp2Exception
{
/**
* The URI queried.
*/
public $uri;
public function __construct(
$message,
$httpStatus,
$uri,
\Exception $previous = null
) {
$this->uri = $uri;
parent::__construct($message, $httpStatus, $previous);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents an error returned by MaxMind's GeoIP2
* web service.
*/
class InvalidRequestException extends HttpException
{
/**
* The code returned by the MaxMind web service.
*/
public $error;
public function __construct(
$message,
$error,
$httpStatus,
$uri,
\Exception $previous = null
) {
$this->error = $error;
parent::__construct($message, $httpStatus, $uri, $previous);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class OutOfQueriesException extends GeoIp2Exception
{
}

View File

@@ -0,0 +1,67 @@
<?php
namespace GeoIp2\Model;
/**
* @ignore
*/
abstract class AbstractModel implements \JsonSerializable
{
protected $raw;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
$this->raw = $raw;
}
/**
* @ignore
*
* @param mixed $field
*/
protected function get($field)
{
if (isset($this->raw[$field])) {
return $this->raw[$field];
}
if (preg_match('/^is_/', $field)) {
return false;
}
return null;
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __get($attr)
{
if ($attr !== 'instance' && property_exists($this, $attr)) {
return $this->$attr;
}
throw new \RuntimeException("Unknown attribute: $attr");
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __isset($attr)
{
return $attr !== 'instance' && isset($this->$attr);
}
public function jsonSerialize()
{
return $this->raw;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace GeoIp2\Model;
/**
* This class provides the GeoIP2 Anonymous IP model.
*
* @property-read bool $isAnonymous This is true if the IP address belongs to
* any sort of anonymous network.
* @property-read bool $isAnonymousVpn This is true if the IP address belongs to
* an anonymous VPN system.
* @property-read bool $isHostingProvider This is true if the IP address belongs
* to a hosting provider.
* @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy.
* @property-read bool $isTorExitNode This is true if the IP address is a Tor
* exit node.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
*/
class AnonymousIp extends AbstractModel
{
protected $isAnonymous;
protected $isAnonymousVpn;
protected $isHostingProvider;
protected $isPublicProxy;
protected $isTorExitNode;
protected $ipAddress;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
parent::__construct($raw);
$this->isAnonymous = $this->get('is_anonymous');
$this->isAnonymousVpn = $this->get('is_anonymous_vpn');
$this->isHostingProvider = $this->get('is_hosting_provider');
$this->isPublicProxy = $this->get('is_public_proxy');
$this->isTorExitNode = $this->get('is_tor_exit_node');
$this->ipAddress = $this->get('ip_address');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace GeoIp2\Model;
/**
* This class provides the GeoLite2 ASN model.
*
* @property-read int|null $autonomousSystemNumber The autonomous system number
* associated with the IP address.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered autonomous system number for the IP
* address.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
*/
class Asn extends AbstractModel
{
protected $autonomousSystemNumber;
protected $autonomousSystemOrganization;
protected $ipAddress;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number');
$this->autonomousSystemOrganization =
$this->get('autonomous_system_organization');
$this->ipAddress = $this->get('ip_address');
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 City web service and database.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* http://dev.maxmind.com/geoip/geoip2/web-services more details.
*
* @property-read \GeoIp2\Record\City $city City data for the requested IP
* address.
* @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address.
* @property-read \GeoIp2\Record\Country $country Country data for the requested
* IP address. This object represents the country where MaxMind believes the
* end user is located.
* @property-read \GeoIp2\Record\Location $location Location data for the
* requested IP address.
* @property-read \GeoIp2\Record\Postal $postal Postal data for the
* requested IP address.
* @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind
* account.
* @property-read \GeoIp2\Record\Country $registeredCountry Registered country
* data for the requested IP address. This record represents the country
* where the ISP has registered a given IP block and may differ from the
* user's country.
* @property-read \GeoIp2\Record\RepresentedCountry $representedCountry
* Represented country data for the requested IP address. The represented
* country is used for things like military bases. It is only present when
* the represented country differs from the country.
* @property-read array $subdivisions An array of {@link \GeoIp2\Record\Subdivision}
* objects representing the country subdivisions for the requested IP
* address. The number and type of subdivisions varies by country, but a
* subdivision is typically a state, province, county, etc. Subdivisions
* are ordered from most general (largest) to most specific (smallest).
* If the response did not contain any subdivisions, this method returns
* an empty array.
* @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object
* representing the most specific subdivision returned. If the response
* did not contain any subdivisions, this method returns an empty
* {@link \GeoIp2\Record\Subdivision} object.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address.
*/
class City extends Country
{
/**
* @ignore
*/
protected $city;
/**
* @ignore
*/
protected $location;
/**
* @ignore
*/
protected $postal;
/**
* @ignore
*/
protected $subdivisions = [];
/**
* @ignore
*
* @param mixed $raw
* @param mixed $locales
*/
public function __construct($raw, $locales = ['en'])
{
parent::__construct($raw, $locales);
$this->city = new \GeoIp2\Record\City($this->get('city'), $locales);
$this->location = new \GeoIp2\Record\Location($this->get('location'));
$this->postal = new \GeoIp2\Record\Postal($this->get('postal'));
$this->createSubdivisions($raw, $locales);
}
private function createSubdivisions($raw, $locales)
{
if (!isset($raw['subdivisions'])) {
return;
}
foreach ($raw['subdivisions'] as $sub) {
array_push(
$this->subdivisions,
new \GeoIp2\Record\Subdivision($sub, $locales)
);
}
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __get($attr)
{
if ($attr === 'mostSpecificSubdivision') {
return $this->$attr();
}
return parent::__get($attr);
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __isset($attr)
{
if ($attr === 'mostSpecificSubdivision') {
// We always return a mostSpecificSubdivision, even if it is the
// empty subdivision
return true;
}
return parent::__isset($attr);
}
private function mostSpecificSubdivision()
{
return empty($this->subdivisions) ?
new \GeoIp2\Record\Subdivision([], $this->locales) :
end($this->subdivisions);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace GeoIp2\Model;
/**
* This class provides the GeoIP2 Connection-Type model.
*
* @property-read string|null $connectionType The connection type may take the
* following values: "Dialup", "Cable/DSL", "Corporate", "Cellular".
* Additional values may be added in the future.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
*/
class ConnectionType extends AbstractModel
{
protected $connectionType;
protected $ipAddress;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
parent::__construct($raw);
$this->connectionType = $this->get('connection_type');
$this->ipAddress = $this->get('ip_address');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Country web service and database.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* http://dev.maxmind.com/geoip/geoip2/web-services more details.
*
* @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address.
* @property-read \GeoIp2\Record\Country $country Country data for the requested
* IP address. This object represents the country where MaxMind believes the
* end user is located.
* @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind
* account.
* @property-read \GeoIp2\Record\Country $registeredCountry Registered country
* data for the requested IP address. This record represents the country
* where the ISP has registered a given IP block and may differ from the
* user's country.
* @property-read \GeoIp2\Record\RepresentedCountry $representedCountry
* Represented country data for the requested IP address. The represented
* country is used for things like military bases. It is only present when
* the represented country differs from the country.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address.
*/
class Country extends AbstractModel
{
protected $continent;
protected $country;
protected $locales;
protected $maxmind;
protected $registeredCountry;
protected $representedCountry;
protected $traits;
/**
* @ignore
*
* @param mixed $raw
* @param mixed $locales
*/
public function __construct($raw, $locales = ['en'])
{
parent::__construct($raw);
$this->continent = new \GeoIp2\Record\Continent(
$this->get('continent'),
$locales
);
$this->country = new \GeoIp2\Record\Country(
$this->get('country'),
$locales
);
$this->maxmind = new \GeoIp2\Record\MaxMind($this->get('maxmind'));
$this->registeredCountry = new \GeoIp2\Record\Country(
$this->get('registered_country'),
$locales
);
$this->representedCountry = new \GeoIp2\Record\RepresentedCountry(
$this->get('represented_country'),
$locales
);
$this->traits = new \GeoIp2\Record\Traits($this->get('traits'));
$this->locales = $locales;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace GeoIp2\Model;
/**
* This class provides the GeoIP2 Domain model.
*
* @property-read string|null $domain The second level domain associated with the
* IP address. This will be something like "example.com" or
* "example.co.uk", not "foo.example.com".
* @property-read string $ipAddress The IP address that the data in the model is
* for.
*/
class Domain extends AbstractModel
{
protected $domain;
protected $ipAddress;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
parent::__construct($raw);
$this->domain = $this->get('domain');
$this->ipAddress = $this->get('ip_address');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Enterprise database lookups.
*
* The only difference between the City and Enterprise model classes is which
* fields in each record may be populated. See
* http://dev.maxmind.com/geoip/geoip2/web-services more details.
*
* @property-read \GeoIp2\Record\City $city City data for the requested IP
* address.
* @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address.
* @property-read \GeoIp2\Record\Country $country Country data for the requested
* IP address. This object represents the country where MaxMind believes the
* end user is located.
* @property-read \GeoIp2\Record\Location $location Location data for the
* requested IP address.
* @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind
* account.
* @property-read \GeoIp2\Record\Country $registeredCountry Registered country
* data for the requested IP address. This record represents the country
* where the ISP has registered a given IP block and may differ from the
* user's country.
* @property-read \GeoIp2\Record\RepresentedCountry $representedCountry
* Represented country data for the requested IP address. The represented
* country is used for things like military bases. It is only present when
* the represented country differs from the country.
* @property-read array $subdivisions An array of {@link \GeoIp2\Record\Subdivision}
* objects representing the country subdivisions for the requested IP
* address. The number and type of subdivisions varies by country, but a
* subdivision is typically a state, province, county, etc. Subdivisions
* are ordered from most general (largest) to most specific (smallest).
* If the response did not contain any subdivisions, this method returns
* an empty array.
* @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object
* representing the most specific subdivision returned. If the response
* did not contain any subdivisions, this method returns an empty
* {@link \GeoIp2\Record\Subdivision} object.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address.
*/
class Enterprise extends City
{
}

View File

@@ -0,0 +1,47 @@
<?php
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Precision: Insights web service.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* http://dev.maxmind.com/geoip/geoip2/web-services more details.
*
* @property-read \GeoIp2\Record\City $city City data for the requested IP
* address.
* @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address.
* @property-read \GeoIp2\Record\Country $country Country data for the requested
* IP address. This object represents the country where MaxMind believes the
* end user is located.
* @property-read \GeoIp2\Record\Location $location Location data for the
* requested IP address.
* @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind
* account.
* @property-read \GeoIp2\Record\Country $registeredCountry Registered country
* data for the requested IP address. This record represents the country
* where the ISP has registered a given IP block and may differ from the
* user's country.
* @property-read \GeoIp2\Record\RepresentedCountry $representedCountry
* Represented country data for the requested IP address. The represented
* country is used for things like military bases. It is only present when
* the represented country differs from the country.
* @property-read array $subdivisions An array of {@link \GeoIp2\Record\Subdivision}
* objects representing the country subdivisions for the requested IP
* address. The number and type of subdivisions varies by country, but a
* subdivision is typically a state, province, county, etc. Subdivisions
* are ordered from most general (largest) to most specific (smallest).
* If the response did not contain any subdivisions, this method returns
* an empty array.
* @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object
* representing the most specific subdivision returned. If the response
* did not contain any subdivisions, this method returns an empty
* {@link \GeoIp2\Record\Subdivision} object.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address.
*/
class Insights extends City
{
}

View File

@@ -0,0 +1,44 @@
<?php
namespace GeoIp2\Model;
/**
* This class provides the GeoIP2 ISP model.
*
* @property-read int|null $autonomousSystemNumber The autonomous system number
* associated with the IP address.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered autonomous system number for the IP
* address.
* @property-read string|null $isp The name of the ISP associated with the IP
* address.
* @property-read string|null $organization The name of the organization associated
* with the IP address.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
*/
class Isp extends AbstractModel
{
protected $autonomousSystemNumber;
protected $autonomousSystemOrganization;
protected $isp;
protected $organization;
protected $ipAddress;
/**
* @ignore
*
* @param mixed $raw
*/
public function __construct($raw)
{
parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number');
$this->autonomousSystemOrganization =
$this->get('autonomous_system_organization');
$this->isp = $this->get('isp');
$this->organization = $this->get('organization');
$this->ipAddress = $this->get('ip_address');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace GeoIp2;
interface ProviderInterface
{
/**
* @param string $ipAddress an IPv4 or IPv6 address to lookup
*
* @return \GeoIp2\Model\Country a Country model for the requested IP address
*/
public function country($ipAddress);
/**
* @param string $ipAddress an IPv4 or IPv6 address to lookup
*
* @return \GeoIp2\Model\City a City model for the requested IP address
*/
public function city($ipAddress);
}

View File

@@ -0,0 +1,66 @@
<?php
namespace GeoIp2\Record;
abstract class AbstractPlaceRecord extends AbstractRecord
{
private $locales;
/**
* @ignore
*
* @param mixed $record
* @param mixed $locales
*/
public function __construct($record, $locales = ['en'])
{
$this->locales = $locales;
parent::__construct($record);
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __get($attr)
{
if ($attr === 'name') {
return $this->name();
}
return parent::__get($attr);
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __isset($attr)
{
if ($attr === 'name') {
return $this->firstSetNameLocale() === null ? false : true;
}
return parent::__isset($attr);
}
private function name()
{
$locale = $this->firstSetNameLocale();
return $locale === null ? null : $this->names[$locale];
}
private function firstSetNameLocale()
{
foreach ($this->locales as $locale) {
if (isset($this->names[$locale])) {
return $locale;
}
}
return null;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace GeoIp2\Record;
abstract class AbstractRecord implements \JsonSerializable
{
private $record;
/**
* @ignore
*
* @param mixed $record
*/
public function __construct($record)
{
$this->record = isset($record) ? $record : [];
}
/**
* @ignore
*
* @param mixed $attr
*/
public function __get($attr)
{
// XXX - kind of ugly but greatly reduces boilerplate code
$key = $this->attributeToKey($attr);
if ($this->__isset($attr)) {
return $this->record[$key];
} elseif ($this->validAttribute($attr)) {
if (preg_match('/^is_/', $key)) {
return false;
}
return null;
}
throw new \RuntimeException("Unknown attribute: $attr");
}
public function __isset($attr)
{
return $this->validAttribute($attr) &&
isset($this->record[$this->attributeToKey($attr)]);
}
private function attributeToKey($attr)
{
return strtolower(preg_replace('/([A-Z])/', '_\1', $attr));
}
private function validAttribute($attr)
{
return in_array($attr, $this->validAttributes, true);
}
public function jsonSerialize()
{
return $this->record;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace GeoIp2\Record;
/**
* City-level data associated with an IP address.
*
* This record is returned by all location services and databases besides
* Country.
*
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the city is correct. This attribute is only available
* from the Insights service and the GeoIP2 Enterprise database.
* @property-read int|null $geonameId The GeoName ID for the city. This attribute
* is returned by all location services and databases.
* @property-read string|null $name The name of the city based on the locales list
* passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names A array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class City extends AbstractPlaceRecord
{
/**
* @ignore
*/
protected $validAttributes = ['confidence', 'geonameId', 'names'];
}

View File

@@ -0,0 +1,32 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the continent record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read string|null $code A two character continent code like "NA" (North
* America) or "OC" (Oceania). This attribute is returned by all location
* services and databases.
* @property-read int|null $geonameId The GeoName ID for the continent. This
* attribute is returned by all location services and databases.
* @property-read string|null $name Returns the name of the continent based on the
* locales list passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class Continent extends AbstractPlaceRecord
{
/**
* @ignore
*/
protected $validAttributes = [
'code',
'geonameId',
'names',
];
}

View File

@@ -0,0 +1,41 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the country record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the country is correct. This attribute is only available
* from the Insights service and the GeoIP2 Enterprise database.
* @property-read int|null $geonameId The GeoName ID for the country. This
* attribute is returned by all location services and databases.
* @property-read bool $isInEuropeanUnion This is true if the country is a
* member state of the European Union. This attribute is returned by all
* location services and databases.
* @property-read string|null $isoCode The
* {@link * http://en.wikipedia.org/wiki/ISO_3166-1 two-character ISO 3166-1 alpha
* code} for the country. This attribute is returned by all location services
* and databases.
* @property-read string|null $name The name of the country based on the locales
* list passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class Country extends AbstractPlaceRecord
{
/**
* @ignore
*/
protected $validAttributes = [
'confidence',
'geonameId',
'isInEuropeanUnion',
'isoCode',
'names',
];
}

View File

@@ -0,0 +1,52 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the location record associated with an IP address.
*
* This record is returned by all location services and databases besides
* Country.
*
* @property-read int|null $averageIncome The average income in US dollars
* associated with the requested IP address. This attribute is only available
* from the Insights service.
* @property-read int|null $accuracyRadius The approximate accuracy radius in
* kilometers around the latitude and longitude for the IP address. This is
* the radius where we have a 67% confidence that the device using the IP
* address resides within the circle centered at the latitude and longitude
* with the provided radius.
* @property-read float|null $latitude The approximate latitude of the location
* associated with the IP address. This value is not precise and should not be
* used to identify a particular address or household.
* @property-read float|null $longitude The approximate longitude of the location
* associated with the IP address. This value is not precise and should not be
* used to identify a particular address or household.
* @property-read int|null $populationDensity The estimated population per square
* kilometer associated with the IP address. This attribute is only available
* from the Insights service.
* @property-read int|null $metroCode The metro code of the location if the location
* is in the US. MaxMind returns the same metro codes as the
* {@link * https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions
* Google AdWords API}.
* @property-read string|null $timeZone The time zone associated with location, as
* specified by the {@link http://www.iana.org/time-zones IANA Time Zone
* Database}, e.g., "America/New_York".
*/
class Location extends AbstractRecord
{
/**
* @ignore
*/
protected $validAttributes = [
'averageIncome',
'accuracyRadius',
'latitude',
'longitude',
'metroCode',
'populationDensity',
'postalCode',
'postalConfidence',
'timeZone',
];
}

View File

@@ -0,0 +1,19 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data about your account.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $queriesRemaining The number of remaining queries you
* have for the service you are calling.
*/
class MaxMind extends AbstractRecord
{
/**
* @ignore
*/
protected $validAttributes = ['queriesRemaining'];
}

View File

@@ -0,0 +1,26 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the postal record associated with an IP address.
*
* This record is returned by all location databases and services besides
* Country.
*
* @property-read string|null $code The postal code of the location. Postal codes
* are not available for all countries. In some countries, this will only
* contain part of the postal code. This attribute is returned by all location
* databases and services besides Country.
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the postal code is correct. This attribute is only
* available from the Insights service and the GeoIP2 Enterprise
* database.
*/
class Postal extends AbstractRecord
{
/**
* @ignore
*/
protected $validAttributes = ['code', 'confidence'];
}

View File

@@ -0,0 +1,39 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the represented country associated with an IP address.
*
* This class contains the country-level data associated with an IP address
* for the IP's represented country. The represented country is the country
* represented by something like a military base.
*
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the country is correct. This attribute is only available
* from the Insights service and the GeoIP2 Enterprise database.
* @property-read int|null $geonameId The GeoName ID for the country.
* @property-read bool $isInEuropeanUnion This is true if the country is a
* member state of the European Union. This attribute is returned by all
* location services and databases.
* @property-read string|null $isoCode The {@link http://en.wikipedia.org/wiki/ISO_3166-1
* two-character ISO 3166-1 alpha code} for the country.
* @property-read string|null $name The name of the country based on the locales list
* passed to the constructor.
* @property-read array|null $names An array map where the keys are locale codes and
* the values are names.
* @property-read string|null $type A string indicating the type of entity that is
* representing the country. Currently we only return <code>military</code>
* but this could expand to include other types in the future.
*/
class RepresentedCountry extends Country
{
protected $validAttributes = [
'confidence',
'geonameId',
'isInEuropeanUnion',
'isoCode',
'names',
'type',
];
}

View File

@@ -0,0 +1,40 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the subdivisions associated with an IP address.
*
* This record is returned by all location databases and services besides
* Country.
*
* @property-read int|null $confidence This is a value from 0-100 indicating
* MaxMind's confidence that the subdivision is correct. This attribute is
* only available from the Insights service and the GeoIP2 Enterprise
* database.
* @property-read int|null $geonameId This is a GeoName ID for the subdivision.
* This attribute is returned by all location databases and services besides
* Country.
* @property-read string|null $isoCode This is a string up to three characters long
* contain the subdivision portion of the
* {@link * http://en.wikipedia.org/wiki/ISO_3166-2 ISO 3166-2 code}. This attribute
* is returned by all location databases and services except Country.
* @property-read string|null $name The name of the subdivision based on the
* locales list passed to the constructor. This attribute is returned by all
* location databases and services besides Country.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* databases and services besides Country.
*/
class Subdivision extends AbstractPlaceRecord
{
/**
* @ignore
*/
protected $validAttributes = [
'confidence',
'geonameId',
'isoCode',
'names',
];
}

View File

@@ -0,0 +1,114 @@
<?php
namespace GeoIp2\Record;
/**
* Contains data for the traits record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $autonomousSystemNumber The
* {@link * http://en.wikipedia.org/wiki/Autonomous_system_(Internet) autonomous
* system number} associated with the IP address. This attribute is only
* available from the City and Insights web service and the GeoIP2
* Enterprise database.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered {@link * http://en.wikipedia.org/wiki/Autonomous_system_(Internet) autonomous
* system number} for the IP address. This attribute is only available from
* the City and Insights web service and the GeoIP2 Enterprise
* database.
* @property-read string|null $connectionType The connection type may take the
* following values: "Dialup", "Cable/DSL", "Corporate", "Cellular".
* Additional values may be added in the future. This attribute is only
* available in the GeoIP2 Enterprise database.
* @property-read string|null $domain The second level domain associated with the
* IP address. This will be something like "example.com" or "example.co.uk",
* not "foo.example.com". This attribute is only available from the
* City and Insights web service and the GeoIP2 Enterprise
* database.
* @property-read string $ipAddress The IP address that the data in the model
* is for. If you performed a "me" lookup against the web service, this
* will be the externally routable IP address for the system the code is
* running on. If the system is behind a NAT, this may differ from the IP
* address locally assigned to it. This attribute is returned by all end
* points.
* @property-read bool $isAnonymous This is true if the IP address belongs to
* any sort of anonymous network. This property is only available from GeoIP2
* Precision Insights.
* @property-read bool $isAnonymousProxy *Deprecated.* Please see our
* {@link * https://www.maxmind.com/en/geoip2-anonymous-ip-database GeoIP2
* Anonymous IP database} to determine whether the IP address is used by an
* anonymizing service.
* @property-read bool $isAnonymousVpn This is true if the IP address belongs to
* an anonymous VPN system. This property is only available from GeoIP2
* Precision Insights.
* @property-read bool $isHostingProvider This is true if the IP address belongs
* to a hosting provider. This property is only available from GeoIP2
* Precision Insights.
* @property-read bool $isLegitimateProxy This attribute is true if MaxMind
* believes this IP address to be a legitimate proxy, such as an internal
* VPN used by a corporation. This attribute is only available in the GeoIP2
* Enterprise database.
* @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy. This property is only available from GeoIP2 Precision
* Insights.
* @property-read bool $isSatelliteProvider *Deprecated.* Due to the
* increased coverage by mobile carriers, very few satellite providers now
* serve multiple countries. As a result, the output does not provide
* sufficiently relevant data for us to maintain it.
* @property-read bool $isTorExitNode This is true if the IP address is a Tor
* exit node. This property is only available from GeoIP2 Precision Insights.
* @property-read string|null $isp The name of the ISP associated with the IP
* address. This attribute is only available from the City and Insights web
* services and the GeoIP2 Enterprise database.
* @property-read string|null $organization The name of the organization associated
* with the IP address. This attribute is only available from the City and
* Insights web services and the GeoIP2 Enterprise database.
* @property-read string|null $userType <p>The user type associated with the IP
* address. This can be one of the following values:</p>
* <ul>
* <li>business
* <li>cafe
* <li>cellular
* <li>college
* <li>content_delivery_network
* <li>dialup
* <li>government
* <li>hosting
* <li>library
* <li>military
* <li>residential
* <li>router
* <li>school
* <li>search_engine_spider
* <li>traveler
* </ul>
* <p>
* This attribute is only available from the Insights web service and the
* GeoIP2 Enterprise database.
* </p>
*/
class Traits extends AbstractRecord
{
/**
* @ignore
*/
protected $validAttributes = [
'autonomousSystemNumber',
'autonomousSystemOrganization',
'connectionType',
'domain',
'ipAddress',
'isAnonymous',
'isAnonymousProxy',
'isAnonymousVpn',
'isHostingProvider',
'isLegitimateProxy',
'isp',
'isPublicProxy',
'isSatelliteProvider',
'isTorExitNode',
'organization',
'userType',
];
}

View File

@@ -0,0 +1,239 @@
<?php
namespace GeoIp2\WebService;
use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\Exception\AuthenticationException;
use GeoIp2\Exception\GeoIp2Exception;
use GeoIp2\Exception\HttpException;
use GeoIp2\Exception\InvalidRequestException;
use GeoIp2\Exception\OutOfQueriesException;
use GeoIp2\ProviderInterface;
use MaxMind\WebService\Client as WsClient;
/**
* This class provides a client API for all the GeoIP2 Precision web services.
* The services are Country, City, and Insights. Each service returns a
* different set of data about an IP address, with Country returning the
* least data and Insights the most.
*
* Each web service is represented by a different model class, and these model
* classes in turn contain multiple record classes. The record classes have
* attributes which contain data about the IP address.
*
* If the web service does not return a particular piece of data for an IP
* address, the associated attribute is not populated.
*
* The web service may not return any information for an entire record, in
* which case all of the attributes for that record class will be empty.
*
* ## Usage ##
*
* The basic API for this class is the same for all of the web service end
* points. First you create a web service object with your MaxMind `$accountId`
* and `$licenseKey`, then you call the method corresponding to a specific end
* point, passing it the IP address you want to look up.
*
* If the request succeeds, the method call will return a model class for
* the service you called. This model in turn contains multiple record
* classes, each of which represents part of the data returned by the web
* service.
*
* If the request fails, the client class throws an exception.
*/
class Client implements ProviderInterface
{
private $locales;
private $client;
private static $basePath = '/geoip/v2.1';
const VERSION = 'v2.9.0';
/**
* Constructor.
*
* @param int $accountId your MaxMind account ID
* @param string $licenseKey your MaxMind license key
* @param array $locales list of locale codes to use in name property
* from most preferred to least preferred
* @param array $options array of options. Valid options include:
* * `host` - The host to use when querying the web service.
* * `timeout` - Timeout in seconds.
* * `connectTimeout` - Initial connection timeout in seconds.
* * `proxy` - The HTTP proxy to use. May include a schema, port,
* username, and password, e.g.,
* `http://username:password@127.0.0.1:10`.
*/
public function __construct(
$accountId,
$licenseKey,
$locales = ['en'],
$options = []
) {
$this->locales = $locales;
// This is for backwards compatibility. Do not remove except for a
// major version bump.
if (is_string($options)) {
$options = ['host' => $options];
}
if (!isset($options['host'])) {
$options['host'] = 'geoip.maxmind.com';
}
$options['userAgent'] = $this->userAgent();
$this->client = new WsClient($accountId, $licenseKey, $options);
}
private function userAgent()
{
return 'GeoIP2-API/' . self::VERSION;
}
/**
* This method calls the GeoIP2 Precision: City service.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you
* provided is not in our database (e.g., a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out
* of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an issue
* with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between
* your server and the web service or that the web service
* returned an invalid document or 500 error code.
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid.
*
* @return \GeoIp2\Model\City
*/
public function city($ipAddress = 'me')
{
return $this->responseFor('city', 'City', $ipAddress);
}
/**
* This method calls the GeoIP2 Precision: Country service.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you provided is not in our database (e.g.,
* a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error
* code or message was returned. This could indicate a problem
* with the connection between your server and the web service
* or that the web service returned an invalid document or 500
* error code.
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It
* will be thrown directly if a 200 status code is returned but
* the body is invalid.
*
* @return \GeoIp2\Model\Country
*/
public function country($ipAddress = 'me')
{
return $this->responseFor('country', 'Country', $ipAddress);
}
/**
* This method calls the GeoIP2 Precision: Insights service.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you
* provided is not in our database (e.g., a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out
* of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between
* your server and the web service or that the web service
* returned an invalid document or 500 error code.
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid.
*
* @return \GeoIp2\Model\Insights
*/
public function insights($ipAddress = 'me')
{
return $this->responseFor('insights', 'Insights', $ipAddress);
}
private function responseFor($endpoint, $class, $ipAddress)
{
$path = implode('/', [self::$basePath, $endpoint, $ipAddress]);
try {
$body = $this->client->get('GeoIP2 ' . $class, $path);
} catch (\MaxMind\Exception\IpAddressNotFoundException $ex) {
throw new AddressNotFoundException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\AuthenticationException $ex) {
throw new AuthenticationException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\InsufficientFundsException $ex) {
throw new OutOfQueriesException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\InvalidRequestException $ex) {
throw new InvalidRequestException(
$ex->getMessage(),
$ex->getErrorCode(),
$ex->getStatusCode(),
$ex->getUri(),
$ex
);
} catch (\MaxMind\Exception\HttpException $ex) {
throw new HttpException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex->getUri(),
$ex
);
} catch (\MaxMind\Exception\WebServiceException $ex) {
throw new GeoIp2Exception(
$ex->getMessage(),
$ex->getCode(),
$ex
);
}
$class = 'GeoIp2\\Model\\' . $class;
return new $class($body, $this->locales);
}
}

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.

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

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.

View File

@@ -0,0 +1,10 @@
<?php
namespace MaxMind\Exception;
/**
* This class represents an error authenticating.
*/
class AuthenticationException extends InvalidRequestException
{
}

View File

@@ -0,0 +1,40 @@
<?php
namespace MaxMind\Exception;
/**
* This class represents an HTTP transport error.
*/
class HttpException extends WebServiceException
{
/**
* The URI queried.
*/
private $uri;
/**
* @param string $message a message describing the error
* @param int $httpStatus the HTTP status code of the response
* @param string $uri the URI used in the request
* @param \Exception $previous the previous exception, if any
*/
public function __construct(
$message,
$httpStatus,
$uri,
\Exception $previous = null
) {
$this->uri = $uri;
parent::__construct($message, $httpStatus, $previous);
}
public function getUri()
{
return $this->uri;
}
public function getStatusCode()
{
return $this->getCode();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace MaxMind\Exception;
/**
* Thrown when the account is out of credits.
*/
class InsufficientFundsException extends InvalidRequestException
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace MaxMind\Exception;
/**
* This class represents an error in creating the request to be sent to the
* web service. For example, if the array cannot be encoded as JSON or if there
* is a missing or invalid field.
*/
class InvalidInputException extends WebServiceException
{
}

View File

@@ -0,0 +1,37 @@
<?php
namespace MaxMind\Exception;
/**
* Thrown when a MaxMind web service returns an error relating to the request.
*/
class InvalidRequestException extends HttpException
{
/**
* The code returned by the MaxMind web service.
*/
private $error;
/**
* @param string $message the exception message
* @param int $error the error code returned by the MaxMind web service
* @param int $httpStatus the HTTP status code of the response
* @param string $uri the URI queries
* @param \Exception $previous the previous exception, if any
*/
public function __construct(
$message,
$error,
$httpStatus,
$uri,
\Exception $previous = null
) {
$this->error = $error;
parent::__construct($message, $httpStatus, $uri, $previous);
}
public function getErrorCode()
{
return $this->error;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace MaxMind\Exception;
class IpAddressNotFoundException extends InvalidRequestException
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace MaxMind\Exception;
/**
* This exception is thrown when the service requires permission to access.
*/
class PermissionRequiredException extends InvalidRequestException
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace MaxMind\Exception;
/**
* This class represents a generic web service error.
*/
class WebServiceException extends \Exception
{
}

View File

@@ -0,0 +1,472 @@
<?php
namespace MaxMind\WebService;
use Composer\CaBundle\CaBundle;
use MaxMind\Exception\AuthenticationException;
use MaxMind\Exception\HttpException;
use MaxMind\Exception\InsufficientFundsException;
use MaxMind\Exception\InvalidInputException;
use MaxMind\Exception\InvalidRequestException;
use MaxMind\Exception\IpAddressNotFoundException;
use MaxMind\Exception\PermissionRequiredException;
use MaxMind\Exception\WebServiceException;
use MaxMind\WebService\Http\RequestFactory;
/**
* This class is not intended to be used directly by an end-user of a
* MaxMind web service. Please use the appropriate client API for the service
* that you are using.
*
* @internal
*/
class Client
{
const VERSION = '0.2.0';
private $caBundle;
private $connectTimeout;
private $host = 'api.maxmind.com';
private $httpRequestFactory;
private $licenseKey;
private $proxy;
private $timeout;
private $userAgentPrefix;
private $accountId;
/**
* @param int $accountId your MaxMind account ID
* @param string $licenseKey your MaxMind license key
* @param array $options an array of options. Possible keys:
* * `host` - The host to use when connecting to the web service.
* * `userAgent` - The prefix of the User-Agent to use in the request.
* * `caBundle` - The bundle of CA root certificates to use in the request.
* * `connectTimeout` - The connect timeout to use for the request.
* * `timeout` - The timeout to use for the request.
* * `proxy` - The HTTP proxy to use. May include a schema, port,
* username, and password, e.g., `http://username:password@127.0.0.1:10`.
*/
public function __construct(
$accountId,
$licenseKey,
$options = []
) {
$this->accountId = $accountId;
$this->licenseKey = $licenseKey;
$this->httpRequestFactory = isset($options['httpRequestFactory'])
? $options['httpRequestFactory']
: new RequestFactory();
if (isset($options['host'])) {
$this->host = $options['host'];
}
if (isset($options['userAgent'])) {
$this->userAgentPrefix = $options['userAgent'] . ' ';
}
$this->caBundle = isset($options['caBundle']) ?
$this->caBundle = $options['caBundle'] : $this->getCaBundle();
if (isset($options['connectTimeout'])) {
$this->connectTimeout = $options['connectTimeout'];
}
if (isset($options['timeout'])) {
$this->timeout = $options['timeout'];
}
if (isset($options['proxy'])) {
$this->proxy = $options['proxy'];
}
}
/**
* @param string $service name of the service querying
* @param string $path the URI path to use
* @param array $input the data to be posted as JSON
*
* @throws InvalidInputException when the request has missing or invalid
* data
* @throws AuthenticationException when there is an issue authenticating the
* request
* @throws InsufficientFundsException when your account is out of funds
* @throws InvalidRequestException when the request is invalid for some
* other reason, e.g., invalid JSON in the POST.
* @throws HttpException when an unexpected HTTP error occurs
* @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions.
*
* @return array The decoded content of a successful response
*/
public function post($service, $path, $input)
{
$body = json_encode($input);
if ($body === false) {
throw new InvalidInputException(
'Error encoding input as JSON: '
. $this->jsonErrorDescription()
);
}
$request = $this->createRequest(
$path,
['Content-Type: application/json']
);
list($statusCode, $contentType, $body) = $request->post($body);
return $this->handleResponse(
$statusCode,
$contentType,
$body,
$service,
$path
);
}
public function get($service, $path)
{
$request = $this->createRequest($path);
list($statusCode, $contentType, $body) = $request->get();
return $this->handleResponse(
$statusCode,
$contentType,
$body,
$service,
$path
);
}
private function userAgent()
{
$curlVersion = curl_version();
return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . PHP_VERSION .
' curl/' . $curlVersion['version'];
}
private function createRequest($path, $headers = [])
{
array_push(
$headers,
'Authorization: Basic '
. base64_encode($this->accountId . ':' . $this->licenseKey),
'Accept: application/json'
);
return $this->httpRequestFactory->request(
$this->urlFor($path),
[
'caBundle' => $this->caBundle,
'connectTimeout' => $this->connectTimeout,
'headers' => $headers,
'proxy' => $this->proxy,
'timeout' => $this->timeout,
'userAgent' => $this->userAgent(),
]
);
}
/**
* @param int $statusCode the HTTP status code of the response
* @param string $contentType the Content-Type of the response
* @param string $body the response body
* @param string $service the name of the service
* @param string $path the path used in the request
*
* @throws AuthenticationException when there is an issue authenticating the
* request
* @throws InsufficientFundsException when your account is out of funds
* @throws InvalidRequestException when the request is invalid for some
* other reason, e.g., invalid JSON in the POST.
* @throws HttpException when an unexpected HTTP error occurs
* @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions
*
* @return array The decoded content of a successful response
*/
private function handleResponse(
$statusCode,
$contentType,
$body,
$service,
$path
) {
if ($statusCode >= 400 && $statusCode <= 499) {
$this->handle4xx($statusCode, $contentType, $body, $service, $path);
} elseif ($statusCode >= 500) {
$this->handle5xx($statusCode, $service, $path);
} elseif ($statusCode !== 200) {
$this->handleUnexpectedStatus($statusCode, $service, $path);
}
return $this->handleSuccess($body, $service);
}
/**
* @return string describing the JSON error
*/
private function jsonErrorDescription()
{
$errno = json_last_error();
switch ($errno) {
case JSON_ERROR_DEPTH:
return 'The maximum stack depth has been exceeded.';
case JSON_ERROR_STATE_MISMATCH:
return 'Invalid or malformed JSON.';
case JSON_ERROR_CTRL_CHAR:
return 'Control character error.';
case JSON_ERROR_SYNTAX:
return 'Syntax error.';
case JSON_ERROR_UTF8:
return 'Malformed UTF-8 characters.';
default:
return "Other JSON error ($errno).";
}
}
/**
* @param string $path the path to use in the URL
*
* @return string the constructed URL
*/
private function urlFor($path)
{
return 'https://' . $this->host . $path;
}
/**
* @param int $statusCode the HTTP status code
* @param string $contentType the response content-type
* @param string $body the response body
* @param string $service the service name
* @param string $path the path used in the request
*
* @throws AuthenticationException
* @throws HttpException
* @throws InsufficientFundsException
* @throws InvalidRequestException
*/
private function handle4xx(
$statusCode,
$contentType,
$body,
$service,
$path
) {
if (strlen($body) === 0) {
throw new HttpException(
"Received a $statusCode error for $service with no body",
$statusCode,
$this->urlFor($path)
);
}
if (!strstr($contentType, 'json')) {
throw new HttpException(
"Received a $statusCode error for $service with " .
'the following body: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
$message = json_decode($body, true);
if ($message === null) {
throw new HttpException(
"Received a $statusCode error for $service but could " .
'not decode the response as JSON: '
. $this->jsonErrorDescription() . ' Body: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
if (!isset($message['code']) || !isset($message['error'])) {
throw new HttpException(
'Error response contains JSON but it does not ' .
'specify code or error keys: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
$this->handleWebServiceError(
$message['error'],
$message['code'],
$statusCode,
$path
);
}
/**
* @param string $message the error message from the web service
* @param string $code the error code from the web service
* @param int $statusCode the HTTP status code
* @param string $path the path used in the request
*
* @throws AuthenticationException
* @throws InvalidRequestException
* @throws InsufficientFundsException
*/
private function handleWebServiceError(
$message,
$code,
$statusCode,
$path
) {
switch ($code) {
case 'IP_ADDRESS_NOT_FOUND':
case 'IP_ADDRESS_RESERVED':
throw new IpAddressNotFoundException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'ACCOUNT_ID_REQUIRED':
case 'ACCOUNT_ID_UNKNOWN':
case 'AUTHORIZATION_INVALID':
case 'LICENSE_KEY_REQUIRED':
case 'USER_ID_REQUIRED':
case 'USER_ID_UNKNOWN':
throw new AuthenticationException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'OUT_OF_QUERIES':
case 'INSUFFICIENT_FUNDS':
throw new InsufficientFundsException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'PERMISSION_REQUIRED':
throw new PermissionRequiredException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
default:
throw new InvalidRequestException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
}
}
/**
* @param int $statusCode the HTTP status code
* @param string $service the service name
* @param string $path the URI path used in the request
*
* @throws HttpException
*/
private function handle5xx($statusCode, $service, $path)
{
throw new HttpException(
"Received a server error ($statusCode) for $service",
$statusCode,
$this->urlFor($path)
);
}
/**
* @param int $statusCode the HTTP status code
* @param string $service the service name
* @param string $path the URI path used in the request
*
* @throws HttpException
*/
private function handleUnexpectedStatus($statusCode, $service, $path)
{
throw new HttpException(
'Received an unexpected HTTP status ' .
"($statusCode) for $service",
$statusCode,
$this->urlFor($path)
);
}
/**
* @param string $body the successful request body
* @param string $service the service name
*
* @throws WebServiceException if the request body cannot be decoded as
* JSON
*
* @return array the decoded request body
*/
private function handleSuccess($body, $service)
{
if (strlen($body) === 0) {
throw new WebServiceException(
"Received a 200 response for $service but did not " .
'receive a HTTP body.'
);
}
$decodedContent = json_decode($body, true);
if ($decodedContent === null) {
throw new WebServiceException(
"Received a 200 response for $service but could " .
'not decode the response as JSON: '
. $this->jsonErrorDescription() . ' Body: ' . $body
);
}
return $decodedContent;
}
private function getCaBundle()
{
$curlVersion = curl_version();
// On OS X, when the SSL version is "SecureTransport", the system's
// keychain will be used.
if ($curlVersion['ssl_version'] === 'SecureTransport') {
return;
}
$cert = CaBundle::getSystemCaRootBundlePath();
// Check if the cert is inside a phar. If so, we need to copy the cert
// to a temp file so that curl can see it.
if (substr($cert, 0, 7) === 'phar://') {
$tempDir = sys_get_temp_dir();
$newCert = tempnam($tempDir, 'geoip2-');
if ($newCert === false) {
throw new \RuntimeException(
"Unable to create temporary file in $tempDir"
);
}
if (!copy($cert, $newCert)) {
throw new \RuntimeException(
"Could not copy $cert to $newCert: "
. var_export(error_get_last(), true)
);
}
// We use a shutdown function rather than the destructor as the
// destructor isn't called on a fatal error such as an uncaught
// exception.
register_shutdown_function(
function () use ($newCert) {
unlink($newCert);
}
);
$cert = $newCert;
}
if (!file_exists($cert)) {
throw new \RuntimeException("CA cert does not exist at $cert");
}
return $cert;
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace MaxMind\WebService\Http;
use MaxMind\Exception\HttpException;
/**
* This class is for internal use only. Semantic versioning does not not apply.
*
* @internal
*/
class CurlRequest implements Request
{
private $url;
private $options;
/**
* @param $url
* @param $options
*/
public function __construct($url, $options)
{
$this->url = $url;
$this->options = $options;
}
/**
* @param $body
*
* @return array
*/
public function post($body)
{
$curl = $this->createCurl();
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
return $this->execute($curl);
}
public function get()
{
$curl = $this->createCurl();
curl_setopt($curl, CURLOPT_HTTPGET, true);
return $this->execute($curl);
}
/**
* @return resource
*/
private function createCurl()
{
$curl = curl_init($this->url);
if (!empty($this->options['caBundle'])) {
$opts[CURLOPT_CAINFO] = $this->options['caBundle'];
}
$opts[CURLOPT_SSL_VERIFYHOST] = 2;
$opts[CURLOPT_FOLLOWLOCATION] = false;
$opts[CURLOPT_SSL_VERIFYPEER] = true;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_HTTPHEADER] = $this->options['headers'];
$opts[CURLOPT_USERAGENT] = $this->options['userAgent'];
$opts[CURLOPT_PROXY] = $this->options['proxy'];
// The defined()s are here as the *_MS opts are not available on older
// cURL versions
$connectTimeout = $this->options['connectTimeout'];
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
$opts[CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000);
} else {
$opts[CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout);
}
$timeout = $this->options['timeout'];
if (defined('CURLOPT_TIMEOUT_MS')) {
$opts[CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000);
} else {
$opts[CURLOPT_TIMEOUT] = ceil($timeout);
}
curl_setopt_array($curl, $opts);
return $curl;
}
private function execute($curl)
{
$body = curl_exec($curl);
if ($errno = curl_errno($curl)) {
$errorMessage = curl_error($curl);
throw new HttpException(
"cURL error ({$errno}): {$errorMessage}",
0,
$this->url
);
}
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
curl_close($curl);
return [$statusCode, $contentType, $body];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace MaxMind\WebService\Http;
/**
* Interface Request.
*
* @internal
*/
interface Request
{
/**
* @param $url
* @param $options
*/
public function __construct($url, $options);
/**
* @param $body
*
* @return mixed
*/
public function post($body);
/**
* @return mixed
*/
public function get();
}

View File

@@ -0,0 +1,26 @@
<?php
namespace MaxMind\WebService\Http;
/**
* Class RequestFactory.
*
* @internal
*/
class RequestFactory
{
public function __construct()
{
}
/**
* @param $url
* @param $options
*
* @return CurlRequest
*/
public function request($url, $options)
{
return new CurlRequest($url, $options);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFI0faZb9T1jloaRMw4g
G/tW2CnYwYTpmXuQCphIrM6rYvjptYtlJBzy370sQovLIzHBiqAYc3Rv5FJBDQ0F
mTR/iF3Tm8YmxjqSuHECn8Q6KSPvaZyQFSM8vUmFjwVtTKgEjo4rFVrne8OOvQ4S
FRvWbeBvxoO6SfLArLK+hJAZSMRyzzfsNL1q5okZqLHQhdtkiFfPWJkXIEhL+vUK
U5SLZoFIh9hVImmJuXHBQ0qXRnTCQlpb80GrMD1CBYFFeHx8IOCZwWZ2ifIPL5n+
vxjZ30zH3DDhcwhQv3y2hJsedJ7w+7I7gs/jmECUG36rbGbpaXrQnwhaiGBdRoiv
awIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,36 @@
<?php
if (defined('WFWAF_VERSION')) { exit(); }
define('WFWAF_VERSION', '1.0.4');
define('WFWAF_PATH', dirname(__FILE__) . '/');
define('WFWAF_LIB_PATH', WFWAF_PATH . 'lib/');
define('WFWAF_VIEW_PATH', WFWAF_PATH . 'views/');
define('WFWAF_API_URL_SEC', 'https://noc4.wordfence.com/v1.9/');
if (!defined('WFWAF_DEBUG')) {
define('WFWAF_DEBUG', false);
}
if (!defined('WFWAF_ENABLED')) {
define('WFWAF_ENABLED', true);
}
define('WFWAF_IS_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
require_once WFWAF_LIB_PATH . 'waf.php';
require_once WFWAF_LIB_PATH . 'utils.php';
require_once WFWAF_LIB_PATH . 'i18n.php';
require_once WFWAF_LIB_PATH . 'xmlrpc.php';
require_once WFWAF_LIB_PATH . 'storage.php';
require_once WFWAF_LIB_PATH . 'storage/file.php';
require_once WFWAF_LIB_PATH . 'storage/mysql.php';
require_once WFWAF_LIB_PATH . 'config.php';
require_once WFWAF_LIB_PATH . 'rules.php';
require_once WFWAF_LIB_PATH . 'parser/lexer.php';
require_once WFWAF_LIB_PATH . 'parser/parser.php';
require_once WFWAF_LIB_PATH . 'parser/sqli.php';
require_once WFWAF_LIB_PATH . 'request.php';
require_once WFWAF_LIB_PATH . 'http.php';
require_once WFWAF_LIB_PATH . 'view.php';

View File

@@ -0,0 +1,88 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
/**
* Class wfWAFConfig provides a convenience interface for accessing the WAF's configuration
* that does not throw exceptions. All exceptions are caught and, if WFWAF_DEBUG is true, logged
* to the server error log.
*/
class wfWAFConfig {
public static function set($key, $val, $waf = null, $category = '') {
if (!($waf instanceof wfWAF)) {
$waf = wfWAF::getInstance();
}
try {
$waf->getStorageEngine()->setConfig($key, $val, $category);
}
catch (Exception $e) {
if (WFWAF_DEBUG) {
error_log("Exception in " . __CLASS__ . "->" . __FUNCTION__ . ": " . $e->getMessage());
}
}
}
public static function get($key, $default = null, $waf = null, $category = '') {
if (!($waf instanceof wfWAF)) {
$waf = wfWAF::getInstance();
}
try {
return $waf->getStorageEngine()->getConfig($key, $default, $category);
}
catch (Exception $e) {
if (WFWAF_DEBUG) {
error_log("Exception in " . __CLASS__ . "->" . __FUNCTION__ . ": " . $e->getMessage());
}
}
return $default;
}
public static function unsetKey($key, $waf = null, $category = '') {
if (!($waf instanceof wfWAF)) {
$waf = wfWAF::getInstance();
}
try {
$waf->getStorageEngine()->unsetConfig($key, $category);
}
catch (Exception $e) {
if (WFWAF_DEBUG) {
error_log("Exception in " . __CLASS__ . "->" . __FUNCTION__ . ": " . $e->getMessage());
}
}
}
public static function isInLearningMode($waf = null) {
if (!($waf instanceof wfWAF)) {
$waf = wfWAF::getInstance();
}
try {
return $waf->getStorageEngine()->isInLearningMode();
}
catch (Exception $e) {
if (WFWAF_DEBUG) {
error_log("Exception in " . __CLASS__ . "->" . __FUNCTION__ . ": " . $e->getMessage());
}
}
return false;
}
public static function isDisabled($waf = null) {
if (!($waf instanceof wfWAF)) {
$waf = wfWAF::getInstance();
}
try {
return $waf->getStorageEngine()->isDisabled();
}
catch (Exception $e) {
if (WFWAF_DEBUG) {
error_log("Exception in " . __CLASS__ . "->" . __FUNCTION__ . ": " . $e->getMessage());
}
}
return true;
}
}
}

View File

@@ -0,0 +1,472 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
class wfWAFHTTP {
private $url;
private $auth;
private $body;
private $cookies;
// private $fileNames;
// private $files;
private $headers;
private $method;
private $queryString;
/**
* @var wfWAFHTTPTransport
*/
private $transport;
/**
* @param string $url
* @param wfWAFHTTP $request
* @return wfWAFHTTPResponse|bool
* @throws wfWAFHTTPTransportException
*/
public static function get($url, $request = null, $timeout = 5, $connectTimeout = null) {
if (!$request) {
$request = new self();
}
$request->setUrl($url);
$request->setMethod('GET');
$transport = wfWAFHTTPTransport::getInstance();
$transport->setConnectTimeout($connectTimeout);
$transport->setTimeout($timeout);
$request->setTransport($transport);
// $request->setCookies("XDEBUG_SESSION=netbeans-xdebug");
return $request->send();
}
/**
* @param string $url
* @param array $post
* @param wfWAFHTTP $request
* @return wfWAFHTTPResponse|bool
* @throws wfWAFHTTPTransportException
*/
public static function post($url, $post = array(), $request = null, $timeout = 5, $connectTimeout = null) {
if (!$request) {
$request = new self();
}
$request->setUrl($url);
$request->setMethod('POST');
$request->setBody($post);
$transport = wfWAFHTTPTransport::getInstance();
$transport->setConnectTimeout($connectTimeout);
$transport->setTimeout($timeout);
$request->setTransport($transport);
return $request->send();
}
/**
* @return wfWAFHTTPResponse|bool
* @throws wfWAFHTTPTransportException
*/
public function send() {
if (!$this->getTransport()) {
throw new wfWAFHTTPTransportException('Need to provide a valid HTTP transport before calling ' . __METHOD__);
}
return $this->getTransport()->send($this);
}
/**
* @return mixed
*/
public function getUrl() {
return $this->url;
}
/**
* @param mixed $url
*/
public function setUrl($url) {
$this->url = $url;
}
/**
* @return mixed
*/
public function getAuth() {
return $this->auth;
}
/**
* @param mixed $auth
*/
public function setAuth($auth) {
$this->auth = $auth;
}
/**
* @return mixed
*/
public function getBody() {
return $this->body;
}
/**
* @param mixed $body
*/
public function setBody($body) {
$this->body = $body;
}
/**
* @return mixed
*/
public function getCookies() {
return $this->cookies;
}
/**
* @param mixed $cookies
*/
public function setCookies($cookies) {
$this->cookies = $cookies;
}
/**
* @return mixed
*/
public function getHeaders() {
return $this->headers;
}
/**
* @param mixed $headers
*/
public function setHeaders($headers) {
$this->headers = $headers;
}
/**
* @return mixed
*/
public function getMethod() {
return $this->method;
}
/**
* @param mixed $method
*/
public function setMethod($method) {
$this->method = $method;
}
/**
* @return mixed
*/
public function getQueryString() {
return $this->queryString;
}
/**
* @param mixed $queryString
*/
public function setQueryString($queryString) {
$this->queryString = $queryString;
}
/**
* @return wfWAFHTTPTransport
*/
public function getTransport() {
return $this->transport;
}
/**
* @param wfWAFHTTPTransport $transport
*/
public function setTransport($transport) {
$this->transport = $transport;
}
}
class wfWAFHTTPResponse {
private $body;
private $headers;
private $statusCode;
/**
* @return mixed
*/
public function getBody() {
return $this->body;
}
/**
* @param mixed $body
*/
public function setBody($body) {
$this->body = $body;
}
/**
* @return mixed
*/
public function getHeaders() {
return $this->headers;
}
/**
* @param mixed $headers
*/
public function setHeaders($headers) {
$this->headers = $headers;
}
/**
* @return mixed
*/
public function getStatusCode() {
return $this->statusCode;
}
/**
* @param mixed $statusCode
*/
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
}
}
abstract class wfWAFHTTPTransport {
private static $instance;
private $_connectTimeout = null;
private $_timeout = 5;
/**
* @return wfWAFHTTPTransport
* @throws wfWAFHTTPTransportException
*/
public static function getInstance() {
if (!self::$instance) {
self::$instance = self::getFirstTransport();
}
return self::$instance;
}
/**
* @param mixed $instance
*/
public static function setInstance($instance) {
self::$instance = $instance;
}
/**
* @return wfWAFHTTPTransport
* @throws wfWAFHTTPTransportException
*/
public static function getFirstTransport() {
if (function_exists('curl_init')) {
return new wfWAFHTTPTransportCurl();
} else if (function_exists('file_get_contents')) {
return new wfWAFHTTPTransportStreams();
}
throw new wfWAFHTTPTransportException('No valid HTTP transport found.');
}
/**
* @param array $cookieArray
* @return string
*/
public static function buildCookieString($cookieArray) {
$cookies = '';
foreach ($cookieArray as $cookieName => $value) {
$cookies .= "$cookieName=" . urlencode($value) . '; ';
}
$cookies = rtrim($cookies);
return $cookies;
}
/**
* @param wfWAFHTTP $request
* @return wfWAFHTTPResponse|bool
*/
abstract public function send($request);
public function setConnectTimeout($connectTimeout) {
$this->_connectTimeout = $connectTimeout;
}
public function getConnectTimeout() {
return $this->_connectTimeout;
}
public function setTimeout($timeout) {
$this->_timeout = $timeout;
}
public function getTimeout() {
return $this->_timeout;
}
}
class wfWAFHTTPTransportCurl extends wfWAFHTTPTransport {
/**
* @todo Proxy settings
* @param wfWAFHTTP $request
* @return wfWAFHTTPResponse|bool
*/
public function send($request) {
$url = $request->getUrl();
if ($queryString = $request->getQueryString()) {
if (is_array($queryString)) {
$queryString = http_build_query($queryString, null, '&');
}
$url .= (wfWAFUtils::strpos($url, '?') !== false ? '&' : '?') . $queryString;
}
$ch = curl_init($url);
switch (wfWAFUtils::strtolower($request->getMethod())) {
case 'post':
curl_setopt($ch, CURLOPT_POST, 1);
break;
}
if ($body = $request->getBody()) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
if ($auth = $request->getAuth()) {
curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
}
if ($cookies = $request->getCookies()) {
if (is_array($cookies)) {
$cookies = self::buildCookieString($cookies);
}
curl_setopt($ch, CURLOPT_COOKIE, $cookies);
}
if ($headers = $request->getHeaders()) {
if (is_array($headers)) {
$_headers = array();
foreach ($headers as $header => $value) {
$_headers[] = $header . ': ' . $value;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
}
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if ($this->getConnectTimeout() !== null) { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->getConnectTimeout()); }
curl_setopt($ch, CURLOPT_TIMEOUT, $this->getTimeout());
if (defined('CURLOPT_ACCEPT_ENCODING')) {
curl_setopt($ch, CURLOPT_ACCEPT_ENCODING, ''); //The empty string is a magic value that means "send all supported encodings"
}
else if (defined('CURLOPT_ENCODING')) {
curl_setopt($ch, CURLOPT_ENCODING, '');
}
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_CAINFO, WFWAF_PATH . 'cacert.pem'); //On some systems curl uses an outdated root certificate chain file
$curlResponse = curl_exec($ch);
if ($curlResponse !== false) {
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = wfWAFUtils::substr($curlResponse, 0, $headerSize);
$body = wfWAFUtils::substr($curlResponse, $headerSize);
$response = new wfWAFHTTPResponse();
$response->setBody($body);
$response->setHeaders($header);
return $response;
}
return false;
}
}
class wfWAFHTTPTransportStreams extends wfWAFHTTPTransport {
/**
* @todo Implement wfWAFHTTPTransportStreams::send.
* @param wfWAFHTTP $request
* @return mixed
* @throws wfWAFHTTPTransportException
*/
public function send($request) {
$timeout = $this->getTimeout();
$url = $request->getUrl();
if ($queryString = $request->getQueryString()) {
if (is_array($queryString)) {
$queryString = http_build_query($queryString, null, '&');
}
$url .= (wfWAFUtils::strpos($url, '?') !== false ? '&' : '?') . $queryString;
}
$urlParsed = parse_url($request->getUrl());
$headers = "Host: $urlParsed[host]\r\n";
if ($auth = $request->getAuth()) {
$headers .= 'Authorization: Basic ' . base64_encode($auth['user'] . ':' . $auth['password']) . "\r\n";
}
if ($cookies = $request->getCookies()) {
if (is_array($cookies)) {
$cookies = self::buildCookieString($cookies);
}
$headers .= "Cookie: $cookies\r\n";
}
$hasUA = false;
if ($_headers = $request->getHeaders()) {
if (is_array($_headers)) {
foreach ($_headers as $header => $value) {
if (trim(wfWAFUtils::strtolower($header)) === 'user-agent') {
$hasUA = true;
}
$headers .= $header . ': ' . $value . "\r\n";
}
}
}
if (!$hasUA) {
$headers .= "User-Agent: Wordfence Streams UA\r\n";
}
$httpOptions = array(
'method' => $request->getMethod(),
'ignore_errors' => true,
'timeout' => $timeout,
'follow_location' => 1,
'max_redirects' => 5,
);
if (wfWAFUtils::strlen($request->getBody()) > 0) {
$httpOptions['content'] = $request->getBody();
$headers .= 'Content-Length: ' . wfWAFUtils::strlen($httpOptions['content']) . "\r\n";
}
$httpOptions['header'] = $headers;
$options = array(
wfWAFUtils::strtolower($urlParsed['scheme']) => $httpOptions,
);
$context = stream_context_create($options);
$stream = fopen($request->getUrl(), 'r', false, $context);
if (!is_resource($stream)) {
return false;
}
$metaData = stream_get_meta_data($stream);
// Get the HTTP response code
$httpResponse = array_shift($metaData['wrapper_data']);
if (preg_match_all('/(\w+\/\d\.\d) (\d{3})/', $httpResponse, $matches) !== false) {
// $protocol = $matches[1][0];
$status = (int) $matches[2][0];
} else {
// $protocol = null;
$status = null;
}
$responseObj = new wfWAFHTTPResponse();
$responseObj->setHeaders(join("\r\n", $metaData['wrapper_data']));
$responseObj->setBody(stream_get_contents($stream));
$responseObj->setStatusCode($status);
// Close the stream after use
fclose($stream);
return $responseObj;
}
}
class wfWAFHTTPTransportException extends wfWAFException {
}
}

View File

@@ -0,0 +1,87 @@
<?php
class wfWAFI18n {
/**
* @var self
*/
protected static $instance;
/**
* @param string $text
* @return string
*/
public static function __($text) {
return self::getInstance()->getI18nEngine()->__($text);
}
public static function esc_html__($text) {
return htmlentities(self::__($text), ENT_QUOTES, 'UTF-8');
}
public static function esc_html_e($text) {
echo self::esc_html__($text);
}
/**
* @return self
*/
public static function getInstance() {
if (!self::$instance) {
self::$instance = new self(new wfWAFI18nEngineDefault());
}
return self::$instance;
}
/**
* @param self $i18nEngine
*/
public static function setInstance($i18nEngine) {
self::$instance = $i18nEngine;
}
/** @var wfWAFI18nEngine */
private $i18nEngine;
/**
* @param wfWAFI18nEngine $i18nEngine
*/
public function __construct($i18nEngine) {
$this->i18nEngine = $i18nEngine;
}
/**
* @return wfWAFI18nEngine
*/
public function getI18nEngine() {
return $this->i18nEngine;
}
/**
* @param wfWAFI18nEngine $i18nEngine
*/
public function setI18nEngine($i18nEngine) {
$this->i18nEngine = $i18nEngine;
}
}
class wfWAFI18nEngineDefault implements wfWAFI18nEngine {
/**
* @param string $text
* @return string
*/
public function __($text) {
return $text;
}
}
interface wfWAFI18nEngine {
/**
* @param string $text
* @return string
*/
public function __($text);
}

View File

@@ -0,0 +1,960 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Converts to and from JSON format.
*
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @category
* @package Services_JSON
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @version CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('WF_SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('WF_SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('WF_SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('WF_SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('WF_SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('WF_SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('WF_SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Behavior switch for Services_JSON::decode()
*/
define('WF_SERVICES_JSON_USE_TO_JSON', 64);
/**
* Converts to and from JSON format.
*
* Brief example of use:
*
* <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
* // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
* print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
*
* // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input);
* </code>
*/
class wfServices_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
* - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects
* It serializes the return value from the toJSON call rather
* than the object itself, toJSON can return associative arrays,
* strings or numbers, if you return an object, make sure it does
* not have a toJSON method, otherwise an error will occur.
*/
function __construct( $use = 0 )
{
$this->use = $use;
$this->_mb_strlen = function_exists('mb_strlen');
$this->_mb_convert_encoding = function_exists('mb_convert_encoding');
$this->_mb_substr = function_exists('mb_substr');
}
/**
* PHP4 constructor.
*/
public function wfServices_JSON( $use = 0 ) {
self::__construct( $use );
}
// private - cache the mbstring lookup results..
var $_mb_strlen = false;
var $_mb_substr = false;
var $_mb_convert_encoding = false;
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
* @access private
*/
function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if($this->_mb_convert_encoding) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch(true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
* @access private
*/
function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if($this->_mb_convert_encoding) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch($this->strlen8($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8[0]) >> 2))
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8[0]) << 4))
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format (and sends JSON Header)
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encode($var)
{
header('Content-type: application/json');
return $this->encodeUnsafe($var);
}
/**
* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encodeUnsafe($var)
{
// see bug #16908 - regarding numeric locale printing
$lc = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, 'C');
$ret = $this->_encode($var);
setlocale(LC_NUMERIC, $lc);
return $ret;
}
/**
* PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function _encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = $this->strlen8($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c+1 >= $strlen_var) {
$c += 1;
$ascii .= '?';
break;
}
$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
if ($c+2 >= $strlen_var) {
$c += 2;
$ascii .= '?';
break;
}
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
@ord($var[$c + 1]),
@ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
if ($c+3 >= $strlen_var) {
$c += 3;
$ascii .= '?';
break;
}
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c+4 >= $strlen_var) {
$c += 4;
$ascii .= '?';
break;
}
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
if ($c+5 >= $strlen_var) {
$c += 5;
$ascii .= '?';
break;
}
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map(array($this, 'name_value'),
array_keys($var),
array_values($var));
foreach($properties as $property) {
if(wfServices_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map(array($this, '_encode'), $var);
foreach($elements as $element) {
if(wfServices_JSON::isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
// support toJSON methods.
if (($this->use & WF_SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
// this may end up allowing unlimited recursion
// so we check the return value to make sure it's not got the same method.
$recode = $var->toJSON();
if (method_exists($recode, 'toJSON')) {
return ($this->use & WF_SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new wfServices_JSON_Error(get_class($var).
" toJSON returned an object with a toJSON method.");
}
return $this->_encode( $recode );
}
$vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'),
array_keys($vars),
array_values($vars));
foreach($properties as $property) {
if(wfServices_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & WF_SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new wfServices_JSON_Error(gettype($var)." can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->_encode($value);
if(wfServices_JSON::isError($encoded_value)) {
return $encoded_value;
}
return $this->_encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
* @access public
*/
function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
$m = array();
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = $this->substr8($str, 0, 1);
$chrs = $this->substr8($str, 1, -1);
$utf8 = '';
$strlen_chrs = $this->strlen8($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
$ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs[++$c];
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
. chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= $this->substr8($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str[0] == '[') {
$stk = array(WF_SERVICES_JSON_IN_ARR);
$arr = array();
} else {
if ($this->use & WF_SERVICES_JSON_LOOSE_TYPE) {
$stk = array(WF_SERVICES_JSON_IN_OBJ);
$obj = array();
} else {
$stk = array(WF_SERVICES_JSON_IN_OBJ);
$obj = new stdClass();
}
}
array_push($stk, array('what' => WF_SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false));
$chrs = $this->substr8($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == WF_SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = $this->strlen8($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == WF_SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => WF_SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
//print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == WF_SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == WF_SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = array();
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
if ($this->use & WF_SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
if ($this->use & WF_SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != WF_SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => WF_SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
//print("Found start of string at {$c}\n");
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == WF_SERVICES_JSON_IN_STR) &&
(($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '[') &&
in_array($top['what'], array(WF_SERVICES_JSON_SLICE, WF_SERVICES_JSON_IN_ARR, WF_SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => WF_SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
} elseif (($chrs[$c] == ']') && ($top['what'] == WF_SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '{') &&
in_array($top['what'], array(WF_SERVICES_JSON_SLICE, WF_SERVICES_JSON_IN_ARR, WF_SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => WF_SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
} elseif (($chrs[$c] == '}') && ($top['what'] == WF_SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(WF_SERVICES_JSON_SLICE, WF_SERVICES_JSON_IN_ARR, WF_SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => WF_SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == WF_SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == WF_SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == WF_SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);
} elseif (is_object($data) && (get_class($data) == 'wfservices_json_error' ||
is_subclass_of($data, 'wfServices_JSON_Error'))) {
return true;
}
return false;
}
/**
* Calculates length of string in bytes
* @param string
* @return integer length
*/
function strlen8( $str )
{
if ( $this->_mb_strlen ) {
return mb_strlen( $str, "8bit" );
}
return strlen( $str );
}
/**
* Returns part of a string, interpreting $start and $length as number of bytes.
* @param string
* @param integer start
* @param integer length
* @return integer length
*/
function substr8( $string, $start, $length=false )
{
if ( $length === false ) {
$length = $this->strlen8( $string ) - $start;
}
if ( $this->_mb_substr ) {
return mb_substr( $string, $start, $length, "8bit" );
}
return substr( $string, $start, $length );
}
}
if (class_exists('PEAR_Error')) {
class wfServices_JSON_Error extends PEAR_Error
{
function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
}
public function wfServices_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null) {
self::__construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null);
}
}
} else {
/**
* @todo Ultimately, this class shall be descended from PEAR_Error
*/
class wfServices_JSON_Error
{
/**
* PHP5 constructor.
*/
function __construct( $message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null )
{
}
/**
* PHP4 constructor.
*/
public function wfServices_JSON_Error( $message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null ) {
self::__construct( $message, $code, $mode, $options, $userinfo );
}
}
}
}

View File

@@ -0,0 +1,690 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
interface wfWAFLexerInterface {
public function nextToken();
}
class wfWAFRuleLexer implements wfWAFLexerInterface {
const MATCH_IDENTIFIER = '/[a-zA-Z_][\\w_]*/';
const MATCH_SINGLE_STRING_LITERAL = '/\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const MATCH_DOUBLE_STRING_LITERAL = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"/As';
const MATCH_NUMBER_LITERAL = '/-?\d+(\.\d+)?/';
const MATCH_DOT = '/\./';
const MATCH_AND_COMPARISON_OPERATOR = '/&&/';
const MATCH_OR_COMPARISON_OPERATOR = '/\|\|/';
const MATCH_OPEN_PARENTHESIS = '/\(/';
const MATCH_CLOSE_PARENTHESIS = '/\)/';
const MATCH_COMMA = '/,/';
const MATCH_RULE_COMPARISON_END = '/:/';
const MATCH_ASSIGNMENT = '/=/';
const MATCH_SINGLE_LINE_COMMENT = '/(?:#|\/\/)[^\n]*/';
const MATCH_MULTIPLE_LINE_COMMENT = '/\/\*.*?\*\//s';
const MATCH_OPEN_BRACKET = '/\[/';
const MATCH_CLOSE_BRACKET = '/\]/';
const T_RULE_START = 'T_RULE_START';
const T_IDENTIFIER = 'T_IDENTIFIER';
const T_SINGLE_STRING_LITERAL = 'T_SINGLE_STRING_LITERAL';
const T_DOUBLE_STRING_LITERAL = 'T_DOUBLE_STRING_LITERAL';
const T_NUMBER_LITERAL = 'T_NUMBER_LITERAL';
const T_DOT = 'T_DOT';
const T_COMPARISON_OPERATOR = 'T_COMPARISON_OPERATOR';
const T_OPEN_PARENTHESIS = 'T_OPEN_PARENTHESIS';
const T_CLOSE_PARENTHESIS = 'T_CLOSE_PARENTHESIS';
const T_COMMA = 'T_COMMA';
const T_RULE_COMPARISON_END = 'T_RULE_COMPARISON_END';
const T_ASSIGNMENT = 'T_ASSIGNMENT';
const T_SINGLE_LINE_COMMENT = 'T_SINGLE_LINE_COMMENT';
const T_MULTIPLE_LINE_COMMENT = 'T_MULTIPLE_LINE_COMMENT';
const T_OPEN_BRACKET = 'T_OPEN_BRACKET';
const T_CLOSE_BRACKET = 'T_CLOSE_BRACKET';
/**
* @var string
*/
private $rules;
/**
* @var wfWAFStringScanner
*/
private $scanner;
/**
* wfWAFRuleLexer constructor.
* @param $rules
*/
public function __construct($rules) {
$this->setRules($rules);
$this->scanner = new wfWAFStringScanner($rules);
}
/**
* @return array
* @throws wfWAFParserSyntaxError
*/
public function tokenize() {
$tokens = array();
while ($token = $this->nextToken()) {
$tokens[] = $token;
}
return $tokens;
}
/**
* @return bool|wfWAFLexerToken
* @throws wfWAFParserSyntaxError
*/
public function nextToken() {
if (!$this->scanner->eos()) {
$this->scanner->skip('/\s+/s');
if ($this->scanner->eos()) {
return false;
}
if (($match = $this->scanner->scan(self::MATCH_IDENTIFIER)) !== null)
switch (wfWAFUtils::strtolower($match)) {
case 'if':
return $this->createToken(self::T_RULE_START, $match);
case 'and':
case 'or':
case 'xor':
return $this->createToken(self::T_COMPARISON_OPERATOR, $match);
default:
return $this->createToken(self::T_IDENTIFIER, $match);
}
else if (($match = $this->scanner->scan(self::MATCH_SINGLE_STRING_LITERAL)) !== null) return $this->createToken(self::T_SINGLE_STRING_LITERAL, $match);
else if (($match = $this->scanner->scan(self::MATCH_DOUBLE_STRING_LITERAL)) !== null) return $this->createToken(self::T_DOUBLE_STRING_LITERAL, $match);
else if (($match = $this->scanner->scan(self::MATCH_NUMBER_LITERAL)) !== null) return $this->createToken(self::T_NUMBER_LITERAL, $match);
else if (($match = $this->scanner->scan(self::MATCH_DOT)) !== null) return $this->createToken(self::T_DOT, $match);
else if (($match = $this->scanner->scan(self::MATCH_AND_COMPARISON_OPERATOR)) !== null) return $this->createToken(self::T_COMPARISON_OPERATOR, $match);
else if (($match = $this->scanner->scan(self::MATCH_OR_COMPARISON_OPERATOR)) !== null) return $this->createToken(self::T_COMPARISON_OPERATOR, $match);
else if (($match = $this->scanner->scan(self::MATCH_OPEN_PARENTHESIS)) !== null) return $this->createToken(self::T_OPEN_PARENTHESIS, $match);
else if (($match = $this->scanner->scan(self::MATCH_CLOSE_PARENTHESIS)) !== null) return $this->createToken(self::T_CLOSE_PARENTHESIS, $match);
else if (($match = $this->scanner->scan(self::MATCH_COMMA)) !== null) return $this->createToken(self::T_COMMA, $match);
else if (($match = $this->scanner->scan(self::MATCH_RULE_COMPARISON_END)) !== null) return $this->createToken(self::T_RULE_COMPARISON_END, $match);
else if (($match = $this->scanner->scan(self::MATCH_ASSIGNMENT)) !== null) return $this->createToken(self::T_ASSIGNMENT, $match);
else if (($match = $this->scanner->scan(self::MATCH_OPEN_BRACKET)) !== null) return $this->createToken(self::T_OPEN_BRACKET, $match);
else if (($match = $this->scanner->scan(self::MATCH_CLOSE_BRACKET)) !== null) return $this->createToken(self::T_CLOSE_BRACKET, $match);
else if (($match = $this->scanner->scan(self::MATCH_SINGLE_LINE_COMMENT)) !== null) return $this->createToken(self::T_SINGLE_LINE_COMMENT, $match);
else if (($match = $this->scanner->scan(self::MATCH_MULTIPLE_LINE_COMMENT)) !== null) return $this->createToken(self::T_MULTIPLE_LINE_COMMENT, $match);
else {
$e = new wfWAFParserSyntaxError(sprintf('Invalid character "%s" found on line %d, column %d',
$this->scanner->scanChar(), $this->scanner->getLine(), $this->scanner->getColumn()));
$e->setParseLine($this->scanner->getLine());
$e->setParseColumn($this->scanner->getColumn());
throw $e;
}
}
return false;
}
/**
* @param $type
* @param $value
* @return wfWAFLexerToken
*/
protected function createToken($type, $value) {
return new wfWAFLexerToken($type, $value, $this->scanner->getLine(), $this->scanner->getColumn());
}
/**
* @return string
*/
public function getRules() {
return $this->rules;
}
/**
* @param string $rules
*/
public function setRules($rules) {
$this->rules = rtrim($rules);
}
}
/**
*
*/
class wfWAFLexerToken {
private $type;
private $value;
private $line;
private $column;
/**
* wfWAFRuleToken constructor.
*
* @param $type
* @param $value
* @param $line
* @param $column
*/
public function __construct($type, $value, $line, $column) {
$this->setType($type);
$this->setValue($value);
$this->setLine($line);
$this->setColumn($column);
}
/**
* @return string
*/
public function getLowerCaseValue() {
return wfWAFUtils::strtolower($this->getValue());
}
/**
* @return string
*/
public function getUpperCaseValue() {
return wfWAFUtils::strtoupper($this->getValue());
}
/**
* @return mixed
*/
public function getType() {
return $this->type;
}
/**
* @param mixed $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @return mixed
*/
public function getValue() {
return $this->value;
}
/**
* @param mixed $value
*/
public function setValue($value) {
$this->value = $value;
}
/**
* @return mixed
*/
public function getLine() {
return $this->line;
}
/**
* @param mixed $line
*/
public function setLine($line) {
$this->line = $line;
}
/**
* @return mixed
*/
public function getColumn() {
return $this->column;
}
/**
* @param mixed $column
*/
public function setColumn($column) {
$this->column = $column;
}
}
class wfWAFParserSyntaxError extends wfWAFException {
private $parseLine;
private $parseColumn;
private $token;
/**
* @return mixed
*/
public function getToken() {
return $this->token;
}
/**
* @param mixed $token
*/
public function setToken($token) {
$this->token = $token;
}
/**
* @return mixed
*/
public function getParseLine() {
return $this->parseLine;
}
/**
* @param mixed $parseLine
*/
public function setParseLine($parseLine) {
$this->parseLine = $parseLine;
}
/**
* @return mixed
*/
public function getParseColumn() {
return $this->parseColumn;
}
/**
* @param mixed $parseColumn
*/
public function setParseColumn($parseColumn) {
$this->parseColumn = $parseColumn;
}
}
class wfWAFBaseParser {
protected $tokens;
protected $index;
/** @var wfWAFLexerInterface */
protected $lexer;
public function __construct($lexer) {
$this->lexer = $lexer;
}
/**
* @param wfWAFLexerToken $token
* @param mixed $type
* @return bool
*/
protected function isTokenOfType($token, $type) {
if (is_array($type)) {
return $token && in_array($token->getType(), $type);
}
return $token && $token->getType() === $type;
}
/**
* @param wfWAFLexerToken $token
* @param int $type
* @param string $message
* @throws wfWAFParserSyntaxError
*/
protected function expectTokenTypeEquals($token, $type, $message = 'Wordfence WAF Syntax Error: Unexpected %s found on line %d, column %d. Expected %s.') {
if ($token->getType() !== $type) {
$this->triggerSyntaxError($token, sprintf($message, $token->getType(),
$token->getLine(), $token->getColumn(), $type));
}
}
/**
* @param wfWAFLexerToken $token
* @param array $types
* @param string $message
* @throws wfWAFParserSyntaxError
*/
protected function expectTokenTypeInArray($token, $types, $message = 'Wordfence WAF Syntax Error: Unexpected %s found on line %d, column %d') {
if (!in_array($token->getType(), $types)) {
$this->triggerSyntaxError($token, sprintf($message, $token->getType(),
$token->getLine(), $token->getColumn()));
}
}
/**
* @param wfWAFLexerToken $token
* @param string $message
* @throws wfWAFParserSyntaxError
*/
protected function triggerSyntaxError($token, $message = 'Wordfence WAF Syntax Error: Unexpected %s %s found on line %d, column %d') {
$e = new wfWAFParserSyntaxError(sprintf($message, $token->getType(), $token->getValue(),
$token->getLine(), $token->getColumn()));
$e->setToken($token);
$e->setParseLine($token->getLine());
$e->setParseColumn($token->getColumn());
throw $e;
}
/**
* @return wfWAFLexerToken
*/
protected function currentToken() {
return $this->getToken($this->index);
}
/**
* @return bool|wfWAFLexerToken
*/
protected function nextToken() {
$this->index++;
return $this->getToken($this->index);
}
/**
* @param string $message
* @return wfWAFLexerToken
* @throws wfWAFParserSyntaxError
*/
protected function expectNextToken($message = 'Expected statement') {
$this->index++;
if ($token = $this->getToken($this->index)) {
return $token;
}
throw new wfWAFParserSyntaxError($message);
}
/**
* @param int $index
* @return mixed
*/
protected function getToken($index) {
if (is_array($this->tokens) && array_key_exists($index, $this->tokens)) {
return $this->tokens[$index];
}
if ($token = $this->getLexer()->nextToken()) {
$this->tokens[$index] = $token;
return $this->tokens[$index];
}
return false;
}
/**
* @return wfWAFLexerInterface
*/
public function getLexer() {
return $this->lexer;
}
/**
* @param wfWAFLexerInterface $lexer
*/
public function setLexer($lexer) {
$this->lexer = $lexer;
}
/**
* @return mixed
*/
public function getTokens() {
return $this->tokens;
}
/**
* @param mixed $tokens
*/
public function setTokens($tokens) {
$this->tokens = $tokens;
}
}
/**
*
*/
class wfWAFStringScanner {
private $string;
private $remainingStringCache;
private $length;
private $pointer;
private $remainingStringCachePointer;
private $prevPointer;
private $match;
private $captures;
/**
* wfWAFStringScanner constructor.
* @param $string
*/
public function __construct($string = null) {
if (is_string($string)) {
$this->setString($string);
}
}
/**
* @param $regex
* @return mixed
*/
public function scan($regex) {
$remaining = $this->getRemainingString();
if ($this->regexMatch($regex, $remaining, $matches)) {
$matchLen = wfWAFUtils::strlen($matches[0]);
if ($matchLen > 0 && wfWAFUtils::strpos($remaining, $matches[0]) === 0) {
return $this->setState($matches, $this->getPointer() + $matchLen, $this->getPointer());
}
}
return $this->setState();
}
/**
* @param $regex
* @return int|null
*/
public function skip($regex) {
return $this->scan($regex) ? wfWAFUtils::strlen($this->getMatch()) : null;
}
/**
* @return mixed
*/
public function scanChar() {
return $this->scan('/./s');
}
/**
* @param string $regex
* @return mixed
*/
public function check($regex) {
$remaining = $this->getRemainingString();
if ($this->regexMatch($regex, $remaining, $matches)) {
$matchLen = wfWAFUtils::strlen($matches[0]);
if ($matchLen > 0 && wfWAFUtils::strpos($remaining, $matches[0]) === 0) {
return $this->setState($matches);
}
}
return $this->setState();
}
/**
* @param string $regex
* @param string $remaining
* @param $matches
* @return int
*/
public function regexMatch($regex, $remaining, &$matches) {
// $startTime = microtime(true);
$result = preg_match($regex, $remaining, $matches);
// printf("%s took %f seconds\n", $regex, microtime(true) - $startTime);
return $result;
}
/**
* @return bool
*/
public function eos() {
return $this->getPointer() === $this->getLength();
}
/**
* @return string
*/
public function getRemainingString() {
$pointer = $this->getPointer();
if ($pointer === $this->remainingStringCachePointer && is_string($this->remainingStringCache)) {
return $this->remainingStringCache;
}
$this->remainingStringCache = wfWAFUtils::substr($this->getString(), $pointer);
$this->remainingStringCachePointer = $pointer;
return $this->remainingStringCache;
}
/**
* @return $this
*/
public function reset() {
$this->remainingStringCache = false;
$this->setState(array(), 0, 0);
return $this;
}
/**
* The current line of the scanned string.
*
* @return int
*/
public function getLine() {
if ($this->getPointer() + 1 > $this->getLength()) {
return wfWAFUtils::substr_count($this->getString(), "\n") + 1;
}
return wfWAFUtils::substr_count($this->getString(), "\n", 0, $this->getPointer() + 1) + 1;
}
/**
* The current column of the line of the scanned string.
*
* @return int
*/
public function getColumn() {
return $this->getPointer() - ((int) wfWAFUtils::strrpos(wfWAFUtils::substr($this->getString(), 0, $this->getPointer() + 1), "\n")) + 1;
}
/**
* @param array $matches
* @param int|null $pointer
* @param int|null $prevPointer
* @return mixed
*/
protected function setState($matches = array(), $pointer = null, $prevPointer = null) {
if ($pointer !== null) {
$this->setPointer($pointer);
}
if ($prevPointer !== null) {
$this->setPrevPointer($prevPointer);
}
if (is_array($matches)) {
$this->setCaptures(array_slice($matches, 1));
if (count($matches) > 0) {
$this->setMatch($matches[0]);
} else {
$this->setMatch(null);
}
} else {
$this->setMatch(null);
}
return $this->getMatch();
}
/**
* @return string
*/
public function getString() {
return $this->string;
}
/**
* @param string $string
* @throws InvalidArgumentException
*/
public function setString($string) {
if (!is_string($string)) {
throw new InvalidArgumentException(sprintf('String expected, got [%s]', gettype($string)));
}
$this->setLength(wfWAFUtils::strlen($string));
$this->string = $string;
$this->reset();
}
/**
* @return int
*/
public function getLength() {
return $this->length;
}
/**
* @param int $length
*/
protected function setLength($length) {
$this->length = $length;
}
/**
* @param int $length
*/
public function advancePointer($length) {
$this->setPointer($this->getPointer() + $length);
}
/**
* @return int
*/
public function getPointer() {
return $this->pointer;
}
/**
* @param int $pointer
*/
protected function setPointer($pointer) {
$this->pointer = $pointer;
}
/**
* @return int
*/
public function getPrevPointer() {
return $this->prevPointer;
}
/**
* @param int $prevPointer
*/
protected function setPrevPointer($prevPointer) {
$this->prevPointer = $prevPointer;
}
/**
* @return mixed
*/
public function getMatch() {
return $this->match;
}
/**
* @param mixed $match
*/
protected function setMatch($match) {
$this->match = $match;
}
/**
* @param null $index
* @return mixed
*/
public function getCaptures($index = null) {
if (is_numeric($index)) {
return isset($this->captures[$index]) ? $this->captures[$index] : null;
}
return $this->captures;
}
/**
* @param mixed $captures
*/
protected function setCaptures($captures) {
$this->captures = $captures;
}
}
}

View File

@@ -0,0 +1,870 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
require_once dirname(__FILE__) . '/lexer.php';
class wfWAFRuleParser extends wfWAFBaseParser {
/**
* @var wfWAF
*/
private $waf;
private $parenCount = 0;
/**
* wfWAFRuleParser constructor.
* @param $lexer
* @param wfWAF $waf
*/
public function __construct($lexer, $waf) {
parent::__construct($lexer);
$this->setWAF($waf);
}
/**
* @return array
* @throws wfWAFParserSyntaxError
* @throws wfWAFRuleParserSyntaxError
*/
public function parse() {
$rules = array();
$scores = array();
$blacklistedParams = array();
$whitelistedParams = array();
$variables = array();
$this->index = -1;
while ($token = $this->nextToken()) {
// Rule parsing
if ($token->getType() == wfWAFRuleLexer::T_RULE_START) {
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
$comparisonGroup = $this->parseConditional();
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_RULE_COMPARISON_END);
$action = $this->parseAction();
$rules[] = new wfWAFRule(
$this->getWAF(),
$action->getRuleID(),
$action->getType(),
$action->getCategory(),
$action->getScore(),
$action->getDescription(),
$action->getWhitelist(),
$action->getAction(),
$comparisonGroup
);
}
// Score/config parsing
if ($token->getType() == wfWAFRuleLexer::T_IDENTIFIER) {
switch ($token->getValue()) {
case 'scores':
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_DOT);
$scoreCategoryToken = $this->expectNextToken();
$this->expectTokenTypeEquals($scoreCategoryToken, wfWAFRuleLexer::T_IDENTIFIER);
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
$scoreToken = $this->expectNextToken();
$this->expectTokenTypeEquals($scoreToken, wfWAFRuleLexer::T_NUMBER_LITERAL);
$scores[$scoreCategoryToken->getValue()] = $scoreToken->getValue();
break;
case 'blacklistParam':
$blacklistedParams[] = $this->parseURLParams();
break;
case 'whitelistParam':
$whitelistedParams[] = $this->parseURLParams();
break;
default:
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
$valueToken = $this->expectNextToken();
$this->expectTokenTypeInArray($valueToken, array(
wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
wfWAFRuleLexer::T_NUMBER_LITERAL,
));
if ($valueToken->getType() === wfWAFRuleLexer::T_SINGLE_STRING_LITERAL) {
$value = wfWAFUtils::substr($valueToken->getValue(), 1, -1);
$value = str_replace("\\'", "'", $value);
} else if ($valueToken->getType() === wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL) {
$value = wfWAFUtils::substr($valueToken->getValue(), 1, -1);
$value = str_replace('\\"', '"', $value);
} else {
$value = $valueToken->getValue();
}
$variables[$token->getValue()] = new wfWAFRuleVariable($this->getWAF(), $token->getValue(), $value);
break;
}
}
}
return array(
'scores' => $scores,
'blacklistedParams' => $blacklistedParams,
'whitelistedParams' => $whitelistedParams,
'variables' => $variables,
'rules' => $rules,
);
}
/**
* @param array $vars
* @return string
*/
public function renderRules($vars) {
$rules = '';
if (array_key_exists('scores', $vars)) {
foreach ($vars['scores'] as $category => $score) {
// scores.sqli = 100
$rules .= sprintf("scores.%s = %d\n", $category, $score);
}
$rules .= "\n";
}
$params = array(
'blacklistParam' => 'blacklistedParams',
'whitelistParam' => 'whitelistedParams',
);
foreach ($params as $action => $key) {
if (array_key_exists($key, $vars)) {
/** @var wfWAFRuleParserURLParam $urlParam */
foreach ($vars[$key] as $urlParam) {
$rules .= $urlParam->renderRule($action) . "\n";
}
$rules .= "\n";
}
}
if (array_key_exists('variables', $vars)) {
/** @var wfWAFRuleVariable $variable */
foreach ($vars['variables'] as $variableName => $variable) {
$rules .= sprintf("%s = %s\n", $variable->renderRule(), $variable->renderValue());
}
$rules .= "\n";
}
if (array_key_exists('rules', $vars)) {
/** @var wfWAFRule $rule */
foreach ($vars['rules'] as $rule) {
$rules .= $rule->renderRule() . "\n";
}
$rules .= "\n";
}
return $rules;
}
/**
* @param int $index
* @return mixed
*/
public function getToken($index) {
if (is_array($this->tokens) && array_key_exists($index, $this->tokens)) {
return $this->tokens[$index];
}
if ($token = $this->getLexer()->nextToken()) {
$this->tokens[$index] = $token;
return $this->tokens[$index];
}
return false;
}
/**
* @return wfWAFRuleComparisonGroup
*/
private function parseConditional() {
$comparisonGroup = new wfWAFRuleComparisonGroup();
while ($token = $this->nextToken()) {
switch ($token->getType()) {
case wfWAFRuleLexer::T_IDENTIFIER:
$comparisonGroup->add($this->parseComparison());
break;
case wfWAFRuleLexer::T_COMPARISON_OPERATOR:
$comparisonGroup->add(new wfWAFRuleLogicalOperator($token->getValue()));
break;
case wfWAFRuleLexer::T_OPEN_PARENTHESIS:
$this->parenCount++;
$comparisonGroup->add($this->parseConditional());
break;
case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
if ($this->parenCount === 0) {
$this->index--;
return $comparisonGroup;
}
$this->parenCount--;
return $comparisonGroup;
}
}
return $comparisonGroup;
}
private function parseComparison() {
/**
* @var wfWAFLexerToken $actionToken
* @var wfWAFLexerToken $expectedToken
*/
$actionToken = $this->currentToken();
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
$value = $this->expectLiteral();
$subjects = array();
while (true) {
$commaToken = $this->nextToken();
if (!($commaToken && $commaToken->getType() === wfWAFRuleLexer::T_COMMA)) {
$this->index--;
break;
}
list($filters, $subject) = $this->parseFilters();
$subjects[] = new wfWAFRuleComparisonSubject($this->getWAF(), $subject, $filters);
}
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
$comparison = new wfWAFRuleComparison($this->getWAF(), $actionToken->getValue(), $value, $subjects);
return $comparison;
}
/**
* @return wfWAFRuleParserAction
*/
private function parseAction() {
$action = new wfWAFRuleParserAction();
$actionToken = $this->expectNextToken();
$this->expectTokenTypeEquals($actionToken, wfWAFRuleLexer::T_IDENTIFIER);
$action->setAction($actionToken->getValue());
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
while (true) {
$token = $this->expectNextToken();
switch ($token->getType()) {
case wfWAFRuleLexer::T_IDENTIFIER:
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
$valueToken = $this->expectNextToken();
$this->expectTokenTypeInArray($valueToken, array(
wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
wfWAFRuleLexer::T_NUMBER_LITERAL,
));
$action->set($token->getValue(), $valueToken->getValue());
break;
case wfWAFRuleLexer::T_COMMA:
break;
case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
break 2;
default:
$this->triggerSyntaxError($token, sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d',
$token->getType(), $token->getLine(), $token->getColumn()));
}
}
return $action;
}
private function parseFilters() {
$filters = array();
$subject = null;
do {
$globalToken = $this->expectNextToken();
$this->expectTokenTypeEquals($globalToken, wfWAFRuleLexer::T_IDENTIFIER);
$parenToken = $this->expectNextToken();
switch ($parenToken->getType()) {
case wfWAFRuleLexer::T_DOT:
$this->index -= 2;
$subject = $this->parseSubject();
break 2;
case wfWAFRuleLexer::T_OPEN_PARENTHESIS:
$filters[] = $globalToken->getValue();
break;
default:
$this->triggerSyntaxError($parenToken,
sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d.',
$parenToken->getType(), $parenToken->getLine(), $parenToken->getColumn()));
}
} while (true);
if ($subject === null) {
throw new wfWAFParserSyntaxError('No subject supplied to filter');
}
for ($i = 0; $i < count($filters); $i++) {
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
}
return array($filters, $subject);
}
/**
* @throws wfWAFParserSyntaxError
*/
private function parseSubject() {
$globalToken = $this->expectNextToken();
$this->expectTokenTypeEquals($globalToken, wfWAFRuleLexer::T_IDENTIFIER);
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_DOT);
$globalToken2 = $this->expectNextToken();
$this->expectTokenTypeEquals($globalToken2, wfWAFRuleLexer::T_IDENTIFIER);
$subject = array(
$globalToken->getValue() . '.' . $globalToken2->getValue(),
);
$savePoint = $this->index;
while (($property = $this->parsePropertyAccessor()) !== false) {
$subject[] = $property;
$savePoint = $this->index;
}
$this->index = $savePoint;
if (count($subject) === 1) {
list($subject) = $subject;
}
return $subject;
}
/**
* @return bool|mixed|string
* @throws wfWAFParserSyntaxError
*/
private function parsePropertyAccessor() {
$savePoint = $this->index;
$nextToken = $this->nextToken();
if ($this->isTokenOfType($nextToken, wfWAFRuleLexer::T_DOT)) {
$property = $this->expectNextToken();
$this->expectTokenTypeEquals($property, wfWAFRuleLexer::T_IDENTIFIER);
return $property->getValue();
} else if ($this->isTokenOfType($nextToken, wfWAFRuleLexer::T_OPEN_BRACKET)) {
$property = $this->expectLiteral();
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_BRACKET);
return $property;
}
$this->index = $savePoint;
return false;
}
/**
* @return wfWAFRuleParserURLParam
* @throws wfWAFParserSyntaxError
*/
private function parseURLParams() {
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
$urlParam = new wfWAFRuleParserURLParam();
while (true) {
$token = $this->expectNextToken();
switch ($token->getType()) {
case wfWAFRuleLexer::T_IDENTIFIER:
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_ASSIGNMENT);
if ($token->getValue() === 'url') {
$url = $this->expectLiteral();
$urlParam->setUrl($url);
} else if ($token->getValue() === 'param') {
$subject = $this->parseSubject();
$urlParam->setParam(wfWAFRuleComparison::getSubjectKey($subject));
} else if ($token->getValue() === 'rules') {
$rules = $this->expectLiteral();
$urlParam->setRules($rules);
} else if ($token->getValue() === 'conditional') {
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_OPEN_PARENTHESIS);
$conditional = $this->parseConditional();
$this->expectTokenTypeEquals($this->expectNextToken(), wfWAFRuleLexer::T_CLOSE_PARENTHESIS);
$urlParam->setConditional($conditional);
} else if ($token->getValue() === 'minVersion') {
$minVersion = $this->expectLiteral();
$urlParam->setMinVersion($minVersion);
}
break;
case wfWAFRuleLexer::T_COMMA:
break;
case wfWAFRuleLexer::T_CLOSE_PARENTHESIS:
break 2;
default:
$this->triggerSyntaxError($token, sprintf('Wordfence WAF Rules Syntax Error: Unexpected %s found on line %d, column %d',
$token->getType(), $token->getLine(), $token->getColumn()));
}
}
return $urlParam;
}
/**
* @return mixed|string
* @throws wfWAFRuleParserSyntaxError
*/
private function expectLiteral() {
$expectedToken = $this->expectNextToken();
$this->expectTokenTypeInArray($expectedToken, array(
wfWAFRuleLexer::T_SINGLE_STRING_LITERAL,
wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL,
wfWAFRuleLexer::T_IDENTIFIER,
wfWAFRuleLexer::T_NUMBER_LITERAL,
wfWAFRuleLexer::T_OPEN_BRACKET,
));
if ($expectedToken->getType() === wfWAFRuleLexer::T_SINGLE_STRING_LITERAL) {
// Remove quotes, strip slashes
$value = wfWAFUtils::substr($expectedToken->getValue(), 1, -1);
$value = str_replace("\\'", "'", $value);
} else if ($expectedToken->getType() === wfWAFRuleLexer::T_DOUBLE_STRING_LITERAL) {
// Remove quotes, strip slashes
$value = wfWAFUtils::substr($expectedToken->getValue(), 1, -1);
$value = str_replace('\\"', '"', $value);
} else if ($expectedToken->getType() === wfWAFRuleLexer::T_IDENTIFIER) {
// Remove quotes, strip slashes
$value = new wfWAFRuleVariable($this->getWAF(), $expectedToken->getValue());
} else if ($expectedToken->getType() === wfWAFRuleLexer::T_OPEN_BRACKET) {
$value = array();
while (true) {
$nextToken = $this->expectNextToken();
if ($nextToken->getType() === wfWAFRuleLexer::T_CLOSE_BRACKET) {
break;
}
if ($nextToken->getType() === wfWAFRuleLexer::T_COMMA) {
continue;
}
$this->index--;
$value[] = $this->expectLiteral();
}
} else {
$value = $expectedToken->getValue();
}
return $value;
}
/**
* @param wfWAFLexerToken $token
* @param string|array $value
* @return bool
*/
private function isIdentifierWithValue($token, $value) {
return $token && $token->getType() === wfWAFRuleLexer::T_IDENTIFIER &&
(is_array($value) ? in_array($token->getLowerCaseValue(), array_map('strtolower', $value)) :
$token->getLowerCaseValue() === strtolower($value));
}
/**
* @param wfWAFLexerToken $token
* @return bool
*/
protected function isCommentToken($token) {
return $token->getType() === wfWAFRuleLexer::T_MULTIPLE_LINE_COMMENT || $token->getType() === wfWAFRuleLexer::T_SINGLE_LINE_COMMENT;
}
/**
* @return wfWAF
*/
public function getWAF() {
return $this->waf;
}
/**
* @param wfWAF $waf
*/
public function setWAF($waf) {
$this->waf = $waf;
}
}
class wfWAFRuleParserAction {
private $ruleID;
private $type;
private $category;
private $score;
private $description;
private $whitelist = 1;
private $action;
/**
* @param string $param
* @param mixed $value
*/
public function set($param, $value) {
$propLinkTable = array(
'id' => 'ruleID',
);
if (array_key_exists($param, $propLinkTable)) {
$param = $propLinkTable[$param];
}
if (property_exists($this, $param)) {
$this->$param = trim($value, '\'"');
}
}
/**
* @return mixed
*/
public function getRuleID() {
return $this->ruleID;
}
/**
* @param mixed $ruleID
*/
public function setRuleID($ruleID) {
$this->ruleID = $ruleID;
}
/**
* @return mixed
*/
public function getType() {
return $this->type;
}
/**
* @param mixed $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @return mixed
*/
public function getCategory() {
return $this->category;
}
/**
* @param mixed $category
*/
public function setCategory($category) {
$this->category = $category;
}
/**
* @return mixed
*/
public function getScore() {
return $this->score;
}
/**
* @param mixed $score
*/
public function setScore($score) {
$this->score = $score;
}
/**
* @return mixed
*/
public function getDescription() {
return $this->description;
}
/**
* @param mixed $description
*/
public function setDescription($description) {
$this->description = $description;
}
/**
* @return mixed
*/
public function getWhitelist() {
return $this->whitelist;
}
/**
* @param mixed $whitelist
*/
public function setWhitelist($whitelist) {
$this->whitelist = $whitelist;
}
/**
* @return mixed
*/
public function getAction() {
return $this->action;
}
/**
* @param mixed $action
*/
public function setAction($action) {
$this->action = $action;
}
}
class wfWAFRuleParserURLParam {
/**
* @var string
*/
private $url;
/**
* @var string
*/
private $param;
/**
* @var null
*/
private $rules;
/**
* @var null
*/
private $conditional;
/**
* @var float
*/
private $minVersion;
/**
* @param string $param
* @param mixed $value
*/
public function set($param, $value) {
if (property_exists($this, $param)) {
$this->$param = trim($value, '\'"');
}
}
/**
* @param string $url
* @param string $param
* @param null $rules
*/
public function __construct($url = null, $param = null, $rules = null, $conditional = null, $minVersion = null) {
$this->url = $url;
$this->param = $param;
$this->rules = $rules;
$this->conditional = $conditional;
$this->minVersion = $minVersion;
}
/**
* Return format:
* blacklistParam(url='/\/uploadify\.php$/i', param=request.fileNames.Filedata, rules=[3, 14], conditional=(match('1', request.body.field)))
*
* @param string $action
* @return string
*/
public function renderRule($action) {
return sprintf('%s(url=%s, param=%s%s%s)', $action,
wfWAFRule::exportString($this->getUrl()),
$this->renderParam($this->getParam()),
$this->getRules() ? ', rules=[' . join(', ', array_map('intval', $this->getRules())) . ']' : '',
$this->getConditional() ? ', conditional=(' . $this->getConditional()->renderRule() . ')' : '');
//minVersion not included in re-rendering
}
/**
* @param string $param
* @return mixed
*/
private function renderParam($param) {
if (preg_match('/([a-zA-Z_][\\w_]*?\\.[a-zA-Z_][\\w_]*)(.*)/', $param, $matches)) {
list(, $global, $params) = $matches;
if (strlen($params) > 0) {
if (preg_match_all('/\\[([^\\]]*?)\\]/', $params, $matches)) {
$rendered = $global;
foreach ($matches[1] as $prop) {
$single = "'" . str_replace(array("'", '\\'), array("\\'", "\\\\"), $prop) . "'";
$double = '"' . str_replace(array('"', '\\'), array('\\"', "\\\\"), $prop) . '"';
$rendered .= sprintf('[%s]', strlen($single) <= strlen($double) ? $single : $double);
}
return $rendered;
}
}
}
return $param;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
*/
public function setUrl($url) {
$this->url = $url;
}
/**
* @return string
*/
public function getParam() {
return $this->param;
}
/**
* @param string $param
*/
public function setParam($param) {
$this->param = $param;
}
/**
* @return null
*/
public function getRules() {
return $this->rules;
}
/**
* @param null $rules
*/
public function setRules($rules) {
$this->rules = $rules;
}
/**
* @return null
*/
public function getConditional() {
return $this->conditional;
}
/**
* @param null $conditional
*/
public function setConditional($conditional) {
$this->conditional = $conditional;
}
/**
* @return float|null
*/
public function getMinVersion() {
return $this->minVersion;
}
/**
* @param float $minVersion
*/
public function setMinVersion($minVersion) {
$this->minVersion = $minVersion;
}
}
class wfWAFRuleParserSyntaxError extends wfWAFParserSyntaxError {
private $token;
/**
* @return mixed
*/
public function getToken() {
return $this->token;
}
/**
* @param mixed $token
*/
public function setToken($token) {
$this->token = $token;
}
}
class wfWAFRuleVariable {
/**
* @var string
*/
private $name;
/**
* @var mixed|null
*/
private $value;
/**
* @var wfWAF
*/
private $waf;
/**
* wfWAFRuleVariable constructor.
* @param wfWAF $waf
* @param string $name
* @param mixed $value
*/
public function __construct($waf, $name, $value = null) {
$this->waf = $waf;
$this->name = $name;
$this->value = $value;
}
public function __sleep() {
return array(
'name',
'value',
);
}
public function render() {
return sprintf('new %s($this, %s, %s)', get_class($this),
var_export($this->getName(), true), var_export($this->getValue(), true));
}
public function renderRule() {
return sprintf('%s', $this->getName());
}
public function renderValue() {
return wfWAFRule::exportString($this);
}
public function __toString() {
$value = $this->getValue();
if (is_string($value)) {
return $value;
}
return (string) $this->getWAF()->getVariable($this->getName());
}
/**
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @return mixed|null
*/
public function getValue() {
return $this->value;
}
/**
* @param mixed|null $value
*/
public function setValue($value) {
$this->value = $value;
}
/**
* @return wfWAF
*/
public function getWAF() {
return $this->waf;
}
/**
* @param wfWAF $waf
*/
public function setWAF($waf) {
$this->waf = $waf;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
interface wfWAFStorageInterface {
const IP_BLOCKS_ALL = PHP_INT_MAX;
const IP_BLOCKS_SINGLE = 1; //1 << 0
const IP_BLOCKS_BLACKLIST = 2; //1 << 1
public function hasPreviousAttackData($olderThan);
public function hasNewerAttackData($newerThan);
public function getAttackData();
public function getAttackDataArray();
public function getNewestAttackDataArray($newerThan);
public function truncateAttackData();
/**
* @param array $failedRules
* @param string $failedParamKey
* @param string $failedParamValue
* @param wfWAFRequestInterface $request
* @param mixed $_
* @return mixed
*/
public function logAttack($failedRules, $failedParamKey, $failedParamValue, $request, $_ = null);
/**
* @param int $timestamp
* @param string $ip
* @param bool $ssl
* @param array $failedRuleIDs
* @param wfWAFRequestInterface|string $request
* @param mixed $_
* @return mixed
*/
// public function logAttack($timestamp, $ip, $ssl, $failedRuleIDs, $request, $_ = null);
/**
* @param float $timestamp
* @param string $ip
* @return mixed
*/
public function blockIP($timestamp, $ip);
public function isIPBlocked($ip);
public function purgeIPBlocks($types = wfWAFStorageInterface::IP_BLOCKS_ALL);
public function getConfig($key, $default = null, $category = '');
public function setConfig($key, $value, $category = '');
public function unsetConfig($key, $category = '');
public function uninstall();
//optional public function fileList();
public function isInLearningMode();
public function isDisabled();
public function getRulesDSLCacheFile();
public function isAttackDataFull();
public function vacuum();
public function getRules();
public function setRules($rules);
public function needsInitialRules();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
class wfWAFView {
/**
* @var string
*/
protected $viewPath;
/**
* @var string
*/
protected $viewFileExtension = '.php';
/**
* @var string
*/
protected $view;
/**
* @var array
*/
protected $data;
/**
* @param string $view
* @param array $data
* @return wfWAFView
*/
public static function create($view, $data = array()) {
return new self($view, $data);
}
/**
* @param string $view
* @param array $data
*/
public function __construct($view, $data = array()) {
$this->viewPath = WFWAF_VIEW_PATH;
$this->view = $view;
$this->data = $data;
}
/**
* @return string
* @throws wfWAFViewNotFoundException
*/
public function render() {
$view = preg_replace('/\.{2,}/', '.', $this->view);
$viewPath = $this->viewPath . '/' . $view . $this->viewFileExtension;
if (!file_exists($viewPath)) {
throw new wfWAFViewNotFoundException('The view ' . $viewPath . ' does not exist or is not readable.');
}
extract($this->data, EXTR_SKIP);
if (!defined('WFWAF_VIEW_RENDERING')) { define('WFWAF_VIEW_RENDERING', true); }
ob_start();
/** @noinspection PhpIncludeInspection */
include $viewPath;
return ob_get_clean();
}
/**
* @return string
*/
public function __toString() {
try {
return $this->render();
} catch (wfWAFViewNotFoundException $e) {
return defined('WFWAF_DEBUG') && WFWAF_DEBUG ? $e->getMessage() : 'The view could not be loaded.';
}
}
/**
* @param $data
* @return $this
*/
public function addData($data) {
$this->data = array_merge($data, $this->data);
return $this;
}
/**
* @return array
*/
public function getData() {
return $this->data;
}
/**
* @param array $data
* @return $this
*/
public function setData($data) {
$this->data = $data;
return $this;
}
/**
* @return string
*/
public function getView() {
return $this->view;
}
/**
* @param string $view
* @return $this
*/
public function setView($view) {
$this->view = $view;
return $this;
}
/**
* Prevent POP
*/
public function __wakeup() {
$this->viewPath = WFWAF_VIEW_PATH;
$this->view = null;
$this->data = array();
$this->viewFileExtension = '.php';
}
}
class wfWAFViewNotFoundException extends Exception {
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
<?php
if (defined('WFWAF_VERSION') && !defined('WFWAF_RUN_COMPLETE')) {
/**
* Adaptation of WordPress's XML-RPC message parser so we can use it without loading the full environment
*
*/
class wfXMLRPCBody
{
var $header;
var $doctype;
var $message;
var $messageType; // methodCall / methodResponse / fault
var $faultCode;
var $faultString;
var $methodName;
var $params;
// Current variable stacks
var $_arraystructs = array(); // The stack used to keep track of the current array/struct
var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
var $_currentStructName = array(); // A stack as well
var $_param;
var $_value;
var $_currentTag;
var $_currentTagContents;
// The XML parser
var $_parser;
static function canParse() {
return function_exists('xml_parser_create');
}
/**
* PHP5 constructor.
*/
function __construct( $message )
{
$this->message =& $message;
}
function __toString() {
$output = '';
if (isset($this->header)) {
$output .= $this->header . "\n";
}
if (isset($this->doctype)) {
$output .= $this->doctype . "\n";
}
$output .= '<methodCall><methodName>' . htmlentities($this->methodName, ENT_XML1) . '</methodName><params>' . $this->_paramsToString($this->params) . '</params></methodCall>';
return $output;
}
function _paramsToString($params, $parentType = false) {
$output = '';
if (is_array($params)) {
foreach ($params as $key => $p) {
if (!$parentType) { //Top level
$output .= '<param><value>';
}
else if ($parentType == 'array') {
$output .= '<value>';
}
else if ($parentType == 'struct') {
$output .= '<member><name>' . htmlentities($key, ENT_XML1) . '</name><value>';
}
if ($p['tag'] == 'data') {
$output .= '<array><data>' . $this->_paramsToString($p['value'], 'array') . '</data></array>';
}
else if ($p['tag'] == 'struct') {
$output .= '<struct>' . $this->_paramsToString($p['value'], 'struct') . '</struct>';
}
else if ($p['tag'] == 'base64') {
$output .= '<base64>' . base64_encode($p['value']) . '</base64>';
}
else if ($p['tag'] == 'value') {
$output .= htmlentities($p['value'], ENT_XML1);
}
else if ($p['tag'] == 'dateTime.iso8601') {
$output .= $p['value']->getXml();
}
else {
$output .= '<' . $p['tag'] . '>' . htmlentities($p['value'], ENT_XML1) . '</' . $p['tag'] . '>';
}
if (!$parentType) { //Top level
$output .= '</value></param>';
}
else if ($parentType == 'array') {
$output .= '</value>';
}
else if ($parentType == 'struct') {
$output .= '</value></member>';
}
}
}
return $output;
}
function parse()
{
if (!function_exists( 'xml_parser_create')) {
return false;
}
// first remove the XML declaration
if (preg_match('/<\?xml.*?\?'.'>/s', substr( $this->message, 0, 100 ), $matches)) {
$this->header = $matches[0];
}
$replacement = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );
$this->message = trim( substr_replace( $this->message, $replacement, 0, 100 ) );
if ( '' == $this->message ) {
return false;
}
// Then remove the DOCTYPE
if (preg_match('/^<!DOCTYPE[^>]*+>/i', substr( $this->message, 0, 100 ), $matches)) {
$this->doctype = $matches[0];
}
$replacement = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );
$this->message = trim( substr_replace( $this->message, $replacement, 0, 200 ) );
if ( '' == $this->message ) {
return false;
}
// Check that the root tag is valid
$root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );
if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {
return false;
}
if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {
return false;
}
// Bail if there are too many elements to parse
$element_limit = 30000;
if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags in to account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->_parser, 'cdata');
// 256Kb, parse in chunks to avoid the RAM usage on very large messages
$chunk_size = 262144;
$final = false;
do {
if (strlen($this->message) <= $chunk_size) {
$final = true;
}
$part = substr($this->message, 0, $chunk_size);
$this->message = substr($this->message, $chunk_size);
if (!xml_parse($this->_parser, $part, $final)) {
return false;
}
if ($final) {
break;
}
} while (true);
xml_parser_free($this->_parser);
// Grab the error messages, if any
if ($this->messageType == 'fault') {
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
function tag_open($parser, $tag, $attr)
{
$this->_currentTagContents = '';
$this->currentTag = $tag;
switch($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
/* Deal with stacks of arrays and structs */
case 'data': // data is to all intents and puposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
}
function cdata($parser, $cdata)
{
$this->_currentTagContents .= $cdata;
}
function tag_close($parser, $tag)
{
$valueFlag = false;
switch($tag) {
case 'int':
case 'i4':
$value = (int)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'double':
$value = (double)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'string':
$value = (string)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new wfXMLRPCDate(trim($this->_currentTagContents));
$valueFlag = true;
break;
case 'value':
// "If no type is indicated, the type is string."
if (trim($this->_currentTagContents) != '') {
$value = (string)$this->_currentTagContents;
$valueFlag = true;
}
break;
case 'boolean':
$value = (boolean)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$valueFlag = true;
break;
/* Deal with stacks of arrays and structs */
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
break;
}
if ($valueFlag) {
if (count($this->_arraystructs) > 0) {
// Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
// Add to struct
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = array('tag' => $tag, 'value' => $value);
} else {
// Add to array
$this->_arraystructs[count($this->_arraystructs)-1][] = array('tag' => $tag, 'value' => $value);
}
} else {
// Just add as a parameter
$this->params[] = array('tag' => $tag, 'value' => $value);
}
}
$this->_currentTagContents = '';
}
}
class wfXMLRPCDate {
var $year;
var $month;
var $day;
var $hour;
var $minute;
var $second;
var $timezone;
function __construct( $time )
{
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
function parseTimestamp($timestamp)
{
$this->year = date('Y', $timestamp);
$this->month = date('m', $timestamp);
$this->day = date('d', $timestamp);
$this->hour = date('H', $timestamp);
$this->minute = date('i', $timestamp);
$this->second = date('s', $timestamp);
$this->timezone = '';
}
function parseIso($iso)
{
$this->year = substr($iso, 0, 4);
$this->month = substr($iso, 4, 2);
$this->day = substr($iso, 6, 2);
$this->hour = substr($iso, 9, 2);
$this->minute = substr($iso, 12, 2);
$this->second = substr($iso, 15, 2);
$this->timezone = substr($iso, 17);
}
function getIso()
{
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
}
function getXml()
{
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
}
function getTimestamp()
{
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
}
}
}

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzovUDp/qu7r6LT5d8dLL
H/87aRrCjUd6XtnG+afAPVfMKNp4u4L+UuYfw1RfpfquP/zLMGdfmJCUp/oJywkW
Rkqo+y7pDuqIFQ59dHvizmYQRvaZgvincBDpey5Ek9AFfB9fqYYnH9+eQw8eLdQi
h6Zsh8RsuxFM2BW6JD9Km7L5Lyxw9jU+lye7I3ICYtUOVxc3n3bJT2SiIwHK57pW
g/asJEUDiYQzsaa90YPOLdf1Ysz2rkgnCduQaEGz/RPhgUrmZfKwq8puEmkh7Yee
auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
1QIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,219 @@
<?php
if (!defined('WFWAF_VIEW_RENDERING')) { exit; }
/** @var wfWAF $waf */
/** @var wfWAFView $this */
/*
* IMPORTANT:
*
* If the form variables below change name or format, admin.ajaxWatcher.js in the main plugin also needs changed. It
* processes these to generate its whitelist button.
*/
$request = $waf->getRequest();
$headerString = '';
if (is_array($request->getHeaders())) {
foreach ($request->getHeaders() as $header => $value) {
switch (wfWAFUtils::strtolower($header)) {
case 'cookie':
$headerString .= 'Cookie: ' . trim($request->getCookieString()) . "\n";
break;
case 'host':
$headerString .= 'Host: ' . $request->getHost() . "\n";
break;
case 'authorization':
$hasAuth = true;
if ($request->getAuth()) {
$headerString .= 'Authorization: Basic <redacted>' . "\n";
}
break;
default:
$headerString .= $header . ': ' . $value . "\n";
break;
}
}
}
$payload = array('ip' => $request->getIP(), 'timestamp' => $request->getTimestamp(), 'headers' => $headerString, 'url' => $request->getProtocol() . '://' . $request->getHost() . $request->getPath(), 'home_url' => $waf->getStorageEngine()->getConfig('homeURL', '', 'synced'));
$payloadJSON = wfWAFUtils::json_encode($payload);
$shouldEncrypt = false;
if (function_exists('openssl_get_publickey') && function_exists('openssl_get_cipher_methods')) {
$ciphers = openssl_get_cipher_methods();
$shouldEncrypt = array_search('aes-256-cbc', $ciphers) !== false;
}
if ($shouldEncrypt) {
$keyData = file_get_contents(dirname(__FILE__) . '/../falsepositive.key');
$key = @openssl_get_publickey($keyData);
if ($key !== false) {
$symmetricKey = wfWAFUtils::random_bytes(32);
$iv = wfWAFUtils::random_bytes(16);
$encrypted = @openssl_encrypt($payloadJSON, 'aes-256-cbc', $symmetricKey, OPENSSL_RAW_DATA, $iv);
if ($encrypted !== false) {
$success = openssl_public_encrypt($symmetricKey, $symmetricKeyEncrypted, $key, OPENSSL_PKCS1_OAEP_PADDING);
if ($success) {
$message = $iv . $symmetricKeyEncrypted . $encrypted;
$signatureRaw = hash('sha256', $message, true);
$success = openssl_public_encrypt($signatureRaw, $signature, $key, OPENSSL_PKCS1_OAEP_PADDING);
if ($success) {
$payload = array('message' => bin2hex($message), 'signature' => bin2hex($signature));
$payloadJSON = wfWAFUtils::json_encode($payload);
}
}
}
}
}
$message = base64_encode($payloadJSON);
$payload = "-----BEGIN REPORT-----\n" . implode("\n", str_split($message, 60)) . "\n-----END REPORT-----";
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><?php wfWAFI18n::esc_html_e('403 Forbidden') ?></title>
<style>
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
h1, h2, h3, h4, h45, h6 {
font-weight: 500;
line-height: 1.1;
}
h1 { font-size: 36px; }
h2 { font-size: 30px; }
h3 { font-size: 24px; }
h4 { font-size: 18px; }
h5 { font-size: 14px; }
h6 { font-size: 12px; }
h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.btn {
background-color: #00709e;
border: 1px solid #09486C;
border-radius: 4px;
box-sizing: border-box;
color: #ffffff;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: normal;
letter-spacing: normal;
line-height: 20px;
margin: 5px 0px;
padding: 12px 6px;
text-align: center;
text-decoration: none;
vertical-align: middle;
white-space: nowrap;
word-spacing: 0px;
}
textarea {
display: block;
height: 48px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
font-family: monospace;
}
textarea:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee
}
.btn.disabled, .btn[disabled] {
background-color: #9f9fa0;
border: 1px solid #7E7E7F;
cursor: not-allowed;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
opacity: .65;
pointer-events: none;
}
</style>
</head>
<body>
<?php
if (!empty($errorNonce)) { echo '<!-- WFWAF NONCE: ' . htmlentities($errorNonce) . ' -->'; }
?>
<h1><?php wfWAFI18n::esc_html_e('403 Forbidden') ?></h1>
<h3><?php wfWAFI18n::esc_html_e('WHAT? Why am I seeing this?') ?></h3>
<p><?php wfWAFI18n::esc_html_e('Your access to this site was blocked by Wordfence, a security provider, who protects sites from malicious activity.') ?></p>
<p><?php wfWAFI18n::esc_html_e('If you believe Wordfence should be allowing you access to this site, please let them know using the steps below so they can investigate why this is happening.') ?></p>
<hr>
<h3><?php wfWAFI18n::esc_html_e('Reporting a Problem') ?></h3>
<h4><?php wfWAFI18n::esc_html_e('1. Please copy this text. You need to paste it into a form later.') ?></h4>
<p><textarea id="payload" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" cols="65"><?php echo htmlspecialchars($payload); ?></textarea></p>
<script type="application/javascript">
(function() {
var textarea = document.getElementById('payload');
var cs = window.getComputedStyle(textarea);
var lines = textarea.value.split('\n');
var height = 1 + lines.length;
var pixelHeight = Math.min(height * parseInt(cs.getPropertyValue('line-height')), 600);
textarea.style.height = pixelHeight + 'px';
textarea.addEventListener('focus', function() {
document.getElementById('reportButton').className = document.getElementById('reportButton').className.replace(new RegExp('(?:^|\\s)'+ 'disabled' + '(?:\\s|$)'), ' ');
document.getElementById('reportButton').href = 'ht' + 'tps:/' + '/user-reports.wordfence' + '.com';
});
})();
</script>
<h4><?php wfWAFI18n::esc_html_e('2. Click this button and you will be prompted to paste the text above.') ?></h4>
<p><a href="#" id="reportButton" class="btn disabled" target="_blank" rel="noopener noreferrer"><?php wfWAFI18n::esc_html_e('Report Problem') ?></a></p>
<p style="color: #999999;margin-top: 2rem;"><em><?php printf(wfWAFI18n::esc_html__('Generated by Wordfence at %s.'), gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())) ?><br><?php wfWAFI18n::esc_html_e('Your computer\'s time: ') ?><script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
</body>
</html>

View File

@@ -0,0 +1,450 @@
<?php
if (!defined('WFWAF_VIEW_RENDERING')) { exit; }
/** @var wfWAF $waf */
/** @var wfWAFView $this */
/*
* IMPORTANT:
*
* If the form variables below change name or format, admin.ajaxWatcher.js in the main plugin also needs changed. It
* processes these to generate its whitelist button.
*/
$method = wfWAFUtils::strtolower($waf->getRequest()->getMethod());
$urlParamsToWhitelist = array();
foreach ($waf->getFailedRules() as $paramKey => $categories) {
foreach ($categories as $category => $failedRules) {
foreach ($failedRules as $failedRule) {
/**
* @var wfWAFRule $rule
* @var wfWAFRuleComparisonFailure $failedComparison
*/
$rule = $failedRule['rule'];
$failedComparison = $failedRule['failedComparison'];
$urlParamsToWhitelist[] = array(
'path' => $waf->getRequest()->getPath(),
'paramKey' => $failedComparison->getParamKey(),
'ruleID' => $rule->getRuleID(),
);
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title><?php wfWAFI18n::esc_html_e('403 Forbidden') ?></title>
<style>
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.875rem;
line-height: 1.42857143;
color: #333;
background-color: #fff;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
a {
color:#00709e;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 200;
line-height: 1.1;
}
h1, .h1 { font-size: 3rem; }
h2, .h2 { font-size: 2.5rem; }
h3, .h3 { font-size: 1.5rem; }
h4, .h4 { font-size: 1rem; }
h5, .h5 { font-size: 0.875rem; }
h6, .h6 { font-size: 0.75rem; }
h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.wf-btn {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
text-transform: uppercase;
padding: .4rem 1rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
@media (min-width: 768px) {
.wf-btn {
padding: .5rem 1.25rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px
}
}
.wf-btn:focus,
.wf-btn.wf-focus,
.wf-btn:active:focus,
.wf-btn:active.wf-focus,
.wf-btn.wf-active:focus,
.wf-btn.wf-active.wf-focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px
}
.wf-btn:hover,
.wf-btn:focus,
.wf-btn.wf-focus {
color: #00709e;
text-decoration: none
}
.wf-btn:active,
.wf-btn.wf-active {
outline: 0;
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125)
}
.wf-btn.wf-disabled,
.wf-btn[disabled],
.wf-btn[readonly],
fieldset[disabled] .wf-btn {
cursor: not-allowed;
-webkit-box-shadow: none;
box-shadow: none
}
a.wf-btn {
text-decoration: none
}
a.wf-btn.wf-disabled,
fieldset[disabled] a.wf-btn {
cursor: not-allowed;
pointer-events: none
}
.wf-btn-default {
color: #00709e;
background-color: #fff;
border-color: #00709e
}
.wf-btn-default:focus,
.wf-btn-default.focus {
color: #00709e;
background-color: #e6e6e6;
border-color: #00161f
}
.wf-btn-default:hover {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active,
.wf-btn-default.active {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active:hover,
.wf-btn-default:active:focus,
.wf-btn-default:active.focus,
.wf-btn-default.active:hover,
.wf-btn-default.active:focus,
.wf-btn-default.active.focus {
color: #00709e;
background-color: #d4d4d4;
border-color: #00161f
}
.wf-btn-default:active,
.wf-btn-default.wf-active {
background-image: none
}
.wf-btn-default.wf-disabled,
.wf-btn-default[disabled],
.wf-btn-default[readonly],
fieldset[disabled] .wf-btn-default {
color: #777;
background-color: #fff;
border-color: #e2e2e2;
cursor: not-allowed
}
.wf-btn-default.wf-disabled:hover,
.wf-btn-default.wf-disabled:focus,
.wf-btn-default.wf-disabled.wf-focus,
.wf-btn-default[disabled]:hover,
.wf-btn-default[disabled]:focus,
.wf-btn-default[disabled].wf-focus,
.wf-btn-default[readonly]:hover,
.wf-btn-default[readonly]:focus,
.wf-btn-default[readonly].wf-focus,
fieldset[disabled] .wf-btn-default:hover,
fieldset[disabled] .wf-btn-default:focus,
fieldset[disabled] .wf-btn-default.wf-focus {
background-color: #fff;
border-color: #00709e
}
input[type="text"], input.wf-input-text {
text-align: left;
max-width: 200px;
height: 30px;
border-radius: 0;
border: 0;
background-color: #ffffff;
box-shadow: 0px 0px 0px 1px rgba(215,215,215,0.65);
padding: 0.25rem;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 4px solid #eee
}
p {
font-size: 1.4rem;
font-weight: 300;
}
p.medium, div.medium p {
font-size: 1.1rem;
}
p.small, div.small p {
font-size: 1rem;
}
.container {
max-width: 900px;
padding: 0 1rem;
margin: 0 auto;
}
.top-accent {
height: 25px;
background-color: #00709e;
}
.block-data {
width: 100%;
border-top: 6px solid #00709e;
}
.block-data tr:nth-child(odd) th, .block-data tr:nth-child(odd) td {
background-color: #eeeeee;
}
.block-data th, .block-data td {
text-align: left;
padding: 1rem;
font-size: 1.1rem;
}
.block-data th.reason, .block-data td.reason {
color: #930000;
}
.block-data th {
font-weight: 300;
}
.block-data td {
font-weight: 500;
}
.about {
margin-top: 2rem;
display: flex;
flex-direction: row;
align-items: stretch;
}
.about .badge {
flex-basis: 116px;
flex-grow: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
}
.about svg {
width: 100px;
height: 100px;
}
.about-text {
background-color: #00709e;
color: #ffffff;
padding: 1rem;
}
.about-text .h4 {
font-weight: 500;
margin-top: 0;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.about-text p {
font-size: 0.875rem;
font-weight: 200;
margin-top: 0.3rem;
margin-bottom: 0.3rem;
}
.about-text p:first-of-type {
margin-top: 0;
}
.about-text p:last-of-type {
margin-bottom: 0;
}
.st0{fill:#00709e;}
.st1{fill:#FFFFFF;}
.generated {
color: #999999;
margin-top: 2rem;
}
</style>
</head>
<body>
<?php if (!empty($errorNonce)) { echo '<!-- WFWAF NONCE: ' . htmlspecialchars($errorNonce) . ' -->'; } ?>
<div class="top-accent"></div>
<div class="container">
<h1><?php wfWAFI18n::esc_html_e('A potentially unsafe operation has been detected in your request to this site') ?></h1>
<p><?php wfWAFI18n::esc_html_e('Your access to this service has been limited. (HTTP response code 403)') ?></p>
<p><?php wfWAFI18n::esc_html_e('If you think you have been blocked in error, contact the owner of this site for assistance.') ?></p>
<?php if (!empty($customText)): ?>
<hr>
<div class="medium"><?php echo $customText; ?></div>
<?php endif; ?>
<?php if ($urlParamsToWhitelist): ?>
<hr>
<p><?php wfWAFI18n::esc_html_e('If you are an administrator and you are certain this is a false positive, you can automatically allowlist this request and repeat the same action.') ?></p>
<form id="whitelist-form" action="<?php echo htmlentities($waf->getRequest()->getPath(), ENT_QUOTES, 'utf-8') ?>" method="post">
<input type="hidden" name="wfwaf-false-positive-params" value="<?php echo htmlentities(wfWAFUtils::json_encode($urlParamsToWhitelist), ENT_QUOTES, 'utf-8') ?>">
<input type="hidden" name="wfwaf-false-positive-nonce" value="<?php echo htmlentities($waf->getAuthCookieValue('nonce', ''), ENT_QUOTES, 'utf-8') ?>">
<div id="whitelist-actions">
<p><label><input id="verified-false-positive-checkbox" type="checkbox" name="wfwaf-false-positive-verified" value="1"> <em><?php wfWAFI18n::esc_html_e('I am certain this is a false positive.') ?></em></label></p>
<p><button id="whitelist-button" type="submit"><?php wfWAFI18n::esc_html_e('Allowlist This Action') ?></button></p>
</div>
<p id="success" style="color: #35b13a; font-weight: bold; display: none"><em><?php wfWAFI18n::esc_html_e('All set! You can refresh the page to try this action again.') ?></em></p>
<p id="error" style="color: #dd422c; font-weight: bold; display: none"><em><?php wfWAFI18n::esc_html_e('Something went wrong allowlisting this request. You can try setting the Firewall Status to Learning Mode under Web Application Firewall in the Wordfence menu, and retry this same action.') ?></em></p>
</form>
<script>
var whitelistButton = document.getElementById('whitelist-button');
var verified = document.getElementById('verified-false-positive-checkbox');
verified.checked = false;
verified.onclick = function() {
whitelistButton.disabled = !this.checked;
};
verified.onclick();
document.getElementById('whitelist-form').onsubmit = function(evt) {
evt.preventDefault();
var request = new XMLHttpRequest();
request.addEventListener("load", function() {
if (this.status === 200 && this.responseText.indexOf('Successfully allowlisted') > -1) {
document.getElementById('whitelist-actions').style.display = 'none';
document.getElementById('success').style.display = 'block';
} else {
document.getElementById('error').style.display = 'block';
}
});
request.open("POST", this.action, true);
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
var inputs = this.querySelectorAll('input[name]');
var data = '';
for (var i = 0; i < inputs.length; i++) {
data += encodeURIComponent(inputs[i].name) + '=' + encodeURIComponent(inputs[i].value) + '&';
}
request.send(data);
return false;
};
</script>
<hr>
<?php endif ?>
<h2 class="h3"><?php wfWAFI18n::esc_html_e('Block Technical Data') ?></h2>
<table border="0" cellspacing="0" cellpadding="0" class="block-data">
<tr>
<th class="reason"><?php wfWAFI18n::esc_html_e('Block Reason:') ?></th>
<td class="reason"><?php wfWAFI18n::esc_html_e('A potentially unsafe operation has been detected in your request to this site') ?></td>
</tr>
<tr>
<th class="time"><?php wfWAFI18n::esc_html_e('Time:') ?></th>
<td class="time"><?php echo htmlspecialchars(gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())); ?></td>
</tr>
</table>
<div class="about">
<div class="badge">
<?php
$contents = file_get_contents(dirname(__FILE__) . '/../../../../../images/wf-error-badge.svg');
$contents = preg_replace('/^<\?xml.+?\?>\s*/i', '', $contents);
$contents = preg_replace('/^<!DOCTYPE.+?>\s*/i', '', $contents);
$contents = preg_replace('/<svg\s+xmlns="[^"]*"/i', '<svg', $contents);
echo $contents;
?>
</div>
<div class="about-text">
<h3 class="h4"><?php wfWAFI18n::esc_html_e('About Wordfence') ?></h3>
<p><?php wfWAFI18n::esc_html_e('Wordfence is a security plugin installed on over 3 million WordPress sites. The owner of this site is using Wordfence to manage access to their site.') ?></p>
<p><?php wfWAFI18n::esc_html_e('You can also read the documentation to learn about Wordfence\'s blocking tools, or visit wordfence.com to learn more about Wordfence.') ?></p>
</div>
</div>
<p class="documentation small"><?php wfWAFI18n::esc_html_e('Click here to learn more: '); ?><a href="https://www.wordfence.com/help/?query=locked-out" target="_blank" rel="noopener noreferrer"><?php wfWAFI18n::esc_html_e('Documentation') ?></a></p>
<p class="generated small"><em><?php printf(wfWAFI18n::esc_html__('Generated by Wordfence at %s.'), gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())) ?><br><?php wfWAFI18n::esc_html_e('Your computer\'s time: ') ?><script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,365 @@
<?php
if (!defined('WFWAF_VIEW_RENDERING')) { exit; }
?>
<!DOCTYPE html>
<html>
<head>
<title><?php wfWAFI18n::esc_html_e('403 Forbidden') ?></title>
<style>
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.875rem;
line-height: 1.42857143;
color: #333;
background-color: #fff;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
a {
color:#00709e;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 200;
line-height: 1.1;
}
h1, .h1 { font-size: 3rem; }
h2, .h2 { font-size: 2.5rem; }
h3, .h3 { font-size: 1.5rem; }
h4, .h4 { font-size: 1rem; }
h5, .h5 { font-size: 0.875rem; }
h6, .h6 { font-size: 0.75rem; }
h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.wf-btn {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
text-transform: uppercase;
padding: .4rem 1rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
@media (min-width: 768px) {
.wf-btn {
padding: .5rem 1.25rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px
}
}
.wf-btn:focus,
.wf-btn.wf-focus,
.wf-btn:active:focus,
.wf-btn:active.wf-focus,
.wf-btn.wf-active:focus,
.wf-btn.wf-active.wf-focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px
}
.wf-btn:hover,
.wf-btn:focus,
.wf-btn.wf-focus {
color: #00709e;
text-decoration: none
}
.wf-btn:active,
.wf-btn.wf-active {
outline: 0;
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125)
}
.wf-btn.wf-disabled,
.wf-btn[disabled],
.wf-btn[readonly],
fieldset[disabled] .wf-btn {
cursor: not-allowed;
-webkit-box-shadow: none;
box-shadow: none
}
a.wf-btn {
text-decoration: none
}
a.wf-btn.wf-disabled,
fieldset[disabled] a.wf-btn {
cursor: not-allowed;
pointer-events: none
}
.wf-btn-default {
color: #00709e;
background-color: #fff;
border-color: #00709e
}
.wf-btn-default:focus,
.wf-btn-default.focus {
color: #00709e;
background-color: #e6e6e6;
border-color: #00161f
}
.wf-btn-default:hover {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active,
.wf-btn-default.active {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active:hover,
.wf-btn-default:active:focus,
.wf-btn-default:active.focus,
.wf-btn-default.active:hover,
.wf-btn-default.active:focus,
.wf-btn-default.active.focus {
color: #00709e;
background-color: #d4d4d4;
border-color: #00161f
}
.wf-btn-default:active,
.wf-btn-default.wf-active {
background-image: none
}
.wf-btn-default.wf-disabled,
.wf-btn-default[disabled],
.wf-btn-default[readonly],
fieldset[disabled] .wf-btn-default {
color: #777;
background-color: #fff;
border-color: #e2e2e2;
cursor: not-allowed
}
.wf-btn-default.wf-disabled:hover,
.wf-btn-default.wf-disabled:focus,
.wf-btn-default.wf-disabled.wf-focus,
.wf-btn-default[disabled]:hover,
.wf-btn-default[disabled]:focus,
.wf-btn-default[disabled].wf-focus,
.wf-btn-default[readonly]:hover,
.wf-btn-default[readonly]:focus,
.wf-btn-default[readonly].wf-focus,
fieldset[disabled] .wf-btn-default:hover,
fieldset[disabled] .wf-btn-default:focus,
fieldset[disabled] .wf-btn-default.wf-focus {
background-color: #fff;
border-color: #00709e
}
input[type="text"], input.wf-input-text {
text-align: left;
max-width: 200px;
height: 30px;
border-radius: 0;
border: 0;
background-color: #ffffff;
box-shadow: 0px 0px 0px 1px rgba(215,215,215,0.65);
padding: 0.25rem;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 4px solid #eee
}
p {
font-size: 1.4rem;
font-weight: 300;
}
p.medium, div.medium p {
font-size: 1.1rem;
}
p.small, div.small p {
font-size: 1rem;
}
.container {
max-width: 900px;
padding: 0 1rem;
margin: 0 auto;
}
.top-accent {
height: 25px;
background-color: #00709e;
}
.block-data {
width: 100%;
border-top: 6px solid #00709e;
}
.block-data tr:nth-child(odd) th, .block-data tr:nth-child(odd) td {
background-color: #eeeeee;
}
.block-data th, .block-data td {
text-align: left;
padding: 1rem;
font-size: 1.1rem;
}
.block-data th.reason, .block-data td.reason {
color: #930000;
}
.block-data th {
font-weight: 300;
}
.block-data td {
font-weight: 500;
}
.about {
margin-top: 2rem;
display: flex;
flex-direction: row;
align-items: stretch;
}
.about .badge {
flex-basis: 116px;
flex-grow: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
}
.about svg {
width: 100px;
height: 100px;
}
.about-text {
background-color: #00709e;
color: #ffffff;
padding: 1rem;
}
.about-text .h4 {
font-weight: 500;
margin-top: 0;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.about-text p {
font-size: 0.875rem;
font-weight: 200;
margin-top: 0.3rem;
margin-bottom: 0.3rem;
}
.about-text p:first-of-type {
margin-top: 0;
}
.about-text p:last-of-type {
margin-bottom: 0;
}
.st0{fill:#00709e;}
.st1{fill:#FFFFFF;}
.generated {
color: #999999;
margin-top: 2rem;
}
</style>
</head>
<body>
<?php if (!empty($errorNonce)) { echo '<!-- WFWAF NONCE: ' . htmlspecialchars($errorNonce) . ' -->'; } ?>
<div class="top-accent"></div>
<div class="container">
<h1><?php wfWAFI18n::esc_html_e('A potentially unsafe operation has been detected in your request to this site') ?></h1>
<p><?php wfWAFI18n::esc_html_e('Your access to this service has been limited. (HTTP response code 403)') ?></p>
<p><?php wfWAFI18n::esc_html_e('If you think you have been blocked in error, contact the owner of this site for assistance.') ?></p>
<?php if (!empty($customText)): ?>
<hr>
<div class="medium"><?php echo $customText; ?></div>
<?php endif; ?>
<h2 class="h3"><?php wfWAFI18n::esc_html_e('Block Technical Data') ?></h2>
<table border="0" cellspacing="0" cellpadding="0" class="block-data">
<tr>
<th class="reason"><?php wfWAFI18n::esc_html_e('Block Reason:') ?></th>
<td class="reason"><?php wfWAFI18n::esc_html_e('A potentially unsafe operation has been detected in your request to this site') ?></td>
</tr>
<tr>
<th class="time"><?php wfWAFI18n::esc_html_e('Time:') ?></th>
<td class="time"><?php echo htmlspecialchars(gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())); ?></td>
</tr>
</table>
<div class="about">
<div class="badge">
<?php
$contents = file_get_contents(dirname(__FILE__) . '/../../../../../images/wf-error-badge.svg');
$contents = preg_replace('/^<\?xml.+?\?>\s*/i', '', $contents);
$contents = preg_replace('/^<!DOCTYPE.+?>\s*/i', '', $contents);
$contents = preg_replace('/<svg\s+xmlns="[^"]*"/i', '<svg', $contents);
echo $contents;
?>
</div>
<div class="about-text">
<h3 class="h4"><?php wfWAFI18n::esc_html_e('About Wordfence') ?></h3>
<p><?php wfWAFI18n::esc_html_e('Wordfence is a security plugin installed on over 3 million WordPress sites. The owner of this site is using Wordfence to manage access to their site.') ?></p>
<p><?php wfWAFI18n::esc_html_e('You can also read the documentation to learn about Wordfence\'s blocking tools, or visit wordfence.com to learn more about Wordfence.') ?></p>
</div>
</div>
<p class="documentation small"><?php wfWAFI18n::esc_html_e('Click here to learn more: ') ?><a href="https://www.wordfence.com/help/?query=locked-out" target="_blank" rel="noopener noreferrer"><?php wfWAFI18n::esc_html_e('Documentation'); ?></a></p>
<p class="generated small"><em><?php printf(wfWAFI18n::esc_html__('Generated by Wordfence at %s.'), gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())); ?><br><?php wfWAFI18n::esc_html_e('Your computer\'s time: ') ?><script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,392 @@
<?php
if (!defined('WFWAF_VIEW_RENDERING')) { exit; }
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><?php wfWAFI18n::esc_html_e('Your access to this site has been limited by the site owner') ?></title>
<style>
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.875rem;
line-height: 1.42857143;
color: #333;
background-color: #fff;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
a {
color:#00709e;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 200;
line-height: 1.1;
}
h1, .h1 { font-size: 3rem; }
h2, .h2 { font-size: 2.5rem; }
h3, .h3 { font-size: 1.5rem; }
h4, .h4 { font-size: 1rem; }
h5, .h5 { font-size: 0.875rem; }
h6, .h6 { font-size: 0.75rem; }
h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.wf-btn {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
text-transform: uppercase;
padding: .4rem 1rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
@media (min-width: 768px) {
.wf-btn {
padding: .5rem 1.25rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px
}
}
.wf-btn:focus,
.wf-btn.wf-focus,
.wf-btn:active:focus,
.wf-btn:active.wf-focus,
.wf-btn.wf-active:focus,
.wf-btn.wf-active.wf-focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px
}
.wf-btn:hover,
.wf-btn:focus,
.wf-btn.wf-focus {
color: #00709e;
text-decoration: none
}
.wf-btn:active,
.wf-btn.wf-active {
outline: 0;
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125)
}
.wf-btn.wf-disabled,
.wf-btn[disabled],
.wf-btn[readonly],
fieldset[disabled] .wf-btn {
cursor: not-allowed;
-webkit-box-shadow: none;
box-shadow: none
}
a.wf-btn {
text-decoration: none
}
a.wf-btn.wf-disabled,
fieldset[disabled] a.wf-btn {
cursor: not-allowed;
pointer-events: none
}
.wf-btn-default {
color: #00709e;
background-color: #fff;
border-color: #00709e
}
.wf-btn-default:focus,
.wf-btn-default.focus {
color: #00709e;
background-color: #e6e6e6;
border-color: #00161f
}
.wf-btn-default:hover {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active,
.wf-btn-default.active {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active:hover,
.wf-btn-default:active:focus,
.wf-btn-default:active.focus,
.wf-btn-default.active:hover,
.wf-btn-default.active:focus,
.wf-btn-default.active.focus {
color: #00709e;
background-color: #d4d4d4;
border-color: #00161f
}
.wf-btn-default:active,
.wf-btn-default.wf-active {
background-image: none
}
.wf-btn-default.wf-disabled,
.wf-btn-default[disabled],
.wf-btn-default[readonly],
fieldset[disabled] .wf-btn-default {
color: #777;
background-color: #fff;
border-color: #e2e2e2;
cursor: not-allowed
}
.wf-btn-default.wf-disabled:hover,
.wf-btn-default.wf-disabled:focus,
.wf-btn-default.wf-disabled.wf-focus,
.wf-btn-default[disabled]:hover,
.wf-btn-default[disabled]:focus,
.wf-btn-default[disabled].wf-focus,
.wf-btn-default[readonly]:hover,
.wf-btn-default[readonly]:focus,
.wf-btn-default[readonly].wf-focus,
fieldset[disabled] .wf-btn-default:hover,
fieldset[disabled] .wf-btn-default:focus,
fieldset[disabled] .wf-btn-default.wf-focus {
background-color: #fff;
border-color: #00709e
}
input[type="text"], input.wf-input-text {
text-align: left;
max-width: 200px;
height: 30px;
border-radius: 0;
border: 0;
background-color: #ffffff;
box-shadow: 0px 0px 0px 1px rgba(215,215,215,0.65);
padding: 0.25rem;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 4px solid #eee
}
p {
font-size: 1.4rem;
font-weight: 300;
}
p.medium, div.medium p {
font-size: 1.1rem;
}
p.small, div.small p {
font-size: 1rem;
}
.container {
max-width: 900px;
padding: 0 1rem;
margin: 0 auto;
}
.top-accent {
height: 25px;
background-color: #00709e;
}
.block-data {
width: 100%;
border-top: 6px solid #00709e;
}
.block-data tr:nth-child(odd) th, .block-data tr:nth-child(odd) td {
background-color: #eeeeee;
}
.block-data th, .block-data td {
text-align: left;
padding: 1rem;
font-size: 1.1rem;
}
.block-data th.reason, .block-data td.reason {
color: #930000;
}
.block-data th {
font-weight: 300;
}
.block-data td {
font-weight: 500;
}
.about {
margin-top: 2rem;
display: flex;
flex-direction: row;
align-items: stretch;
}
.about .badge {
flex-basis: 116px;
flex-grow: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
}
.about svg {
width: 100px;
height: 100px;
}
.about-text {
background-color: #00709e;
color: #ffffff;
padding: 1rem;
}
.about-text .h4 {
font-weight: 500;
margin-top: 0;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.about-text p {
font-size: 0.875rem;
font-weight: 200;
margin-top: 0.3rem;
margin-bottom: 0.3rem;
}
.about-text p:first-of-type {
margin-top: 0;
}
.about-text p:last-of-type {
margin-bottom: 0;
}
.st0{fill:#00709e;}
.st1{fill:#FFFFFF;}
.generated {
color: #999999;
margin-top: 2rem;
}
</style>
</head>
<body>
<?php if (!empty($errorNonce)) { echo '<!-- WFWAF NONCE: ' . htmlspecialchars($errorNonce) . ' -->'; } ?>
<div class="top-accent"></div>
<div class="container">
<h1><?php wfWAFI18n::esc_html_e('Your access to this site has been temporarily limited by the site owner') ?></h1>
<p><?php wfWAFI18n::esc_html_e('Your access to this service has been temporarily limited. Please try again in a few minutes. (HTTP response code 503)') ?></p>
<p><?php wfWAFI18n::esc_html_e('If you think you have been blocked in error, contact the owner of this site for assistance.') ?></p>
<?php if (!empty($customText)): ?>
<hr>
<div class="medium"><?php echo $customText; ?></div>
<?php endif; ?>
<?php if (!empty($homeURL)): ?>
<hr>
<ul>
<li><a href="<?php echo $homeURL; ?>"><?php wfWAFI18n::esc_html_e('Return to the site home page') ?></a></li>
</ul>
<?php
endif;
$nonce = $waf->createNonce('wf-form');
if (!empty($siteURL) && !empty($nonce)) : ?>
<hr>
<p class="medium"><?php wfWAFI18n::esc_html_e('If you are a WordPress user with administrative privileges on this site, please enter your email address in the box below and click "Send". You will then receive an email that helps you regain access.') ?></p>
<form method="POST" id="unlock-form" action="#">
<input type="hidden" name="nonce" value="<?php echo $nonce; ?>">
<input type="text" size="50" name="email" id="unlock-email" value="" maxlength="255" placeholder="email@example.com">&nbsp;&nbsp;<input type="submit" class="wf-btn wf-btn-default" id="unlock-submit" name="s" value="<?php wfWAFI18n::esc_html_e('Send Unlock Email') ?>" disabled>
</form>
<script type="application/javascript">
(function() {
var textfield = document.getElementById('unlock-email');
textfield.addEventListener('focus', function() {
document.getElementById('unlock-form').action = "<?php echo rtrim($siteURL, '/') . '/'; ?>" + "?_wfsf=unlockEmail";
document.getElementById('unlock-submit').disabled = false;
});
})();
</script>
<?php endif; ?>
<h2 class="h3"><?php wfWAFI18n::esc_html_e('Block Technical Data') ?></h2>
<table border="0" cellspacing="0" cellpadding="0" class="block-data">
<tr>
<th class="reason"><?php wfWAFI18n::esc_html_e('Block Reason:') ?></th>
<td class="reason"><?php wfWAFI18n::esc_html_e('You have been temporarily locked out of this system. This means that you will not be able to log in for a while.') ?></td>
</tr>
<tr>
<th class="time"><?php wfWAFI18n::esc_html_e('Time:') ?></th>
<td class="time"><?php echo htmlspecialchars(gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())); ?></td>
</tr>
</table>
<div class="about">
<div class="badge">
<?php
$contents = file_get_contents(dirname(__FILE__) . '/../../../../../images/wf-error-badge.svg');
$contents = preg_replace('/^<\?xml.+?\?>\s*/i', '', $contents);
$contents = preg_replace('/^<!DOCTYPE.+?>\s*/i', '', $contents);
$contents = preg_replace('/<svg\s+xmlns="[^"]*"/i', '<svg', $contents);
echo $contents;
?>
</div>
<div class="about-text">
<h3 class="h4"><?php wfWAFI18n::esc_html_e('About Wordfence') ?></h3>
<p><?php wfWAFI18n::esc_html_e('Wordfence is a security plugin installed on over 3 million WordPress sites. The owner of this site is using Wordfence to manage access to their site.') ?></p>
<p><?php wfWAFI18n::esc_html_e('You can also read the documentation to learn about Wordfence\'s blocking tools, or visit wordfence.com to learn more about Wordfence.') ?></p>
</div>
</div>
<p class="documentation small"><?php wfWAFI18n::esc_html_e('Click here to learn more: '); ?><a href="https://www.wordfence.com/help/?query=locked-out" target="_blank" rel="noopener noreferrer"><?php wfWAFI18n::esc_html_e('Documentation'); ?></a></p>
<p class="generated small"><em><?php printf(wfWAFI18n::esc_html__('Generated by Wordfence at %s.'), gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())) ?><br><?php wfWAFI18n::esc_html_e('Your computer\'s time: ') ?><script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,386 @@
<?php
if (!defined('WFWAF_VIEW_RENDERING')) { exit; }
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><?php wfWAFI18n::esc_html_e('Your access to this site has been limited by the site owner') ?></title>
<style>
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 0.875rem;
line-height: 1.42857143;
color: #333;
background-color: #fff;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
a {
color:#00709e;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 200;
line-height: 1.1;
}
h1, .h1 { font-size: 3rem; }
h2, .h2 { font-size: 2.5rem; }
h3, .h3 { font-size: 1.5rem; }
h4, .h4 { font-size: 1rem; }
h5, .h5 { font-size: 0.875rem; }
h6, .h6 { font-size: 0.75rem; }
h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.wf-btn {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
text-transform: uppercase;
padding: .4rem 1rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
@media (min-width: 768px) {
.wf-btn {
padding: .5rem 1.25rem;
font-size: .875rem;
line-height: 1.3125rem;
border-radius: 4px
}
}
.wf-btn:focus,
.wf-btn.wf-focus,
.wf-btn:active:focus,
.wf-btn:active.wf-focus,
.wf-btn.wf-active:focus,
.wf-btn.wf-active.wf-focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px
}
.wf-btn:hover,
.wf-btn:focus,
.wf-btn.wf-focus {
color: #00709e;
text-decoration: none
}
.wf-btn:active,
.wf-btn.wf-active {
outline: 0;
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125)
}
.wf-btn.wf-disabled,
.wf-btn[disabled],
.wf-btn[readonly],
fieldset[disabled] .wf-btn {
cursor: not-allowed;
-webkit-box-shadow: none;
box-shadow: none
}
a.wf-btn {
text-decoration: none
}
a.wf-btn.wf-disabled,
fieldset[disabled] a.wf-btn {
cursor: not-allowed;
pointer-events: none
}
.wf-btn-default {
color: #00709e;
background-color: #fff;
border-color: #00709e
}
.wf-btn-default:focus,
.wf-btn-default.focus {
color: #00709e;
background-color: #e6e6e6;
border-color: #00161f
}
.wf-btn-default:hover {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active,
.wf-btn-default.active {
color: #00709e;
background-color: #e6e6e6;
border-color: #004561
}
.wf-btn-default:active:hover,
.wf-btn-default:active:focus,
.wf-btn-default:active.focus,
.wf-btn-default.active:hover,
.wf-btn-default.active:focus,
.wf-btn-default.active.focus {
color: #00709e;
background-color: #d4d4d4;
border-color: #00161f
}
.wf-btn-default:active,
.wf-btn-default.wf-active {
background-image: none
}
.wf-btn-default.wf-disabled,
.wf-btn-default[disabled],
.wf-btn-default[readonly],
fieldset[disabled] .wf-btn-default {
color: #777;
background-color: #fff;
border-color: #e2e2e2;
cursor: not-allowed
}
.wf-btn-default.wf-disabled:hover,
.wf-btn-default.wf-disabled:focus,
.wf-btn-default.wf-disabled.wf-focus,
.wf-btn-default[disabled]:hover,
.wf-btn-default[disabled]:focus,
.wf-btn-default[disabled].wf-focus,
.wf-btn-default[readonly]:hover,
.wf-btn-default[readonly]:focus,
.wf-btn-default[readonly].wf-focus,
fieldset[disabled] .wf-btn-default:hover,
fieldset[disabled] .wf-btn-default:focus,
fieldset[disabled] .wf-btn-default.wf-focus {
background-color: #fff;
border-color: #00709e
}
input[type="text"], input.wf-input-text {
text-align: left;
max-width: 200px;
height: 30px;
border-radius: 0;
border: 0;
background-color: #ffffff;
box-shadow: 0px 0px 0px 1px rgba(215,215,215,0.65);
padding: 0.25rem;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 4px solid #eee
}
p {
font-size: 1.4rem;
font-weight: 300;
}
p.medium, div.medium p {
font-size: 1.1rem;
}
p.small, div.small p {
font-size: 1rem;
}
.container {
max-width: 900px;
padding: 0 1rem;
margin: 0 auto;
}
.top-accent {
height: 25px;
background-color: #00709e;
}
.block-data {
width: 100%;
border-top: 6px solid #00709e;
}
.block-data tr:nth-child(odd) th, .block-data tr:nth-child(odd) td {
background-color: #eeeeee;
}
.block-data th, .block-data td {
text-align: left;
padding: 1rem;
font-size: 1.1rem;
}
.block-data th.reason, .block-data td.reason {
color: #930000;
}
.block-data th {
font-weight: 300;
}
.block-data td {
font-weight: 500;
}
.about {
margin-top: 2rem;
display: flex;
flex-direction: row;
align-items: stretch;
}
.about .badge {
flex-basis: 116px;
flex-grow: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
}
.about svg {
width: 100px;
height: 100px;
}
.about-text {
background-color: #00709e;
color: #ffffff;
padding: 1rem;
}
.about-text .h4 {
font-weight: 500;
margin-top: 0;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.about-text p {
font-size: 0.875rem;
font-weight: 200;
margin-top: 0.3rem;
margin-bottom: 0.3rem;
}
.about-text p:first-of-type {
margin-top: 0;
}
.about-text p:last-of-type {
margin-bottom: 0;
}
.st0{fill:#00709e;}
.st1{fill:#FFFFFF;}
.generated {
color: #999999;
margin-top: 2rem;
}
</style>
</head>
<body>
<?php if (!empty($errorNonce)) { echo '<!-- WFWAF NONCE: ' . htmlspecialchars($errorNonce) . ' -->'; } ?>
<div class="top-accent"></div>
<div class="container">
<h1><?php wfWAFI18n::esc_html_e('Your access to this site has been limited by the site owner') ?></h1>
<p><?php wfWAFI18n::esc_html_e('Your access to this service has been limited. (HTTP response code 503)') ?></p>
<p><?php wfWAFI18n::esc_html_e('If you think you have been blocked in error, contact the owner of this site for assistance.') ?></p>
<?php if (!empty($customText)): ?>
<hr>
<div class="medium"><?php echo $customText; ?></div>
<?php endif; ?>
<?php
$nonce = $waf->createNonce('wf-form');
if (!empty($siteURL) && !empty($nonce)) : ?>
<hr>
<p class="medium"><?php wfWAFI18n::esc_html_e('If you are a WordPress user with administrative privileges on this site, please enter your email address in the box below and click "Send". You will then receive an email that helps you regain access.') ?></p>
<form method="POST" id="unlock-form" action="#">
<input type="hidden" name="nonce" value="<?php echo $nonce; ?>">
<input type="text" size="50" name="email" id="unlock-email" value="" maxlength="255" placeholder="email@example.com">&nbsp;&nbsp;<input type="submit" class="wf-btn wf-btn-default" id="unlock-submit" name="s" value="<?php echo htmlentities(wfWAFI18n::esc_html__('Send Unlock Email'), ENT_QUOTES, 'utf-8') ?>" disabled>
</form>
<script type="application/javascript">
(function() {
var textfield = document.getElementById('unlock-email');
textfield.addEventListener('focus', function() {
document.getElementById('unlock-form').action = "<?php echo rtrim($siteURL, '/') . '/'; ?>" + "?_wfsf=unlockEmail";
document.getElementById('unlock-submit').disabled = false;
});
})();
</script>
<?php endif; ?>
<h2 class="h3"><?php wfWAFI18n::esc_html_e('Block Technical Data') ?></h2>
<table border="0" cellspacing="0" cellpadding="0" class="block-data">
<tr>
<th class="reason"><?php wfWAFI18n::esc_html_e('Block Reason:') ?></th>
<td class="reason"><?php echo htmlspecialchars($reason); ?></td>
</tr>
<tr>
<th class="time"><?php wfWAFI18n::esc_html_e('Time:') ?></th>
<td class="time"><?php echo htmlspecialchars(gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())); ?></td>
</tr>
</table>
<div class="about">
<div class="badge">
<?php
$contents = file_get_contents(dirname(__FILE__) . '/../../../../../images/wf-error-badge.svg');
$contents = preg_replace('/^<\?xml.+?\?>\s*/i', '', $contents);
$contents = preg_replace('/^<!DOCTYPE.+?>\s*/i', '', $contents);
$contents = preg_replace('/<svg\s+xmlns="[^"]*"/i', '<svg', $contents);
echo $contents;
?>
</div>
<div class="about-text">
<h3 class="h4"><?php wfWAFI18n::esc_html_e('About Wordfence') ?></h3>
<p><?php wfWAFI18n::esc_html_e('Wordfence is a security plugin installed on over 3 million WordPress sites. The owner of this site is using Wordfence to manage access to their site.') ?></p>
<p><?php wfWAFI18n::esc_html_e('You can also read the documentation to learn about Wordfence\'s blocking tools, or visit wordfence.com to learn more about Wordfence.') ?></p>
</div>
</div>
<p class="documentation small"><?php wfWAFI18n::esc_html_e('Click here to learn more: '); ?><a href="https://www.wordfence.com/help/?query=locked-out" target="_blank" rel="noopener noreferrer"><?php wfWAFI18n::esc_html_e('Documentation'); ?></a></p>
<p class="generated small"><em><?php printf(wfWAFI18n::esc_html__('Generated by Wordfence at %s.'), gmdate('D, j M Y G:i:s T', wfWAFUtils::normalizedTime())) ?><br><?php wfWAFI18n::esc_html_e('Your computer\'s time: ') ?><script type="application/javascript">document.write(new Date().toUTCString());</script>.</em></p>
</div>
</body>
</html>