Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/UserCountry/GeoIPAutoUpdater.php')
-rw-r--r--plugins/UserCountry/GeoIPAutoUpdater.php731
1 files changed, 0 insertions, 731 deletions
diff --git a/plugins/UserCountry/GeoIPAutoUpdater.php b/plugins/UserCountry/GeoIPAutoUpdater.php
deleted file mode 100644
index bee89ac8fd..0000000000
--- a/plugins/UserCountry/GeoIPAutoUpdater.php
+++ /dev/null
@@ -1,731 +0,0 @@
-<?php
-/**
- * Piwik - free/libre analytics platform
- *
- * @link https://matomo.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
-namespace Piwik\Plugins\UserCountry;
-
-require_once PIWIK_INCLUDE_PATH . "/core/ScheduledTask.php"; // for the tracker which doesn't include this file
-
-use Exception;
-use Piwik\Common;
-use Piwik\Container\StaticContainer;
-use Piwik\Date;
-use Piwik\Http;
-use Piwik\Log;
-use Piwik\Option;
-use Piwik\Piwik;
-use Piwik\Plugins\UserCountry\LocationProvider\GeoIp\Php;
-use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
-use Piwik\Plugins\UserCountry\LocationProvider;
-use Piwik\Scheduler\Scheduler;
-use Piwik\Scheduler\Task;
-use Piwik\Scheduler\Timetable;
-use Piwik\Scheduler\Schedule\Monthly;
-use Piwik\Scheduler\Schedule\Weekly;
-use Piwik\SettingsPiwik;
-use Piwik\Unzip;
-use Psr\Log\LoggerInterface;
-
-/**
- * Used to automatically update installed GeoIP databases, and manages the updater's
- * scheduled task.
- */
-class GeoIPAutoUpdater extends Task
-{
- const SCHEDULE_PERIOD_MONTHLY = 'month';
- const SCHEDULE_PERIOD_WEEKLY = 'week';
-
- const SCHEDULE_PERIOD_OPTION_NAME = 'geoip.updater_period';
- const LOC_URL_OPTION_NAME = 'geoip.loc_db_url';
- const ISP_URL_OPTION_NAME = 'geoip.isp_db_url';
- const ORG_URL_OPTION_NAME = 'geoip.org_db_url';
-
- const LAST_RUN_TIME_OPTION_NAME = 'geoip.updater_last_run_time';
-
- private static $urlOptions = array(
- 'loc' => self::LOC_URL_OPTION_NAME,
- 'isp' => self::ISP_URL_OPTION_NAME,
- 'org' => self::ORG_URL_OPTION_NAME,
- );
-
- /**
- * PHP Error caught through a custom error handler while trying to use a downloaded
- * GeoIP database. See catchGeoIPError for more info.
- *
- * @var array
- */
- private static $unzipPhpError = null;
-
- /**
- * Constructor.
- */
- public function __construct()
- {
- if (!SettingsPiwik::isInternetEnabled()) {
- // no automatic updates possible if no internet available
- return;
- }
-
- $schedulePeriodStr = self::getSchedulePeriod();
-
- // created the scheduledtime instance, also, since GeoIP updates are done on tuesdays,
- // get new DBs on Wednesday
- switch ($schedulePeriodStr) {
- case self::SCHEDULE_PERIOD_WEEKLY:
- $schedulePeriod = new Weekly();
- $schedulePeriod->setDay(3);
- break;
- case self::SCHEDULE_PERIOD_MONTHLY:
- default:
- $schedulePeriod = new Monthly();
- $schedulePeriod->setDayOfWeek(3, 0);
- break;
- }
-
- parent::__construct($this, 'update', null, $schedulePeriod, Task::LOWEST_PRIORITY);
- }
-
- /**
- * Attempts to download new location, ISP & organization GeoIP databases and
- * replace the existing ones w/ them.
- */
- public function update()
- {
- try {
- Option::set(self::LAST_RUN_TIME_OPTION_NAME, Date::factory('today')->getTimestamp());
-
- $locUrl = Option::get(self::LOC_URL_OPTION_NAME);
- if (!empty($locUrl)) {
- $this->downloadFile('loc', $locUrl);
- }
-
- $ispUrl = Option::get(self::ISP_URL_OPTION_NAME);
- if (!empty($ispUrl)) {
- $this->downloadFile('isp', $ispUrl);
- }
-
- $orgUrl = Option::get(self::ORG_URL_OPTION_NAME);
- if (!empty($orgUrl)) {
- $this->downloadFile('org', $orgUrl);
- }
- } catch (Exception $ex) {
- // message will already be prefixed w/ 'GeoIPAutoUpdater: '
- StaticContainer::get(LoggerInterface::class)->error('Auto-update failed: {exception}', [
- 'exception' => $ex,
- 'ignoreInScreenWriter' => true,
- ]);
- $this->performRedundantDbChecks();
- throw $ex;
- }
-
- $this->performRedundantDbChecks();
- }
-
- /**
- * Downloads a GeoIP database archive, extracts the .dat file and overwrites the existing
- * old database.
- *
- * If something happens that causes the download to fail, no exception is thrown, but
- * an error is logged.
- *
- * @param string $dbType
- * @param string $url URL to the database to download. The type of database is determined
- * from this URL.
- * @throws Exception
- */
- protected function downloadFile($dbType, $url)
- {
- $url = trim($url);
-
- if (strpos($url, 'GeoLite')) {
- Log::info('GeoLite databases have been discontinued. Skipping download of '.$url.'. Consider switching to GeoIP 2.');
- return;
- }
-
- $ext = GeoIPAutoUpdater::getGeoIPUrlExtension($url);
-
- // NOTE: using the first item in $dbNames[$dbType] makes sure GeoLiteCity will be renamed to GeoIPCity
- $zippedFilename = GeoIp::$dbNames[$dbType][0] . '.' . $ext;
-
- $zippedOutputPath = GeoIp::getPathForGeoIpDatabase($zippedFilename);
-
- $url = self::removeDateFromUrl($url);
-
- // download zipped file to misc dir
- try {
- $success = Http::sendHttpRequest($url, $timeout = 3600, $userAgent = null, $zippedOutputPath);
- } catch (Exception $ex) {
- throw new Exception("GeoIPAutoUpdater: failed to download '$url' to "
- . "'$zippedOutputPath': " . $ex->getMessage());
- }
-
- if ($success !== true) {
- throw new Exception("GeoIPAutoUpdater: failed to download '$url' to "
- . "'$zippedOutputPath'! (Unknown error)");
- }
-
- Log::info("GeoIPAutoUpdater: successfully downloaded '%s'", $url);
-
- try {
- self::unzipDownloadedFile($zippedOutputPath, $unlink = true);
- } catch (Exception $ex) {
- throw new Exception("GeoIPAutoUpdater: failed to unzip '$zippedOutputPath' after "
- . "downloading " . "'$url': " . $ex->getMessage());
- }
-
- Log::info("GeoIPAutoUpdater: successfully updated GeoIP database '%s'", $url);
- }
-
- /**
- * Unzips a downloaded GeoIP database. Only unzips .gz & .tar.gz files.
- *
- * @param string $path Path to zipped file.
- * @param bool $unlink Whether to unlink archive or not.
- * @throws Exception
- */
- public static function unzipDownloadedFile($path, $unlink = false)
- {
- $parts = explode('.', basename($path));
- $filenameStart = $parts[0];
-
- $dbFilename = $filenameStart . '.dat';
- $tempFilename = $filenameStart . '.dat.new';
- $outputPath = GeoIp::getPathForGeoIpDatabase($tempFilename);
-
- // extract file
- if (substr($path, -7, 7) == '.tar.gz') {
- // find the .dat file in the tar archive
- $unzip = Unzip::factory('tar.gz', $path);
- $content = $unzip->listContent();
-
- if (empty($content)) {
- throw new Exception(Piwik::translate('UserCountry_CannotListContent',
- array("'$path'", $unzip->errorInfo())));
- }
-
- $datFile = null;
- foreach ($content as $info) {
- $archivedPath = $info['filename'];
- if (basename($archivedPath) === $dbFilename) {
- $datFile = $archivedPath;
- }
- }
-
- if ($datFile === null) {
- throw new Exception(Piwik::translate('UserCountry_CannotFindGeoIPDatabaseInArchive',
- array($dbFilename, "'$path'")));
- }
-
- // extract JUST the .dat file
- $unzipped = $unzip->extractInString($datFile);
-
- if (empty($unzipped)) {
- throw new Exception(Piwik::translate('UserCountry_CannotUnzipDatFile',
- array("'$path'", $unzip->errorInfo())));
- }
-
- // write unzipped to file
- $fd = fopen($outputPath, 'wb');
- fwrite($fd, $unzipped);
- fclose($fd);
- } else if (substr($path, -3, 3) == '.gz') {
- $unzip = Unzip::factory('gz', $path);
- $success = $unzip->extract($outputPath);
-
- if ($success !== true) {
- throw new Exception(Piwik::translate('UserCountry_CannotUnzipDatFile',
- array("'$path'", $unzip->errorInfo())));
- }
- } else {
- $ext = end(explode(basename($path), '.', 2));
- throw new Exception(Piwik::translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
- }
-
- try {
- // test that the new archive is a valid GeoIP database
- $dbType = GeoIp::getGeoIPDatabaseTypeFromFilename($dbFilename);
- if ($dbType === false) // sanity check
- {
- throw new Exception("Unexpected GeoIP archive file name '$path'.");
- }
-
- $customDbNames = array(
- 'loc' => array(),
- 'isp' => array(),
- 'org' => array()
- );
- $customDbNames[$dbType] = array($tempFilename);
-
- $phpProvider = new Php($customDbNames);
-
- $location = self::getTestLocationCatchPhpErrors($phpProvider);
-
- if (empty($location)
- || self::$unzipPhpError !== null
- ) {
- if (self::$unzipPhpError !== null) {
- list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
- Log::info("GeoIPAutoUpdater: Encountered PHP error when testing newly downloaded" .
- " GeoIP database: %s: %s on line %s of %s.", $errno, $errstr, $errline, $errfile);
- }
-
- throw new Exception(Piwik::translate('UserCountry_ThisUrlIsNotAValidGeoIPDB'));
- }
-
- // delete the existing GeoIP database (if any) and rename the downloaded file
- $oldDbFile = GeoIp::getPathForGeoIpDatabase($dbFilename);
- if (file_exists($oldDbFile)) {
- unlink($oldDbFile);
- }
-
- $tempFile = GeoIp::getPathForGeoIpDatabase($tempFilename);
- if (@rename($tempFile, $oldDbFile) !== true) {
- //In case the $tempfile cannot be renamed, we copy the file.
- copy($tempFile, $oldDbFile);
- unlink($tempFile);
- }
-
- // delete original archive
- if ($unlink) {
- unlink($path);
- }
- } catch (Exception $ex) {
- // remove downloaded files
- if (file_exists($outputPath)) {
- unlink($outputPath);
- }
- unlink($path);
-
- throw $ex;
- }
- }
-
- /**
- * Sets the options used by this class based on query parameter values.
- *
- * See setUpdaterOptions for query params used.
- */
- public static function setUpdaterOptionsFromUrl()
- {
- $options = array(
- 'loc' => Common::getRequestVar('loc_db', false, 'string'),
- 'isp' => Common::getRequestVar('isp_db', false, 'string'),
- 'org' => Common::getRequestVar('org_db', false, 'string'),
- 'period' => Common::getRequestVar('period', false, 'string'),
- );
-
- foreach (self::$urlOptions as $optionKey => $optionName) {
- $options[$optionKey] = Common::unsanitizeInputValue($options[$optionKey]); // URLs should not be sanitized
- }
-
- self::setUpdaterOptions($options);
- }
-
- /**
- * Sets the options used by this class based on the elements in $options.
- *
- * The following elements of $options are used:
- * 'loc' - URL for location database.
- * 'isp' - URL for ISP database.
- * 'org' - URL for Organization database.
- * 'period' - 'weekly' or 'monthly'. When to run the updates.
- *
- * @param array $options
- * @throws Exception
- */
- public static function setUpdaterOptions($options)
- {
- // set url options
- foreach (self::$urlOptions as $optionKey => $optionName) {
- if (!isset($options[$optionKey])) {
- continue;
- }
-
- $url = $options[$optionKey];
- $url = self::removeDateFromUrl($url);
-
- Option::set($optionName, $url);
- }
-
- // set period option
- if (!empty($options['period'])) {
- $period = $options['period'];
-
- if ($period != self::SCHEDULE_PERIOD_MONTHLY
- && $period != self::SCHEDULE_PERIOD_WEEKLY
- ) {
- throw new Exception(Piwik::translate(
- 'UserCountry_InvalidGeoIPUpdatePeriod',
- array("'$period'", "'" . self::SCHEDULE_PERIOD_MONTHLY . "', '" . self::SCHEDULE_PERIOD_WEEKLY . "'")
- ));
- }
-
- Option::set(self::SCHEDULE_PERIOD_OPTION_NAME, $period);
-
- /** @var Scheduler $scheduler */
- $scheduler = StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler');
-
- $scheduler->rescheduleTaskAndRunTomorrow(new GeoIPAutoUpdater());
- }
- }
-
- /**
- * Removes all options to disable any configured automatic updates
- */
- public static function clearOptions()
- {
- foreach (self::$urlOptions as $optionKey => $optionName) {
- Option::delete($optionName);
- }
- Option::delete(self::SCHEDULE_PERIOD_OPTION_NAME);
- }
-
- /**
- * Returns true if the auto-updater is setup to update at least one type of
- * database. False if otherwise.
- *
- * @return bool
- */
- public static function isUpdaterSetup()
- {
- if (Option::get(self::LOC_URL_OPTION_NAME) !== false
- || Option::get(self::ISP_URL_OPTION_NAME) !== false
- || Option::get(self::ORG_URL_OPTION_NAME) !== false
- ) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Retrieves the URLs used to update various GeoIP database files.
- *
- * @return array
- */
- public static function getConfiguredUrls()
- {
- $result = array();
- foreach (self::$urlOptions as $key => $optionName) {
- $result[$key] = Option::get($optionName);
- }
- return $result;
- }
-
- /**
- * Returns the confiured URL (if any) for a type of database.
- *
- * @param string $key 'loc', 'isp' or 'org'
- * @throws Exception
- * @return string|false
- */
- public static function getConfiguredUrl($key)
- {
- if (empty(self::$urlOptions[$key])) {
- throw new Exception("Invalid key $key");
- }
- $url = Option::get(self::$urlOptions[$key]);
- return $url;
- }
-
- /**
- * Performs a GeoIP database update.
- */
- public static function performUpdate()
- {
- $instance = new GeoIPAutoUpdater();
- $instance->update();
- }
-
- /**
- * Returns the configured update period, either 'week' or 'month'. Defaults to
- * 'month'.
- *
- * @return string
- */
- public static function getSchedulePeriod()
- {
- $period = Option::get(self::SCHEDULE_PERIOD_OPTION_NAME);
- if ($period === false) {
- $period = self::SCHEDULE_PERIOD_MONTHLY;
- }
- return $period;
- }
-
- /**
- * Returns an array of strings for GeoIP databases that have update URLs configured, but
- * are not present in the misc directory. Each string is a key describing the type of
- * database (ie, 'loc', 'isp' or 'org').
- *
- * @return array
- */
- public static function getMissingDatabases()
- {
- $result = array();
- foreach (self::getConfiguredUrls() as $key => $url) {
- if (!empty($url)) {
- // if a database of the type does not exist, but there's a url to update, then
- // a database is missing
- $path = GeoIp::getPathToGeoIpDatabase(
- GeoIp::$dbNames[$key]);
- if ($path === false) {
- $result[] = $key;
- }
- }
- }
- return $result;
- }
-
- /**
- * Returns the extension of a URL used to update a GeoIP database, if it can be found.
- */
- public static function getGeoIPUrlExtension($url)
- {
- // check for &suffix= query param that is special to MaxMind URLs
- if (preg_match('/suffix=([^&]+)/', $url, $matches)) {
- $ext = $matches[1];
- } else {
- // use basename of url
- $filenameParts = explode('.', basename($url), 2);
- if (count($filenameParts) > 1) {
- $ext = end($filenameParts);
- } else {
- $ext = reset($filenameParts);
- }
- }
-
- self::checkForSupportedArchiveType($ext);
-
- return $ext;
- }
-
- /**
- * Avoid downloading archive types we don't support. No point in downloading it,
- * if we can't unzip it...
- *
- * @param string $ext The URL file's extension.
- * @throws \Exception
- */
- private static function checkForSupportedArchiveType($ext)
- {
- if ($ext != 'tar.gz'
- && $ext != 'gz'
- && $ext != 'dat.gz'
- && $ext != 'mmdb.gz'
- ) {
- throw new \Exception(Piwik::translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
- }
- }
-
- /**
- * Tests a location provider using a test IP address and catches PHP errors
- * (ie, notices) if they occur. PHP error information is held in self::$unzipPhpError.
- *
- * @param LocationProvider $provider The provider to test.
- * @return array|false $location The result of geolocation. False if no location
- * can be found.
- */
- private static function getTestLocationCatchPhpErrors($provider)
- {
- // note: in most cases where this will fail, the error will usually be a PHP fatal error/notice.
- // in order to delete the files in such a case (which can be caused by a man-in-the-middle attack)
- // we need to catch them, so we set a new error handler.
- self::$unzipPhpError = null;
- set_error_handler(array('Piwik\Plugins\UserCountry\GeoIPAutoUpdater', 'catchGeoIPError'));
-
- $location = $provider->getLocation(array('ip' => GeoIp::TEST_IP));
-
- restore_error_handler();
-
- return $location;
- }
-
- /**
- * Utility function that checks if geolocation works with each installed database,
- * and if one or more doesn't, they are renamed to make sure tracking will work.
- * This is a safety measure used to make sure tracking isn't affected if strange
- * update errors occur.
- *
- * Databases are renamed to ${original}.broken .
- *
- * Note: method is protected for testability.
- *
- * @param $logErrors - only used to hide error logs during tests
- */
- protected function performRedundantDbChecks($logErrors = true)
- {
- $databaseTypes = array_keys(GeoIp::$dbNames);
-
- foreach ($databaseTypes as $type) {
- $customNames = array(
- 'loc' => array(),
- 'isp' => array(),
- 'org' => array()
- );
- $customNames[$type] = GeoIp::$dbNames[$type];
-
- // create provider that only uses the DB type we're testing
- $provider = new Php($customNames);
-
- // test the provider. on error, we rename the broken DB.
- self::getTestLocationCatchPhpErrors($provider);
- if (self::$unzipPhpError !== null) {
- list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
-
- if ($logErrors) {
- StaticContainer::get(LoggerInterface::class)->error("GeoIPAutoUpdater: Encountered PHP error when performing redundant tests on GeoIP "
- . "{type} database: {errno}: {errstr} on line {errline} of {errfile}.", [
- 'ignoreInScreenWriter' => true,
- 'type' => $type,
- 'errno' => $errno,
- 'errstr' => $errstr,
- 'errline' => $errline,
- 'errfile' => $errfile,
- ]);
- }
-
- // get the current filename for the DB and an available new one to rename it to
- list($oldPath, $newPath) = $this->getOldAndNewPathsForBrokenDb($customNames[$type]);
-
- // rename the DB so tracking will not fail
- if ($oldPath !== false
- && $newPath !== false
- ) {
- if (file_exists($newPath)) {
- unlink($newPath);
- }
-
- rename($oldPath, $newPath);
- }
- }
- }
- }
-
- /**
- * Returns the path to a GeoIP database and a path to rename it to if it's broken.
- *
- * @param array $possibleDbNames The possible names of the database.
- * @return array Array with two elements, the path to the existing database, and
- * the path to rename it to if it is broken. The second will end
- * with something like .broken .
- */
- private function getOldAndNewPathsForBrokenDb($possibleDbNames)
- {
- $pathToDb = GeoIp::getPathToGeoIpDatabase($possibleDbNames);
- $newPath = false;
-
- if ($pathToDb !== false) {
- $newPath = $pathToDb . ".broken";
- }
-
- return array($pathToDb, $newPath);
- }
-
- /**
- * Custom PHP error handler used to catch any PHP errors that occur when
- * testing a downloaded GeoIP file.
- *
- * If we download a file that is supposed to be a GeoIP database, we need to make
- * sure it is one. This is done simply by attempting to use it. If this fails, it
- * will most of the time fail as a PHP error, which we catch w/ this function
- * after it is passed to set_error_handler.
- *
- * The PHP error is stored in self::$unzipPhpError.
- *
- * @param int $errno
- * @param string $errstr
- * @param string $errfile
- * @param int $errline
- */
- public static function catchGeoIPError($errno, $errstr, $errfile, $errline)
- {
- self::$unzipPhpError = array($errno, $errstr, $errfile, $errline);
- }
-
- /**
- * Returns the time the auto updater was last run.
- *
- * @return Date|false
- */
- public static function getLastRunTime()
- {
- $timestamp = Option::get(self::LAST_RUN_TIME_OPTION_NAME);
- return $timestamp === false ? false : Date::factory((int)$timestamp);
- }
-
- /**
- * Removes the &date=... query parameter if present in the URL. This query parameter
- * is in MaxMind URLs by default and will force the download of an old database.
- *
- * @param string $url
- * @return string
- */
- private static function removeDateFromUrl($url)
- {
- return preg_replace("/&date=[^&#]*/", '', $url);
- }
-
- /**
- * Returns the next scheduled time for the auto updater.
- *
- * @return Date|false
- */
- public static function getNextRunTime()
- {
- $task = new GeoIPAutoUpdater();
-
- $timetable = new Timetable();
- return $timetable->getScheduledTaskTime($task->getName());
- }
-
- /**
- * See {@link Piwik\Scheduler\Schedule\Schedule::getRescheduledTime()}.
- */
- public function getRescheduledTime()
- {
- $nextScheduledTime = parent::getRescheduledTime();
-
- // if a geoip database is out of date, run the updater as soon as possible
- if ($this->isAtLeastOneGeoIpDbOutOfDate($nextScheduledTime)) {
- return time();
- }
-
- return $nextScheduledTime;
- }
-
- private function isAtLeastOneGeoIpDbOutOfDate($rescheduledTime)
- {
- $previousScheduledRuntime = $this->getPreviousScheduledTime($rescheduledTime)->setTime("00:00:00")->getTimestamp();
-
- foreach (GeoIp::$dbNames as $type => $dbNames) {
- $dbUrl = Option::get(self::$urlOptions[$type]);
- $dbPath = GeoIp::getPathToGeoIpDatabase($dbNames);
-
- // if there is a URL for this DB type and the GeoIP DB file's last modified time is before
- // the time the updater should have been previously run, then **the file is out of date**
- if (!empty($dbUrl)
- && filemtime($dbPath) < $previousScheduledRuntime
- ) {
- return true;
- }
- }
-
- return false;
- }
-
- private function getPreviousScheduledTime($rescheduledTime)
- {
- $updaterPeriod = self::getSchedulePeriod();
-
- if ($updaterPeriod == self::SCHEDULE_PERIOD_WEEKLY) {
- return Date::factory($rescheduledTime)->subWeek(1);
- } else if ($updaterPeriod == self::SCHEDULE_PERIOD_MONTHLY) {
- return Date::factory($rescheduledTime)->subMonth(1);
- }
- throw new Exception("Unknown GeoIP updater period found in database: %s", $updaterPeriod);
- }
-}