filepond: fix crash 'can't access property main, n.status is undefined'

Fixes three root causes of FilePond errors on TFE upload forms:

1. server.process.onerror accessed .status on a string (XHR response
   text body) — now extracts the body safely.

2. server.load was a bare URL string with no error handling — converted
   to object with onload/onerror to prevent FilePond internal _write
   crash when load.php returns HTTP errors.

3. destroyFilePondsIn now aborts in-flight processing before pond.destroy()
   to prevent stale XHR callbacks firing on a torn-down FilePond instance.

Server-side: FilepondHandler now emits Content-Type: text/plain on all
responses (PHP defaults to text/html on die(), confusing FilePond's
response parser).
This commit is contained in:
Pontoporeia
2026-06-09 21:53:08 +02:00
parent 38ef550397
commit 2829d13a16
4 changed files with 68 additions and 10 deletions

View File

@@ -81,6 +81,10 @@ class FilepondHandler
public function handleProcess(): never
{
// All responses from this endpoint must be text/plain
// (PHP defaults to text/html, which confuses FilePond on error).
header('Content-Type: text/plain; charset=utf-8');
error_log($this->logPrefix . ':process ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | files_keys=' . implode(',', array_keys($_FILES)) . ' | post_keys=' . implode(',', array_keys($_POST)));
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
@@ -197,7 +201,6 @@ class FilepondHandler
file_put_contents($tmpDir . '/manifest.json', json_encode($manifest, JSON_UNESCAPED_SLASHES));
error_log($this->logPrefix . ':process SUCCESS | file_id=' . $fileId . ' | queue_type=' . $queueType . ' | name=' . $originalName);
header('Content-Type: text/plain; charset=utf-8');
echo $fileId;
exit;
}
@@ -209,6 +212,7 @@ class FilepondHandler
public function handleLoad(): never
{
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(405);
die('Méthode non autorisée.');
}
@@ -224,6 +228,7 @@ class FilepondHandler
// Numeric IDs → DB files
$dbId = filter_var($fileId, FILTER_VALIDATE_INT);
if ($dbId === false || $dbId <= 0) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(400);
die('ID invalide.');
}
@@ -237,6 +242,7 @@ class FilepondHandler
if (!$fileRow) {
error_log($this->logPrefix . ':load DB NOT FOUND | db_id=' . $dbId);
header('Content-Type: text/plain; charset=utf-8');
http_response_code(404);
die('Fichier introuvable.');
}
@@ -259,6 +265,7 @@ class FilepondHandler
exit;
}
if (str_starts_with($filePath, 'http://') || str_starts_with($filePath, 'https://')) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(404);
die('URL — pas de flux direct.');
}
@@ -266,6 +273,7 @@ class FilepondHandler
$absPath = STORAGE_ROOT . '/' . $filePath;
if (!file_exists($absPath) || !is_readable($absPath)) {
error_log($this->logPrefix . ':load DISK MISSING | db_id=' . $dbId . ' | absPath=' . $absPath);
header('Content-Type: text/plain; charset=utf-8');
http_response_code(404);
die('Fichier absent du disque.');
}
@@ -286,6 +294,8 @@ class FilepondHandler
public function handleRemove(): never
{
header('Content-Type: text/plain; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
http_response_code(405);
die('Méthode non autorisée.');
@@ -339,6 +349,8 @@ class FilepondHandler
public function handleRevert(): never
{
header('Content-Type: text/plain; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
http_response_code(405);
die('Méthode non autorisée.');
@@ -480,12 +492,14 @@ class FilepondHandler
$manifestPath = $tmpDir . '/manifest.json';
if (!is_dir($tmpDir) || !file_exists($manifestPath)) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(404);
die('Fichier temporaire introuvable.');
}
$manifest = json_decode(file_get_contents($manifestPath), true);
if (!is_array($manifest) || ($manifest['session_id'] ?? '') !== session_id()) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(403);
die('Session invalide.');
}
@@ -502,6 +516,7 @@ class FilepondHandler
closedir($dh);
if ($actualFile === null || !file_exists($actualFile)) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(404);
die('Fichier temporaire introuvable.');
}
@@ -569,6 +584,7 @@ class FilepondHandler
private function validateMimeExt(string $queueType, string $mimeType, string $ext): void
{
// Content-Type already set by handleProcess() header
$allowedMimes = self::QUEUE_MIME_MAP[$queueType] ?? null;
if ($allowedMimes !== null) {
if (!in_array($mimeType, $allowedMimes, true)) {