fix(share) : durcissement download (allowlist inline anti-XSS + nosniff) et masquage des erreurs SMB

This commit is contained in:
Matthieu
2026-06-03 17:42:36 +02:00
parent 74b6d298fb
commit 4ffa19e53f
2 changed files with 11 additions and 7 deletions
@@ -39,8 +39,8 @@ class ShareBrowseController extends AbstractController
$entries = $this->fileSource->dir($path); $entries = $this->fileSource->dir($path);
} catch (ShareNotConfiguredException) { } catch (ShareNotConfiguredException) {
return new JsonResponse(['error' => 'Share not configured.'], 409); return new JsonResponse(['error' => 'Share not configured.'], 409);
} catch (ShareConnectionException $e) { } catch (ShareConnectionException) {
return new JsonResponse(['error' => $e->getMessage()], 502); return new JsonResponse(['error' => 'Unable to reach the file share.'], 502);
} }
return new JsonResponse([ return new JsonResponse([
@@ -48,17 +48,19 @@ class ShareDownloadController extends AbstractController
$stream = $this->fileSource->read($path); $stream = $this->fileSource->read($path);
} catch (ShareNotConfiguredException) { } catch (ShareNotConfiguredException) {
return new Response('Share not configured.', 409); return new Response('Share not configured.', 409);
} catch (ShareConnectionException $e) { } catch (ShareConnectionException) {
throw new NotFoundHttpException($e->getMessage()); throw new NotFoundHttpException('File not found.');
} }
$name = basename($path); $name = basename($path);
$extension = pathinfo($name, PATHINFO_EXTENSION); $extension = pathinfo($name, PATHINFO_EXTENSION);
$mime = MimeTypes::getDefault()->getMimeTypes($extension)[0] ?? 'application/octet-stream'; $mime = MimeTypes::getDefault()->getMimeTypes($extension)[0] ?? 'application/octet-stream';
// SVG toujours en attachment (anti-XSS) ; sinon respecte le paramètre demandé. // Anti-XSS : seuls des types non exécutables sont servis inline (images hors SVG, PDF).
$requested = 'attachment' === $request->query->get('disposition') ? 'attachment' : 'inline'; // Tout le reste (HTML, SVG, octet-stream, etc.) est forcé en attachment, même si inline est demandé.
$disposition = 'image/svg+xml' === $mime ? HeaderUtils::DISPOSITION_ATTACHMENT : $requested; $inlineSafe = ('image/svg+xml' !== $mime && str_starts_with($mime, 'image/')) || 'application/pdf' === $mime;
$wantInline = 'attachment' !== $request->query->get('disposition');
$disposition = ($inlineSafe && $wantInline) ? HeaderUtils::DISPOSITION_INLINE : HeaderUtils::DISPOSITION_ATTACHMENT;
$response = new StreamedResponse(function () use ($stream): void { $response = new StreamedResponse(function () use ($stream): void {
if (is_resource($stream)) { if (is_resource($stream)) {
@@ -68,6 +70,8 @@ class ShareDownloadController extends AbstractController
}); });
$response->headers->set('Content-Type', $mime); $response->headers->set('Content-Type', $mime);
$response->headers->set('Content-Disposition', HeaderUtils::makeDisposition($disposition, $name)); $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition($disposition, $name));
// Empêche le navigateur de "deviner" un type exécutable à partir du contenu.
$response->headers->set('X-Content-Type-Options', 'nosniff');
return $response; return $response;
} }