<?php
// Relais CERT-FR pour AvisCERT
// Retourne exactement les champs consommés par AvisCERT :
// title, link, description, pubDate

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Cache-Control: public, max-age=300');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

$feedUrl = 'https://www.cert.ssi.gouv.fr/avis/feed/';
$cacheFile = sys_get_temp_dir() . '/aviscert-certfr-cache.json';
$cacheTtl = 300;

function sendJson(array $data, int $status = 200): never {
    http_response_code($status);
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function readFreshCache(string $file, int $ttl): ?array {
    if (!is_file($file) || (time() - filemtime($file)) > $ttl) {
        return null;
    }
    $raw = @file_get_contents($file);
    if ($raw === false) return null;
    $data = json_decode($raw, true);
    return is_array($data) ? $data : null;
}

function readAnyCache(string $file): ?array {
    if (!is_file($file)) return null;
    $raw = @file_get_contents($file);
    if ($raw === false) return null;
    $data = json_decode($raw, true);
    return is_array($data) ? $data : null;
}

if (($cached = readFreshCache($cacheFile, $cacheTtl)) !== null) {
    sendJson($cached);
}

$ch = curl_init($feedUrl);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_CONNECTTIMEOUT => 8,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_USERAGENT => 'AvisCERT-Relay/1.0 (+https://anssi.pokeluche.fr)',
    CURLOPT_HTTPHEADER => ['Accept: application/rss+xml, application/xml, text/xml;q=0.9, */*;q=0.8'],
]);
$xmlRaw = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$curlError = curl_error($ch);
curl_close($ch);

if ($xmlRaw === false || $httpCode < 200 || $httpCode >= 300) {
    if (($cached = readAnyCache($cacheFile)) !== null) {
        sendJson($cached);
    }
    sendJson(['error' => 'Impossible de récupérer le flux CERT-FR', 'detail' => $curlError], 502);
}

libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlRaw, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($xml === false) {
    if (($cached = readAnyCache($cacheFile)) !== null) {
        sendJson($cached);
    }
    sendJson(['error' => 'Flux CERT-FR XML invalide'], 502);
}

$items = [];
$nodes = $xml->channel->item ?? [];
foreach ($nodes as $item) {
    $title = trim((string)($item->title ?? ''));
    $link = trim((string)($item->link ?? ''));
    $description = trim((string)($item->description ?? ''));
    $pubDate = trim((string)($item->pubDate ?? ''));

    // AvisCERT injecte description dans innerHTML. On conserve le contenu texte,
    // en retirant les balises provenant du RSS pour éviter d'injecter du HTML tiers.
    $description = trim(html_entity_decode(strip_tags($description), ENT_QUOTES | ENT_HTML5, 'UTF-8'));

    if ($link === '') continue;

    $items[] = [
        'title' => $title,
        'link' => $link,
        'description' => $description,
        'pubDate' => $pubDate,
    ];
}

@file_put_contents($cacheFile, json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
sendJson($items);
