fix: block doc artifacts and add error templates
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests du groupe « dons » : synchronisation dons.php / dons.php.sample,
|
||||
* gabarits d'erreur 404/500 et blocage des artefacts de documentation.
|
||||
*
|
||||
* ARC-7 : dons.php inclut 404.php / 500.php — les gabarits .sample existent
|
||||
* et le chemin d'erreur affiche une page propre (sans warning PHP).
|
||||
* SEC-6 : DEPLOY.adoc/html/pdf à la racine sont bloqués par les modèles
|
||||
* nginx / Apache.
|
||||
* Dérive : linkUrlsInText() présent dans dons.php comme dans le .sample.
|
||||
*/
|
||||
|
||||
$root = dirname(__DIR__, 2);
|
||||
|
||||
// --- Synchronisation dons.php / dons.php.sample -----------------------------
|
||||
|
||||
$donsLive = file_get_contents($root . '/dons.php');
|
||||
$donsSample = file_get_contents($root . '/dons.php.sample');
|
||||
|
||||
assertContains(
|
||||
'function linkUrlsInText',
|
||||
$donsLive,
|
||||
'dons.php définit linkUrlsInText() comme le .sample'
|
||||
);
|
||||
assertContains(
|
||||
'linkUrlsInText(DONATIONS_OKI_DISCLAIMER)',
|
||||
$donsLive,
|
||||
'dons.php rend le message de transparence avec linkUrlsInText()'
|
||||
);
|
||||
assertNotContains(
|
||||
'htmlspecialchars(DONATIONS_OKI_DISCLAIMER)',
|
||||
$donsLive,
|
||||
'dons.php n\'utilise plus un simple htmlspecialchars() pour le message de transparence'
|
||||
);
|
||||
|
||||
// La définition de la fonction doit être identique dans les deux fichiers
|
||||
// (verrou anti-dérive entre le modèle et la copie d'instance)
|
||||
$extractFunction = function (string $source): ?string {
|
||||
if (preg_match('/(function linkUrlsInText\(string \$text\): string \{.*?\n\})/s', $source, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
assertEquals(
|
||||
$extractFunction($donsSample),
|
||||
$extractFunction($donsLive),
|
||||
'linkUrlsInText() est identique dans dons.php et dons.php.sample'
|
||||
);
|
||||
|
||||
// --- linkUrlsInText() : comportement ----------------------------------------
|
||||
|
||||
if (!function_exists('linkUrlsInText')) {
|
||||
eval($extractFunction($donsSample));
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
'Texte & "échappé"',
|
||||
linkUrlsInText('Texte & "échappé"'),
|
||||
'linkUrlsInText échappe le texte sans URL'
|
||||
);
|
||||
|
||||
$linked = linkUrlsInText('Soutenez-nous sur https://example.com/dons merci');
|
||||
assertContains(
|
||||
'<a href="https://example.com/dons" target="_blank" rel="noopener noreferrer">https://example.com/dons</a>',
|
||||
$linked,
|
||||
'linkUrlsInText rend les URLs cliquables avec target et rel'
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
'<script>alert(1)</script>',
|
||||
linkUrlsInText('<script>alert(1)</script>'),
|
||||
'linkUrlsInText échappe le HTML injecté dans le texte'
|
||||
);
|
||||
|
||||
assertContains(
|
||||
'href="https://example.com/?a=1&b=2"',
|
||||
linkUrlsInText('Lien : https://example.com/?a=1&b=2'),
|
||||
'linkUrlsInText échappe les & dans l\'URL du lien'
|
||||
);
|
||||
|
||||
// --- Gabarits d'erreur 404.php.sample / 500.php.sample (ARC-7) ---------------
|
||||
|
||||
foreach (['404' => '404.php.sample', '500' => '500.php.sample'] as $code => $file) {
|
||||
$path = $root . '/' . $file;
|
||||
assertTrue(file_exists($path), "$file existe");
|
||||
$template = file_get_contents($path);
|
||||
|
||||
assertContains(
|
||||
"http_response_code($code)",
|
||||
$template,
|
||||
"$file positionne le code HTTP $code"
|
||||
);
|
||||
assertContains(
|
||||
'noindex',
|
||||
$template,
|
||||
"$file demande la non-indexation (robots noindex)"
|
||||
);
|
||||
assertNotContains(
|
||||
'style="',
|
||||
$template,
|
||||
"$file n'utilise pas d'attribut style inline (bloqué par la CSP)"
|
||||
);
|
||||
|
||||
// Tout <script> inline (sans src) doit porter un nonce CSP
|
||||
preg_match_all('/<script(?![^>]*\bsrc=)[^>]*>/i', $template, $inlineScripts);
|
||||
$allNonced = true;
|
||||
foreach ($inlineScripts[0] as $scriptTag) {
|
||||
if (strpos($scriptTag, 'nonce=') === false) {
|
||||
$allNonced = false;
|
||||
}
|
||||
}
|
||||
assertTrue($allNonced, "$file : tous les scripts inline portent un nonce CSP");
|
||||
|
||||
// Le gabarit doit être syntaxiquement valide
|
||||
$lint = shell_exec(escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($path) . ' 2>&1');
|
||||
assertContains('No syntax errors', $lint, "$file passe php -l");
|
||||
}
|
||||
|
||||
// --- Blocage des artefacts de documentation (SEC-6) --------------------------
|
||||
|
||||
$nginxConf = file_get_contents($root . '/conf/nginx.conf.sample');
|
||||
assertContains(
|
||||
'^/[^/]+\.(adoc|html|pdf)$',
|
||||
$nginxConf,
|
||||
'nginx.conf.sample bloque les .adoc/.html/.pdf à la racine web'
|
||||
);
|
||||
|
||||
$htaccess = file_get_contents($root . '/conf/.htaccess.sample');
|
||||
assertContains(
|
||||
'RewriteRule ^[^/]+\.(adoc|html|pdf)$ - [F,L,NC]',
|
||||
$htaccess,
|
||||
'.htaccess.sample bloque les .adoc/.html/.pdf à la racine web'
|
||||
);
|
||||
|
||||
// --- dons.php : chemins d'erreur 404 et 500 (exécution réelle) ---------------
|
||||
|
||||
// dons.php inclut 404.php / 500.php (copies d'instance, non versionnées) :
|
||||
// on les crée à partir des .sample si elles sont absentes, puis on nettoie.
|
||||
$createdCopies = [];
|
||||
foreach (['404.php', '500.php'] as $copy) {
|
||||
$copyPath = $root . '/' . $copy;
|
||||
if (!file_exists($copyPath)) {
|
||||
copy($root . '/' . $copy . '.sample', $copyPath);
|
||||
$createdCopies[] = $copyPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute dons.php dans un sous-processus PHP avec des constantes prédéfinies
|
||||
* (PEERTUBE_URL privée : aucun appel réseau, cf. bootstrap.php).
|
||||
*
|
||||
* @param array $defines Constantes définies avant le chargement de dons.php
|
||||
* @return string Sortie complète du sous-processus (stdout + stderr)
|
||||
*/
|
||||
$runDonsPage = function (array $defines) use ($root): string {
|
||||
$lines = ['<?php'];
|
||||
foreach ($defines as $name => $value) {
|
||||
$lines[] = 'define(' . var_export($name, true) . ', ' . var_export($value, true) . ');';
|
||||
}
|
||||
$lines[] = 'chdir(' . var_export($root, true) . ');';
|
||||
$lines[] = 'require ' . var_export($root . '/dons.php', true) . ';';
|
||||
|
||||
$runner = tempnam(sys_get_temp_dir(), 'dons-runner-') . '.php';
|
||||
file_put_contents($runner, implode("\n", $lines) . "\n");
|
||||
$output = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($runner) . ' 2>&1');
|
||||
unlink($runner);
|
||||
|
||||
return $output ?? '';
|
||||
};
|
||||
|
||||
$noPlatform = [
|
||||
'PEERTUBE_URL' => 'http://127.0.0.1', // URL privée volontaire : bloque tout appel API réel
|
||||
'APP_HOST_NAME' => 'test.local',
|
||||
'DONATIONS_ENABLED' => true,
|
||||
'LIBERAPAY_URL' => '',
|
||||
'KOFI_URL' => '',
|
||||
'STRIPE_ENABLED' => false,
|
||||
];
|
||||
|
||||
$phpErrorNeedles = ['PHP Warning', 'PHP Notice', 'PHP Deprecated', 'PHP Fatal error',
|
||||
'Fatal error', 'Warning:', 'Notice:', 'Deprecated:', 'Uncaught'];
|
||||
|
||||
// Dons activés mais aucune plateforme configurée -> page 500 propre
|
||||
$output500 = $runDonsPage($noPlatform);
|
||||
assertContains(
|
||||
'<p class="error-code" aria-hidden="true">500</p>',
|
||||
$output500,
|
||||
'dons.php sans plateforme configurée affiche la page 500'
|
||||
);
|
||||
foreach ($phpErrorNeedles as $needle) {
|
||||
assertNotContains($needle, $output500, "page 500 sans erreur PHP ($needle)");
|
||||
}
|
||||
|
||||
// Dons désactivés -> page 404 propre
|
||||
$output404 = $runDonsPage(['DONATIONS_ENABLED' => false] + $noPlatform);
|
||||
assertContains(
|
||||
'<p class="error-code" aria-hidden="true">404</p>',
|
||||
$output404,
|
||||
'dons.php avec dons désactivés affiche la page 404'
|
||||
);
|
||||
foreach ($phpErrorNeedles as $needle) {
|
||||
assertNotContains($needle, $output404, "page 404 sans erreur PHP ($needle)");
|
||||
}
|
||||
|
||||
foreach ($createdCopies as $copyPath) {
|
||||
unlink($copyPath);
|
||||
}
|
||||
Reference in New Issue
Block a user