licenseKey !== ''; } public function download(string $tempDir): array { $countryPath = $this->fetchEdition($tempDir, 'GeoLite2-Country', 'country'); MmdbVerifier::assertCountry($countryPath); $asnPath = $this->fetchEdition($tempDir, 'GeoLite2-ASN', 'asn'); MmdbVerifier::assertAsn($asnPath); return ['country' => $countryPath, 'asn' => $asnPath]; } private function fetchEdition(string $tempDir, string $edition, string $kind): string { $tarPath = $tempDir . '/' . $kind . '.tar.gz'; $sha256Path = $tempDir . '/' . $kind . '.tar.gz.sha256'; $this->fetchTo($edition, 'tar.gz', $tarPath); $this->fetchTo($edition, 'tar.gz.sha256', $sha256Path); $expected = trim((string) file_get_contents($sha256Path)); // The .sha256 file is " "; take the leading hash. if (preg_match('/^([0-9a-f]{64})/', $expected, $m) !== 1) { throw new DownloaderException(sprintf('maxmind %s sha256 file unparseable', $kind)); } $expectedHash = $m[1]; $actualHash = hash_file('sha256', $tarPath); if ($actualHash !== $expectedHash) { throw new DownloaderException(sprintf( 'maxmind %s sha256 mismatch (expected %s, got %s)', $kind, $expectedHash, (string) $actualHash, )); } $extractDir = $tempDir . '/' . $kind . '-extract'; if (!mkdir($extractDir) && !is_dir($extractDir)) { throw new DownloaderException(sprintf('cannot mkdir %s', $extractDir)); } try { $phar = new PharData($tarPath); $phar->extractTo($extractDir, null, true); } catch (Throwable $e) { throw new DownloaderException( sprintf('maxmind %s extract failed: %s', $kind, $e->getMessage()), 0, $e, ); } $mmdbPath = $this->findMmdb($extractDir); if ($mmdbPath === null) { throw new DownloaderException(sprintf('maxmind %s tarball had no .mmdb', $kind)); } $finalPath = $tempDir . '/' . $kind . '.mmdb'; if (!@rename($mmdbPath, $finalPath)) { throw new DownloaderException(sprintf('rename %s -> %s failed', $mmdbPath, $finalPath)); } return $finalPath; } private function fetchTo(string $edition, string $suffix, string $sink): void { $url = sprintf('%s?edition_id=%s&license_key=%s&suffix=%s', self::ENDPOINT, $edition, $this->licenseKey, $suffix); try { $this->http->request('GET', $url, ['sink' => $sink]); } catch (GuzzleException $e) { // Sanitise the URL in the surfaced message. $sanitised = sprintf('%s?edition_id=%s&license_key=***&suffix=%s', self::ENDPOINT, $edition, $suffix); throw new DownloaderException(sprintf('GET %s failed', $sanitised), 0, $e); } } private function findMmdb(string $dir): ?string { $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)); foreach ($it as $file) { if ($file->isFile() && str_ends_with($file->getFilename(), '.mmdb')) { return $file->getPathname(); } } return null; } }