diff --git a/application/Espo/Controllers/Attachment.php b/application/Espo/Controllers/Attachment.php index 9eed56f100..06a51669fe 100644 --- a/application/Espo/Controllers/Attachment.php +++ b/application/Espo/Controllers/Attachment.php @@ -122,31 +122,6 @@ class Attachment extends RecordBase ->getValueMap(); } - /** - * @throws BadRequest - * @throws Forbidden - * @throws NotFound - */ - public function getActionFile(Request $request, Response $response): void - { - $id = $request->getRouteParam('id'); - - if (!$id) { - throw new BadRequest(); - } - - $fileData = $this->getAttachmentService()->getFileData($id); - - if ($fileData->getType()) { - $response->setHeader('Content-Type', $fileData->getType()); - } - - $response - ->setHeader('Content-Disposition', 'attachment; filename="' . $fileData->getName() . '"') - ->setHeader('Content-Length', (string) $fileData->getSize()) - ->setBody($fileData->getStream()); - } - /** * @throws BadRequest * @throws Forbidden diff --git a/application/Espo/Core/Api/ActionHandler.php b/application/Espo/Core/Api/ActionHandler.php index 2d25ebeed1..7118128ad9 100644 --- a/application/Espo/Core/Api/ActionHandler.php +++ b/application/Espo/Core/Api/ActionHandler.php @@ -34,6 +34,7 @@ use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Exceptions\NotFound; +use Espo\Core\Utils\Config; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface as Psr7Response; use Psr\Http\Message\ServerRequestInterface; @@ -49,7 +50,8 @@ class ActionHandler implements RequestHandlerInterface public function __construct( private Action $action, - private ProcessData $processData + private ProcessData $processData, + private Config $config ) {} /** @@ -73,15 +75,27 @@ class ActionHandler implements RequestHandlerInterface private function prepareResponse(Response $response): Psr7Response { - $psr7Response = $response instanceof ResponseWrapper ? - $response->toPsr7() : - self::responseToPsr7($response); - - if (!$psr7Response->getHeader('Content-Type')) { - $psr7Response = $psr7Response->withHeader('Content-Type', self::DEFAULT_CONTENT_TYPE); + if (!$response->hasHeader('Content-Type')) { + $response->setHeader('Content-Type', self::DEFAULT_CONTENT_TYPE); } - return $psr7Response; + if (!$response->hasHeader('Cache-Control')) { + $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); + } + + if (!$response->hasHeader('Expires')) { + $response->setHeader('Expires', '0'); + } + + if (!$response->hasHeader('Last-Modified')) { + $response->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); + } + + $response->setHeader('X-App-Timestamp', (string) ($this->config->get('appTimestamp') ?? '0')); + + return $response instanceof ResponseWrapper ? + $response->toPsr7() : + self::responseToPsr7($response); } private static function responseToPsr7(Response $response): Psr7Response diff --git a/application/Espo/Core/Api/ResponseComposer.php b/application/Espo/Core/Api/ResponseComposer.php index d96ae485e6..298c08c0d7 100644 --- a/application/Espo/Core/Api/ResponseComposer.php +++ b/application/Espo/Core/Api/ResponseComposer.php @@ -41,13 +41,19 @@ class ResponseComposer * @param array|stdClass|scalar|null $data A data to encode. */ public static function json(mixed $data): Response + { + return self::empty() + ->writeBody(Json::encode($data)) + ->setHeader('Content-Type', 'application/json'); + } + + /** + * Compose an empty response. + */ + public static function empty(): Response { $psr7Response = (new ResponseFactory())->createResponse(); - $response = new ResponseWrapper($psr7Response); - $response->writeBody(Json::encode($data)); - $response->setHeader('Content-Type', 'application/json'); - - return $response; + return new ResponseWrapper($psr7Response); } } diff --git a/application/Espo/Core/Api/RouteProcessor.php b/application/Espo/Core/Api/RouteProcessor.php index 102ad7216d..22cfee3b80 100644 --- a/application/Espo/Core/Api/RouteProcessor.php +++ b/application/Espo/Core/Api/RouteProcessor.php @@ -167,6 +167,7 @@ class RouteProcessor $handler = new ActionHandler( action: $action, processData: $processData, + config: $this->config, ); $dispatcher = new MiddlewareDispatcher($handler); diff --git a/application/Espo/Resources/routes.json b/application/Espo/Resources/routes.json index f3218b989f..2914587d3f 100644 --- a/application/Espo/Resources/routes.json +++ b/application/Espo/Resources/routes.json @@ -271,10 +271,7 @@ { "route": "/Attachment/file/:id", "method": "get", - "params": { - "controller": "Attachment", - "action": "file" - } + "actionClassName": "Espo\\Tools\\Attachment\\Api\\GetFile" }, { "route": "/Attachment/chunk/:id", diff --git a/application/Espo/Tools/Attachment/Api/GetFile.php b/application/Espo/Tools/Attachment/Api/GetFile.php new file mode 100644 index 0000000000..dded7b43cf --- /dev/null +++ b/application/Espo/Tools/Attachment/Api/GetFile.php @@ -0,0 +1,67 @@ +getRouteParam('id'); + + if (!$id) { + throw new BadRequest(); + } + + $fileData = $this->service->getFileData($id); + + $response = ResponseComposer::empty() + ->setHeader('Content-Disposition', 'attachment; filename="' . $fileData->getName() . '"') + ->setHeader('Content-Length', (string) $fileData->getSize()) + ->setBody($fileData->getStream()); + + if ($fileData->getType()) { + $response->setHeader('Content-Type', $fileData->getType()); + } + + return $response; + } +}