Files
service_wopi/lib/Controllers/WopiController.php
T
2026-09-07 21:58:35 -04:00

68 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXM\ServiceWopi\Controllers;
use KTXC\Context\TenantContextInterface;
use KTXC\Http\Request\Request;
use KTXC\Http\Response\JsonResponse;
use KTXC\Http\Response\Response;
use KTXF\Controller\ControllerAbstract;
use KTXF\Routing\Attributes\AnonymousPrefixRoute;
use KTXF\Routing\Attributes\AnonymousRoute;
use KTXM\ServiceWopi\Host\HostException;
use KTXM\ServiceWopi\Host\WopiService;
class WopiController extends ControllerAbstract
{
public function __construct(private readonly TenantContextInterface $tenant, private readonly WopiService $wopi) {}
private const HEADERS = ['Cache-Control' => 'no-store', 'X-Content-Type-Options' => 'nosniff'];
#[AnonymousPrefixRoute('/wopi/files/', name: 'wopi.unsupported', methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])]
public function unsupported(Request $request): Response
{
return $request->getMethod() === 'GET'
? new Response('', 404, self::HEADERS)
: new Response('', 405, self::HEADERS + ['Allow' => 'GET']);
}
#[AnonymousRoute('/wopi/files/{id}', name: 'wopi.retrieve_meta')]
public function retrieveMeta(Request $request, string $id): Response
{
return $this->respond($request, $id, static function (array $session, array $file, array $headers): Response {
return new JsonResponse([
'BaseFileName' => $file['label'], 'Size' => $file['size'], 'Version' => $file['version'],
'OwnerId' => $session['user'], 'UserId' => $session['user'],
'ReadOnly' => true, 'UserCanWrite' => false, 'SupportsUpdate' => false,
'SupportsLocks' => false, 'UserCanNotWriteRelative' => true,
], 200, $headers);
});
}
#[AnonymousRoute('/wopi/files/{id}/contents', name: 'wopi.retrieve_contents')]
public function retrieveContents(Request $request, string $id): Response
{
return $this->respond($request, $id, static fn (array $session, array $file, array $headers): Response =>
new Response($file['content'], 200, $headers + ['Content-Type' => 'application/octet-stream', 'Content-Length' => (string) $file['size']])
);
}
private function respond(Request $request, string $id, callable $response): Response
{
$headers = self::HEADERS;
if ($request->getMethod() !== 'GET') { return $this->unsupported($request); }
if (!preg_match('/^[a-f0-9]{32}$/D', $id)) { return new Response('', 404, $headers); }
try {
if (!$this->tenant->enabled()) { throw new HostException('Document access is unavailable.', 404); }
['session' => $session, 'file' => $file] = $this->wopi->access($this->tenant->requireIdentifier(), $id, $request);
$headers['X-WOPI-ItemVersion'] = $file['version'];
return $response($session, $file, $headers);
} catch (HostException $error) {
return new JsonResponse(['message' => $error->getMessage()], $error->status, $headers);
} catch (\Throwable) {
// Never log the request, query string, file contents, or access token.
return new JsonResponse(['message' => 'Document access is unavailable.'], 503, $headers);
}
}
}