191 lines
6.6 KiB
PHP
191 lines
6.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
// -------------------- CONSTANTES --------------------
|
|
const CACHE_DIR = '/var/cache/homepage';
|
|
const INSTANCES_JSON = CACHE_DIR . '/instances.json';
|
|
const URLS_TXT = CACHE_DIR . '/urls.txt';
|
|
const INSTANCES_URL = 'https://searx.space/data/instances.json';
|
|
const CACHE_MAX_AGE = 3600; // 1 heure
|
|
const BANG_FILE = __DIR__ . '/bang.json';
|
|
|
|
// -------------------- HEADERS DE SÉCURITÉ --------------------
|
|
header('X-Frame-Options: SAMEORIGIN');
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('Referrer-Policy: no-referrer-when-downgrade');
|
|
header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
|
|
|
|
// -------------------- CLASSES --------------------
|
|
class CacheManager
|
|
{
|
|
private array $urlsCache = [];
|
|
|
|
public function __construct(private string $cacheDir = CACHE_DIR) {
|
|
if (!is_dir($cacheDir) && !mkdir($cacheDir, 0755, true)) {
|
|
throw new RuntimeException("Impossible de créer le dossier cache");
|
|
}
|
|
}
|
|
|
|
public function isExpired(): bool {
|
|
return !file_exists(URLS_TXT) || (time() - filemtime(URLS_TXT)) > CACHE_MAX_AGE;
|
|
}
|
|
|
|
public function downloadInstances(): bool {
|
|
$data = file_get_contents(INSTANCES_URL);
|
|
if ($data === false) return false;
|
|
return $this->safeWrite(INSTANCES_JSON, $data);
|
|
}
|
|
|
|
public function extractValidUrls(): bool {
|
|
if (!file_exists(INSTANCES_JSON)) return false;
|
|
|
|
$json = json_decode(file_get_contents(INSTANCES_JSON), true);
|
|
if (!isset($json['instances']) || !is_array($json['instances'])) return false;
|
|
|
|
$valid = array_filter(array_keys($json['instances']), function($url) use ($json) {
|
|
$data = $json['instances'][$url] ?? [];
|
|
return ($data['network_type'] ?? '') === 'normal'
|
|
&& ($data['http']['status_code'] ?? 0) === 200
|
|
&& ($data['timing']['search']['success_percentage'] ?? 0) === 100.0
|
|
&& ($data['timing']['initial']['success_percentage'] ?? 0) === 100.0
|
|
&& filter_var($url, FILTER_VALIDATE_URL)
|
|
&& in_array(parse_url($url, PHP_URL_SCHEME), ['http','https'], true);
|
|
});
|
|
|
|
if (empty($valid)) return false;
|
|
|
|
return $this->safeWrite(URLS_TXT, implode("\n", array_map('rtrim', $valid)));
|
|
}
|
|
|
|
private function safeWrite(string $file, string $data): bool {
|
|
$written = file_put_contents($file, $data, LOCK_EX);
|
|
return $written !== false && $written === strlen($data);
|
|
}
|
|
|
|
public function loadUrls(): array {
|
|
if (empty($this->urlsCache)) {
|
|
if (!file_exists(URLS_TXT)) return [];
|
|
$urls = file(URLS_TXT, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
$this->urlsCache = array_filter($urls, fn($url) => filter_var($url, FILTER_VALIDATE_URL));
|
|
}
|
|
return $this->urlsCache;
|
|
}
|
|
|
|
public function getRandomUrl(): string {
|
|
$urls = $this->loadUrls();
|
|
if (empty($urls)) throw new RuntimeException("Aucune URL disponible");
|
|
return $urls[array_rand($urls)];
|
|
}
|
|
}
|
|
|
|
class BangManager
|
|
{
|
|
private array $bangs = [];
|
|
|
|
public function __construct(private string $file = BANG_FILE) {
|
|
$this->loadBangs();
|
|
}
|
|
|
|
private function loadBangs(): void {
|
|
if (!file_exists($this->file)) return;
|
|
$data = json_decode(file_get_contents($this->file), true);
|
|
$this->bangs = is_array($data) ? $data : [];
|
|
}
|
|
|
|
public function tryRedirect(string $query): bool {
|
|
if (!str_starts_with($query, '!')) return false;
|
|
if (!preg_match('/^!(\w+)\s?(.*)$/u', $query, $matches)) return false;
|
|
|
|
[$bangCode, $searchTerm] = [$matches[1], trim($matches[2])];
|
|
if ($searchTerm === '') return false;
|
|
|
|
foreach ($this->bangs as $bang) {
|
|
if (($bang['bang'] ?? '') === $bangCode) {
|
|
header("Location: " . rtrim($bang['url'], '/') . '?q=' . rawurlencode($searchTerm));
|
|
exit;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class RequestHandler
|
|
{
|
|
public function __construct(private CacheManager $cache, private BangManager $bang) {}
|
|
|
|
public function handle(): void {
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
$query = $_REQUEST['q'] ?? null;
|
|
|
|
if ($query !== null && !$this->bang->tryRedirect($query)) {
|
|
$instance = rtrim($this->cache->getRandomUrl(), '/');
|
|
|
|
if ($method === 'GET') {
|
|
$this->redirectGet($instance, $query);
|
|
} elseif ($method === 'POST') {
|
|
$this->redirectGet($instance, $query);
|
|
// $this->proxyPost($instance, $_POST);
|
|
} else {
|
|
http_response_code(405);
|
|
exit('Méthode non autorisée');
|
|
}
|
|
} elseif ($query === null) {
|
|
$instance = rtrim($this->cache->getRandomUrl(), '/');
|
|
if ($method === 'GET') {
|
|
header("Location: $instance");
|
|
exit;
|
|
} elseif ($method === 'POST') {
|
|
$this->proxyPost($instance, $_POST);
|
|
} else {
|
|
http_response_code(405);
|
|
exit('Méthode non autorisée');
|
|
}
|
|
}
|
|
}
|
|
|
|
private function redirectGet(string $instance, string $query): void {
|
|
$url = $instance . '/search?' . http_build_query(['q' => $query]);
|
|
header("Location: $url");
|
|
exit;
|
|
}
|
|
|
|
private function proxyPost(string $urlBase, array $postData): void {
|
|
$url = $urlBase . '/search';
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => http_build_query($postData),
|
|
CURLOPT_HEADER => false,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_TIMEOUT => 10,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
]);
|
|
$response = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
http_response_code($code);
|
|
echo $response;
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// -------------------- EXECUTION --------------------
|
|
try {
|
|
$cache = new CacheManager();
|
|
if ($cache->isExpired()) {
|
|
if (!$cache->downloadInstances() || !$cache->extractValidUrls()) {
|
|
http_response_code(500);
|
|
exit('Erreur interne : impossible de générer le cache.');
|
|
}
|
|
}
|
|
|
|
$bang = new BangManager();
|
|
$handler = new RequestHandler($cache, $bang);
|
|
$handler->handle();
|
|
|
|
} catch (\Throwable $e) {
|
|
http_response_code(500);
|
|
exit('Erreur interne : ' . $e->getMessage());
|
|
}
|