#!/usr/bin/env php &1', $out, $code); if ($code !== 0) { fwrite(STDERR, " o arquivo não abriu -- ignorado\n"); $status = 1; continue; } // Nem toda a zona do ipdeny é um país. O zz.zone é um apanha-tudo de blocos // /8 inteiros (1.0.0.0/8, 8.0.0.0/8…) que se sobrepõem aos países reais, e // o eu.zone é um agregado continental. Deixá-los entrar fazia o 8.8.8.8 sair // como "ZZ" em vez de US, porque a busca binária cai no range que apanhar -- // e com sobreposições não há garantia nenhuma de ser o mais específico. // Fora estes dois, sobram 2 sobreposições em 262 mil. $notCountries = ['ZZ', 'EU', 'AP']; $ranges = []; foreach (glob("$work/*.zone") ?: [] as $file) { // ipdeny nomeia cada ficheiro pelo código ISO: pt.zone, br.zone… $cc = strtoupper(basename($file, '.zone')); if (!preg_match('/^[A-Z]{2}$/', $cc) || in_array($cc, $notCountries, true)) { continue; } foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } $r = geo_cidr_to_range($line); // Um ficheiro v4 não pode contaminar o índice v6 nem o contrário: // os registos são de largura fixa e um intruso desalinhava tudo a // partir dali, o que a busca binária não tem como detectar. if ($r === null || strlen($r[0]) !== $addrLen) { continue; } $ranges[] = [$r[0], $r[1], $cc]; } } if (count($ranges) < 1000) { fwrite(STDERR, ' só ' . count($ranges) . " ranges — recusado, fica o índice anterior\n"); $status = 1; continue; } usort($ranges, static fn($a, $b) => strcmp($a[0], $b[0])); // As poucas sobreposições que restam são descartadas, ficando a primeira. // A busca binária pressupõe ranges disjuntos: com sobreposições devolveria // um resultado que depende de onde a bissecção calhou aterrar, o que é pior // do que não responder -- daria países diferentes para o mesmo endereço // consoante o tamanho do índice. $clean = []; $lastEnd = null; $dropped = 0; foreach ($ranges as $r) { if ($lastEnd !== null && strcmp($r[0], $lastEnd) <= 0) { $dropped++; continue; } $clean[] = $r; $lastEnd = $r[1]; } $ranges = $clean; if ($dropped > 0) { fwrite(STDERR, " $dropped ranges sobrepostos descartados\n"); } // Escrita para ficheiro temporário e rename: o rename é atómico, por isso // um processo a ler nunca apanha o índice a meio de ser escrito. $outFile = "$dataDir/country-index.$fam"; $tmpOut = $outFile . '.tmp'; $fh = fopen($tmpOut, 'wb'); if ($fh === false) { fwrite(STDERR, " não consegui escrever em $tmpOut\n"); $status = 1; continue; } foreach ($ranges as [$start, $end, $cc]) { fwrite($fh, $start . $end . $cc); } fclose($fh); @chmod($tmpOut, 0664); rename($tmpOut, $outFile); printf( " %s: %d ranges, %.1f MB\n", $fam, count($ranges), filesize($outFile) / 1048576 ); } exit($status); function download(string $url, string $dest): bool { $fh = fopen($dest, 'wb'); if ($fh === false) { return false; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_FILE => $fh, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 180, CURLOPT_FAILONERROR => true, ]); $ok = curl_exec($ch) !== false; curl_close($ch); fclose($fh); return $ok && filesize($dest) > 1024; }