seeky/index.php

253 lines
8.7 KiB
PHP
Raw Permalink Normal View History

2025-06-24 13:39:47 +02:00
<?php
2025-12-09 20:27:03 +00:00
declare(strict_types=1);
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
// -------------------- CONSTANTES --------------------
2025-12-11 21:21:56 +00:00
const CACHE_DIR = '/var/cache/homepage';
const INSTANCES_JSON = CACHE_DIR . '/instances.json';
const URLS_TXT = __DIR__ . '/urls.txt'; // ← Hors du cache, à la racine
const INSTANCES_URL = 'https://searx.space/data/instances.json';
const CACHE_MAX_AGE = 3600; // 1 heure
const BANG_FILE = __DIR__ . '/bang.json';
const USE_SEARX_INSTANCES = true; // false → utilise engines.txt
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
// -------------------- HEADERS DE SÉCURITÉ --------------------
2025-06-24 13:39:47 +02:00
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");
2025-12-11 21:21:56 +00:00
header('X-Search-Mode: ' . (USE_SEARX_INSTANCES ? 'searx' : 'engines')); // ← Point 6
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
// -------------------- CLASSES --------------------
2025-12-11 21:21:56 +00:00
2025-12-09 20:27:03 +00:00
class CacheManager
2025-06-24 13:39:47 +02:00
{
2025-12-09 20:27:03 +00:00
private array $urlsCache = [];
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
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");
}
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
public function isExpired(): bool {
return !file_exists(URLS_TXT) || (time() - filemtime(URLS_TXT)) > CACHE_MAX_AGE;
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
public function downloadInstances(): bool {
$data = file_get_contents(INSTANCES_URL);
if ($data === false) return false;
return $this->safeWrite(INSTANCES_JSON, $data);
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
public function extractValidUrls(): bool {
if (!file_exists(INSTANCES_JSON)) return false;
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
$json = json_decode(file_get_contents(INSTANCES_JSON), true);
if (!isset($json['instances']) || !is_array($json['instances'])) return false;
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
$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);
});
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
if (empty($valid)) return false;
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
return $this->safeWrite(URLS_TXT, implode("\n", array_map('rtrim', $valid)));
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
private function safeWrite(string $file, string $data): bool {
$written = file_put_contents($file, $data, LOCK_EX);
return $written !== false && $written === strlen($data);
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
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));
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
return $this->urlsCache;
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
public function getRandomUrl(): string {
$urls = $this->loadUrls();
if (empty($urls)) throw new RuntimeException("Aucune URL disponible");
return $urls[array_rand($urls)];
2025-06-24 13:39:47 +02:00
}
}
2025-12-09 20:27:03 +00:00
class BangManager
2025-06-24 13:39:47 +02:00
{
2025-12-09 20:27:03 +00:00
private array $bangs = [];
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
public function __construct(private string $file = BANG_FILE) {
$this->loadBangs();
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
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 : [];
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
public function tryRedirect(string $query): bool {
if (!str_starts_with($query, '!')) return false;
if (!preg_match('/^!(\w+)\s?(.*)$/u', $query, $matches)) return false;
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
[$bangCode, $searchTerm] = [$matches[1], trim($matches[2])];
if ($searchTerm === '') return false;
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
foreach ($this->bangs as $bang) {
if (($bang['bang'] ?? '') === $bangCode) {
header("Location: " . rtrim($bang['url'], '/') . '?q=' . rawurlencode($searchTerm));
exit;
}
}
2025-06-24 13:39:47 +02:00
return false;
}
}
2025-12-11 21:21:56 +00:00
class EngineManager
{
private static ?array $cachedEngines = null;
private array $engines = [];
public function __construct(private string $file = __DIR__ . '/engines.txt')
{
if (self::$cachedEngines === null) {
$this->loadEngines();
self::$cachedEngines = $this->engines;
} else {
$this->engines = self::$cachedEngines;
}
}
private function loadEngines(): void
{
if (!file_exists($this->file)) {
throw new RuntimeException("Fichier engines.txt introuvable");
}
$lines = file($this->file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$this->engines = array_filter($lines, fn($line) => !empty(trim($line)));
}
public function getRandomEngine(): string
{
if (empty($this->engines)) {
throw new RuntimeException("Aucun moteur de recherche configuré dans engines.txt");
}
return $this->engines[array_rand($this->engines)];
}
}
class SearxHandler
2025-06-24 13:39:47 +02:00
{
2025-12-09 20:27:03 +00:00
public function __construct(private CacheManager $cache, private BangManager $bang) {}
2025-12-11 21:21:56 +00:00
public function handle(): void
{
2025-12-09 20:27:03 +00:00
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$query = $_REQUEST['q'] ?? null;
if ($query !== null && !$this->bang->tryRedirect($query)) {
$instance = rtrim($this->cache->getRandomUrl(), '/');
2025-12-11 21:21:56 +00:00
$url = $instance . '/search?' . http_build_query(['q' => $query]);
2025-12-09 20:27:03 +00:00
if ($method === 'GET') {
2025-12-11 21:21:56 +00:00
header("Location: $url");
exit;
2025-12-09 20:27:03 +00:00
} elseif ($method === 'POST') {
2025-12-11 21:21:56 +00:00
header("Location: $url"); // Redirige en GET pour simplifier
exit;
2025-12-09 20:27:03 +00:00
} 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') {
2025-12-11 21:21:56 +00:00
header("Location: $instance");
exit;
2025-12-09 20:27:03 +00:00
} else {
http_response_code(405);
exit('Méthode non autorisée');
}
}
2025-06-24 13:39:47 +02:00
}
2025-12-11 21:21:56 +00:00
}
class EngineHandler
{
public function __construct(private EngineManager $engineManager, private BangManager $bang) {}
public function handle(): void
{
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$query = $_REQUEST['q'] ?? null;
if ($query !== null && !$this->bang->tryRedirect($query)) {
$engineTemplate = $this->engineManager->getRandomEngine();
$url = sprintf($engineTemplate, rawurlencode($query));
2025-06-24 13:39:47 +02:00
2025-12-11 21:21:56 +00:00
if ($method === 'GET') {
header("Location: $url");
exit;
} elseif ($method === 'POST') {
header("Location: $url"); // Redirige en GET
exit;
} else {
http_response_code(405);
exit('Méthode non autorisée');
}
} elseif ($query === null) {
// Rediriger vers la page d'accueil du premier moteur
$engineTemplate = $this->engineManager->getRandomEngine();
$homepage = str_replace('?query=%s', '', str_replace('?q=%s', '', $engineTemplate));
if ($method === 'GET') {
header("Location: $homepage");
exit;
} elseif ($method === 'POST') {
header("Location: $homepage");
exit;
} else {
http_response_code(405);
exit('Méthode non autorisée');
}
}
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
}
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
// -------------------- EXECUTION --------------------
try {
2025-12-11 21:21:56 +00:00
$bang = new BangManager();
$engineManager = new EngineManager();
if (USE_SEARX_INSTANCES) {
$cache = new CacheManager();
if ($cache->isExpired()) {
if (!$cache->downloadInstances() || !$cache->extractValidUrls()) {
http_response_code(500);
exit('Erreur interne : veuillez réessayer plus tard.');
}
2025-12-09 20:27:03 +00:00
}
2025-12-11 21:21:56 +00:00
$handler = new SearxHandler($cache, $bang);
} else {
$handler = new EngineHandler($engineManager, $bang);
2025-06-24 13:39:47 +02:00
}
2025-12-09 20:27:03 +00:00
$handler->handle();
2025-06-24 13:39:47 +02:00
2025-12-09 20:27:03 +00:00
} catch (\Throwable $e) {
2025-12-11 21:21:56 +00:00
// Ne pas exposer les détails techniques → Point 3
error_log('Erreur interne : ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
2025-12-09 20:27:03 +00:00
http_response_code(500);
2025-12-11 21:21:56 +00:00
exit('Erreur interne : veuillez réessayer plus tard.');
}