query->get('path', ''); try { $path = $this->pathResolver->normalizeRelative($rawPath); } catch (InvalidPathException) { return new Response('Invalid path.', 400); } if ('' === $path) { throw new NotFoundHttpException('No file requested.'); } try { $stream = $this->fileSource->read($path); } catch (ShareNotConfiguredException) { return new Response('Share not configured.', 409); } catch (ShareConnectionException) { throw new NotFoundHttpException('File not found.'); } $name = basename($path); $extension = pathinfo($name, PATHINFO_EXTENSION); $mime = MimeTypes::getDefault()->getMimeTypes($extension)[0] ?? 'application/octet-stream'; // Anti-XSS : seuls des types non exécutables sont servis inline (images hors SVG, PDF). // Tout le reste (HTML, SVG, octet-stream, etc.) est forcé en attachment, même si inline est demandé. $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 { if (is_resource($stream)) { fpassthru($stream); fclose($stream); } }); $response->headers->set('Content-Type', $mime); $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; } }