2019-08-10 18:06:28 +02:00
< ? php
2020-02-22 18:14:36 +01:00
/**
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
2022-11-29 22:28:53 +01:00
* Copyright ( C ) 2019 - 2022 Jan Böhmer ( https :// github . com / jbtronics )
2020-02-22 18:14:36 +01:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
2020-01-05 15:46:58 +01:00
declare ( strict_types = 1 );
2019-08-10 18:06:28 +02:00
namespace App\Controller ;
2019-10-04 18:06:37 +02:00
use App\DataTables\AttachmentDataTable ;
2022-09-11 02:00:22 +02:00
use App\DataTables\Filters\AttachmentFilter ;
2025-09-07 20:04:48 +02:00
use App\DataTables\PartsDataTable ;
2019-08-12 15:47:57 +02:00
use App\Entity\Attachments\Attachment ;
2022-09-11 02:00:22 +02:00
use App\Form\Filters\AttachmentFilterType ;
2019-10-19 23:29:51 +02:00
use App\Services\Attachments\AttachmentManager ;
2022-09-11 02:00:22 +02:00
use App\Services\Trees\NodesListBuilder ;
2024-08-21 22:15:26 +02:00
use App\Settings\BehaviorSettings\TableSettings ;
2026-02-24 22:27:33 +01:00
use App\Settings\SystemSettings\AttachmentsSettings ;
2019-10-04 18:06:37 +02:00
use Omines\DataTablesBundle\DataTableFactory ;
2020-01-05 22:49:00 +01:00
use RuntimeException ;
2019-08-10 18:06:28 +02:00
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ;
use Symfony\Component\HttpFoundation\BinaryFileResponse ;
2019-10-04 18:06:37 +02:00
use Symfony\Component\HttpFoundation\Request ;
2020-01-05 22:49:00 +01:00
use Symfony\Component\HttpFoundation\Response ;
2019-08-10 18:06:28 +02:00
use Symfony\Component\HttpFoundation\ResponseHeaderBag ;
2024-03-03 20:37:33 +01:00
use Symfony\Component\Routing\Attribute\Route ;
2019-08-10 18:06:28 +02:00
class AttachmentFileController extends AbstractController
{
2026-02-24 22:27:33 +01:00
public function __construct ( private readonly AttachmentManager $helper )
2019-08-10 18:06:28 +02:00
{
2026-02-24 22:27:33 +01:00
}
2020-03-30 16:56:58 +02:00
2026-02-24 22:27:33 +01:00
#[Route(path: '/attachment/{id}/sandbox', name: 'attachment_html_sandbox')]
public function htmlSandbox ( Attachment $attachment , AttachmentsSettings $attachmentsSettings ) : Response
{
//Check if the sandbox is enabled in the settings, as it can be a security risk if used without proper precautions, so it should be opt-in
if ( ! $attachmentsSettings -> showHTMLAttachments ) {
throw $this -> createAccessDeniedException ( 'The HTML sandbox for attachments is disabled in the settings, as it can be a security risk if used without proper precautions. Please enable it in the settings if you want to use it.' );
2019-08-10 18:06:28 +02:00
}
2026-02-24 22:27:33 +01:00
$this -> checkPermissions ( $attachment );
$file_path = $this -> helper -> toAbsoluteInternalFilePath ( $attachment );
$attachmentContent = file_get_contents ( $file_path );
$response = $this -> render ( 'attachments/html_sandbox.html.twig' , [
'attachment' => $attachment ,
'content' => $attachmentContent ,
]);
//Set an CSP that allows to run inline scripts, styles and images from external ressources, but does not allow any connections or others.
//Also set the sandbox CSP directive with only "allow-script" to run basic scripts
$response -> headers -> set ( 'Content-Security-Policy' , " default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; sandbox allow-scripts; " );
//Forbid to embed the attachment render page in an iframe to prevent clickjacking, as it is not used anywhere else for now
$response -> headers -> set ( 'X-Frame-Options' , 'DENY' );
return $response ;
}
2019-08-10 18:06:28 +02:00
2026-02-24 22:27:33 +01:00
/**
* Download the selected attachment .
*/
#[Route(path: '/attachment/{id}/download', name: 'attachment_download')]
public function download ( Attachment $attachment ) : BinaryFileResponse
{
$this -> checkPermissions ( $attachment );
$file_path = $this -> helper -> toAbsoluteInternalFilePath ( $attachment );
2019-08-10 18:06:28 +02:00
$response = new BinaryFileResponse ( $file_path );
2026-02-24 22:48:18 +01:00
$response = $this -> forbidHTMLContentType ( $response );
2019-08-10 18:06:28 +02:00
//Set header content disposition, so that the file will be downloaded
2026-02-24 22:48:18 +01:00
$response -> setContentDisposition ( ResponseHeaderBag :: DISPOSITION_ATTACHMENT , $attachment -> getFilename ());
2019-08-10 18:06:28 +02:00
return $response ;
}
/**
2019-11-09 00:47:20 +01:00
* View the attachment .
2019-08-10 18:06:28 +02:00
*/
2023-05-28 01:21:05 +02:00
#[Route(path: '/attachment/{id}/view', name: 'attachment_view')]
2026-02-24 22:27:33 +01:00
public function view ( Attachment $attachment ) : BinaryFileResponse
{
$this -> checkPermissions ( $attachment );
$file_path = $this -> helper -> toAbsoluteInternalFilePath ( $attachment );
$response = new BinaryFileResponse ( $file_path );
2026-02-24 22:48:18 +01:00
$response = $this -> forbidHTMLContentType ( $response );
2026-02-24 22:27:33 +01:00
//Set header content disposition, so that the file will be downloaded
2026-02-24 22:48:18 +01:00
$response -> setContentDisposition ( ResponseHeaderBag :: DISPOSITION_INLINE , $attachment -> getFilename ());
return $response ;
}
private function forbidHTMLContentType ( BinaryFileResponse $response ) : BinaryFileResponse
{
$mimeType = $response -> getFile () -> getMimeType ();
if ( $mimeType === 'text/html' ) {
$mimeType = 'text/plain' ;
}
$response -> headers -> set ( 'Content-Type' , $mimeType );
2026-02-24 22:27:33 +01:00
return $response ;
}
private function checkPermissions ( Attachment $attachment ) : void
2019-08-10 18:06:28 +02:00
{
2019-08-20 18:39:57 +02:00
$this -> denyAccessUnlessGranted ( 'read' , $attachment );
2019-08-10 18:06:28 +02:00
2020-03-30 16:56:58 +02:00
if ( $attachment -> isSecure ()) {
$this -> denyAccessUnlessGranted ( 'show_private' , $attachment );
}
2025-02-22 17:29:14 +01:00
if ( ! $attachment -> hasInternal ()) {
throw $this -> createNotFoundException ( 'The file for this attachment is external and not stored locally!' );
2019-08-10 18:06:28 +02:00
}
2026-02-24 22:27:33 +01:00
if ( ! $this -> helper -> isInternalFileExisting ( $attachment )) {
2024-08-24 15:49:45 +02:00
throw $this -> createNotFoundException ( 'The file associated with the attachment is not existing!' );
2019-08-10 18:06:28 +02:00
}
}
2023-05-28 01:21:05 +02:00
#[Route(path: '/attachment/list', name: 'attachment_list')]
2024-08-21 22:15:26 +02:00
public function attachmentsTable ( Request $request , DataTableFactory $dataTableFactory , NodesListBuilder $nodesListBuilder ,
TableSettings $tableSettings ) : Response
2019-10-04 18:06:37 +02:00
{
2022-11-05 23:49:53 +01:00
$this -> denyAccessUnlessGranted ( '@attachments.list_attachments' );
2019-10-04 18:06:37 +02:00
2022-09-11 02:00:22 +02:00
$formRequest = clone $request ;
$formRequest -> setMethod ( 'GET' );
$filter = new AttachmentFilter ( $nodesListBuilder );
$filterForm = $this -> createForm ( AttachmentFilterType :: class , $filter , [ 'method' => 'GET' ]);
$filterForm -> handleRequest ( $formRequest );
2025-09-07 20:04:48 +02:00
$table = $dataTableFactory -> createFromType ( AttachmentDataTable :: class , [ 'filter' => $filter ], [ 'pageLength' => $tableSettings -> fullDefaultPageSize , 'lengthMenu' => PartsDataTable :: LENGTH_MENU ])
2019-10-04 18:06:37 +02:00
-> handleRequest ( $request );
if ( $table -> isCallback ()) {
return $table -> getResponse ();
}
return $this -> render ( 'attachment_list.html.twig' , [
2019-11-09 00:47:20 +01:00
'datatable' => $table ,
2023-05-28 01:21:05 +02:00
'filterForm' => $filterForm ,
2019-10-04 18:06:37 +02:00
]);
}
2019-11-09 00:47:20 +01:00
}