From 935fa0d2030f2642b50f5c61ab2f0caa1bec48d5 Mon Sep 17 00:00:00 2001 From: Alexander Piskun Date: Wed, 29 Nov 2023 12:59:48 +0300 Subject: [PATCH] part2: proxying JS files from ExApp Signed-off-by: Alexander Piskun --- appinfo/routes.php | 19 +++--- lib/Controller/MenuEntryController.php | 89 +++----------------------- lib/Db/UI/InitialStateMapper.php | 3 +- lib/Db/UI/ScriptMapper.php | 3 +- lib/Db/UI/StyleMapper.php | 3 +- lib/Middleware/ExAppUiMiddleware.php | 22 +++---- lib/ProxyResponse.php | 4 +- lib/Service/ExAppScriptsService.php | 24 +++++-- lib/Service/ExAppStylesService.php | 7 +- psalm.xml | 1 + 10 files changed, 60 insertions(+), 115 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 6fcd9c19..5985c429 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -12,17 +12,14 @@ ['name' => 'MenuEntry#ExAppIcon', 'url' => '/embedded/{appId}/{name}/icon', 'verb' => 'GET' , 'root' => '/embedded'], // Proxy -// ['name' => 'MenuEntry#ExAppProxySubLinksGet', -// 'url' => '/proxying/{appId}/{other}', 'verb' => 'GET' , 'root' => '/proxying', -// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], -// ['name' => 'MenuEntry#ExAppProxySubLinksPost', -// 'url' => '/proxying/{appId}/{other}', 'verb' => 'POST' , 'root' => '/proxying', -// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], -// ['name' => 'MenuEntry#ExAppProxySubLinksPut', -// 'url' => '/proxying/{appId}/{other}', 'verb' => 'PUT' , 'root' => '/proxying', -// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], - ['name' => 'MenuEntry#ExAppProxySubLinksGet', - 'url' => '/proxy/css/{appId}/{other}', 'verb' => 'GET' , 'root' => '', + ['name' => 'MenuEntry#ExAppProxyGet', + 'url' => '/proxy/{appId}/{other}', 'verb' => 'GET' , 'root' => '/proxy', + 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], + ['name' => 'MenuEntry#ExAppProxyPost', + 'url' => '/proxy/{appId}/{other}', 'verb' => 'POST' , 'root' => '/proxying', + 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], + ['name' => 'MenuEntry#ExAppProxyPut', + 'url' => '/proxy/{appId}/{other}', 'verb' => 'PUT' , 'root' => '/proxying', 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')], // ExApps actions diff --git a/lib/Controller/MenuEntryController.php b/lib/Controller/MenuEntryController.php index 1c47581d..c0cd1519 100644 --- a/lib/Controller/MenuEntryController.php +++ b/lib/Controller/MenuEntryController.php @@ -4,15 +4,13 @@ namespace OCA\AppAPI\Controller; -use DOMDocument; -use DOMException; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OCA\AppAPI\AppInfo\Application; use OCA\AppAPI\Attribute\AppAPIAuth; use OCA\AppAPI\ProxyResponse; use OCA\AppAPI\Service\AppAPIService; -use OCA\AppAPI\Service\ExAppMenuEntryService; use OCA\AppAPI\Service\ExAppInitialStateService; +use OCA\AppAPI\Service\ExAppMenuEntryService; use OCA\AppAPI\Service\ExAppScriptsService; use OCA\AppAPI\Service\ExAppStylesService; use OCP\AppFramework\Controller; @@ -31,10 +29,12 @@ use OCP\Http\Client\IResponse; use OCP\IRequest; use OCP\IURLGenerator; -use OCP\Util; class MenuEntryController extends Controller { + public bool $postprocess = false; + public array $jsProxyMap = []; + public function __construct( IRequest $request, private IInitialState $initialState, @@ -74,64 +74,14 @@ public function viewExAppPage(string $appId, string $name): TemplateResponse { foreach ($initialStates as $key => $value) { $this->initialState->provideInitialState($key, $value); } - $this->scriptsService->applyExAppScripts($appId, 'top_menu'); + $this->jsProxyMap = $this->scriptsService->applyExAppScripts($appId, 'top_menu'); $this->stylesService->applyExAppStyles($appId, 'top_menu'); + $this->postprocess = true; $response = new TemplateResponse(Application::APP_ID, 'main'); return $response; } - /** - * @NoCSRFRequired - * @NoAdminRequired - * @throws DOMException - */ - #[NoAdminRequired] - #[NoCSRFRequired] - public function ExAppIframeProxy(string $appId, string $name): Response { - $exApp = $this->service->getExApp($appId); - if ($exApp === null) { - return new NotFoundResponse(); - } - if (!$exApp->getEnabled()) { - return new NotFoundResponse(); - } - $menuEntry = $this->menuEntryService->getExAppMenuEntry($appId, $name); - if ($menuEntry === null) { - return new NotFoundResponse(); - } - $response = $this->service->aeRequestToExApp( - $exApp, $menuEntry->getRoute(), $this->userId, 'GET', request: $this->request - ); - if (is_array($response)) { - $error_response = new Response(); - return $error_response->setStatus(500); - } - $reHeaders = []; - foreach ($response->getHeaders() as $k => $values) { - $reHeaders[$k] = count($values) === 1 ? $values[0] : $values; - } - $reHeaders['content-security-policy'] = 'frame-ancestors *;'; - - $dom = new DOMDocument(); - @$dom->loadHTML($response->getBody(), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); - $base = $dom->createElement('base'); - $base->setAttribute( - 'href', - $this->url->getAbsoluteURL('index.php/apps/app_api/proxying/'. $exApp->getAppid() . '/') - ); - $base->setAttribute('target', '_parent'); - $head = $dom->getElementsByTagName('head')->item(0); - if ($head) { - if ($head->hasChildNodes()) { - $head->insertBefore($base, $head->firstChild); - } else { - $head->appendChild($base); - } - } - return new DataDisplayResponse($dom->saveHTML(), $response->getStatusCode(), $reHeaders); - } - private function createProxyResponse(string $path, IResponse $response, $cache = true): ProxyResponse { $content = $response->getBody(); $isHTML = pathinfo($path, PATHINFO_EXTENSION) === 'html'; @@ -181,28 +131,7 @@ private function createProxyResponse(string $path, IResponse $response, $cache = #[NoAdminRequired] #[NoCSRFRequired] - public function ExAppProxyCss(string $appId, string $other): Response { - $exApp = $this->service->getExApp($appId); - if ($exApp === null) { - return new NotFoundResponse(); - } - if (!$exApp->getEnabled()) { - return new NotFoundResponse(); - } - - $response = $this->service->aeRequestToExApp( - $exApp, '/' . $other, $this->userId, 'GET', request: $this->request - ); - if (is_array($response)) { - $error_response = new Response(); - return $error_response->setStatus(500); - } - return $this->createProxyResponse($other, $response); - } - - #[NoAdminRequired] - #[NoCSRFRequired] - public function ExAppProxySubLinksGet(string $appId, string $other): Response { + public function ExAppProxyGet(string $appId, string $other): Response { $exApp = $this->service->getExApp($appId); if ($exApp === null) { return new NotFoundResponse(); @@ -223,7 +152,7 @@ public function ExAppProxySubLinksGet(string $appId, string $other): Response { #[NoAdminRequired] #[NoCSRFRequired] - public function ExAppIframeProxySubLinksPost(string $appId, string $other): Response { + public function ExAppProxyPost(string $appId, string $other): Response { $exApp = $this->service->getExApp($appId); if ($exApp === null) { return new NotFoundResponse(); @@ -246,7 +175,7 @@ public function ExAppIframeProxySubLinksPost(string $appId, string $other): Resp #[NoAdminRequired] #[NoCSRFRequired] - public function ExAppIframeProxySubLinksPut(string $appId, string $other): Response { + public function ExAppProxyPut(string $appId, string $other): Response { $exApp = $this->service->getExApp($appId); if ($exApp === null) { return new NotFoundResponse(); diff --git a/lib/Db/UI/InitialStateMapper.php b/lib/Db/UI/InitialStateMapper.php index 9a90dcad..bf02d10f 100644 --- a/lib/Db/UI/InitialStateMapper.php +++ b/lib/Db/UI/InitialStateMapper.php @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array { ->where( $qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)), $qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR)) - )->executeQuery();; + )->executeQuery(); + ; return $result->fetchAll(); } } diff --git a/lib/Db/UI/ScriptMapper.php b/lib/Db/UI/ScriptMapper.php index aceb4270..7516a1f2 100644 --- a/lib/Db/UI/ScriptMapper.php +++ b/lib/Db/UI/ScriptMapper.php @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array { ->where( $qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)), $qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR)) - )->executeQuery();; + )->executeQuery(); + ; return $result->fetchAll(); } } diff --git a/lib/Db/UI/StyleMapper.php b/lib/Db/UI/StyleMapper.php index b8b0b0c8..dd296cd4 100644 --- a/lib/Db/UI/StyleMapper.php +++ b/lib/Db/UI/StyleMapper.php @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array { ->where( $qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)), $qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR)) - )->executeQuery();; + )->executeQuery(); + ; return $result->fetchAll(); } } diff --git a/lib/Middleware/ExAppUiMiddleware.php b/lib/Middleware/ExAppUiMiddleware.php index c323340b..3fbcd9fc 100644 --- a/lib/Middleware/ExAppUiMiddleware.php +++ b/lib/Middleware/ExAppUiMiddleware.php @@ -4,21 +4,14 @@ namespace OCA\AppAPI\Middleware; -use Exception; -use OCA\AppAPI\Attribute\AppAPIAuth; use OCA\AppAPI\Controller\MenuEntryController; -use OCA\AppAPI\Exceptions\AppAPIAuthNotValidException; use OCA\AppAPI\Service\AppAPIService; use OCP\AppFramework\Controller; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; use OCP\IL10N; use OCP\IRequest; use Psr\Log\LoggerInterface; -use ReflectionMethod; class ExAppUiMiddleware extends Middleware { @@ -31,12 +24,19 @@ public function __construct( } public function beforeOutput(Controller $controller, string $methodName, string $output) { - if ($controller instanceof MenuEntryController) { - $adjustedCss = preg_replace( - '/(href=")(\/.*?)(\/app_api\/css\/)(proxy\/css\/.*css")/', + if (($controller instanceof MenuEntryController) && ($controller->postprocess)) { + $correctedOutput = preg_replace( + '/(href=")(\/.*?)(\/app_api\/css\/)(proxy\/.*css")/', '$1/index.php/apps/app_api/$4', $output); - return $adjustedCss; + foreach ($controller->jsProxyMap as $key => $value) { + $correctedOutput = preg_replace( + '/(src=")(\/.*?)(\/app_api\/js\/)(proxy_js\/' . $key . '.js")/', + '$1/index.php/apps/app_api/proxy/' . $value . '.js"', + $correctedOutput, + limit: 1); + } + return $correctedOutput; } return $output; } diff --git a/lib/ProxyResponse.php b/lib/ProxyResponse.php index da2fa958..41b6453c 100644 --- a/lib/ProxyResponse.php +++ b/lib/ProxyResponse.php @@ -14,8 +14,8 @@ class ProxyResponse extends Response implements ICallbackResponse { private mixed $data; public function __construct(int $status = HttpAlias::STATUS_OK, - array $headers = [], mixed $data = null, int $length = 0, - string $mimeType = '', int $lastModified = 0) { + array $headers = [], mixed $data = null, int $length = 0, + string $mimeType = '', int $lastModified = 0) { parent::__construct(); $this->data = $data; $this->setStatus($status); diff --git a/lib/Service/ExAppScriptsService.php b/lib/Service/ExAppScriptsService.php index fd56844a..b12b78bb 100644 --- a/lib/Service/ExAppScriptsService.php +++ b/lib/Service/ExAppScriptsService.php @@ -4,6 +4,7 @@ namespace OCA\AppAPI\Service; +use LengthException; use OCA\AppAPI\AppInfo\Application; use OCA\AppAPI\Db\UI\ScriptMapper; use OCP\DB\Exception; @@ -12,6 +13,8 @@ class ExAppScriptsService { + public const MAX_JS_FILES = 20 + 1; //should be equal to number of files in "proxy_js" folder. + public function __construct( private ScriptMapper $mapper, private LoggerInterface $logger, @@ -29,16 +32,29 @@ public function clearExAppScripts(string $appId, string $type) { /** * @throws Exception */ - public function applyExAppScripts(string $appId, string $type): void { + public function applyExAppScripts(string $appId, string $type): array { // TODO: Add caching + $mapResult = []; $scripts = $this->mapper->findByAppIdType($appId, $type); + if (count($scripts) > self::MAX_JS_FILES) { + throw new LengthException('More than' . self::MAX_JS_FILES . 'JS files on one page are not supported.'); + } + + $i = 0; foreach ($scripts as $value) { + $fakeJsPath = 'proxy_js/' . $i; if (is_null($value['after_app_id'])) { - Util::addScript(Application::APP_ID, $value['path']); + Util::addScript(Application::APP_ID, $fakeJsPath); + } else { + Util::addScript(Application::APP_ID, $fakeJsPath, $value['after_app_id']); } - else { - Util::addScript(Application::APP_ID, $value['path'], $value['after_app_id']); + if (str_starts_with($value['path'], '/')) { + $mapResult[$i] = $appId . $value['path']; + } else { + $mapResult[$i] = $appId . '/' . $value['path']; } + $i++; } + return $mapResult; } } diff --git a/lib/Service/ExAppStylesService.php b/lib/Service/ExAppStylesService.php index bac56dc2..519d988c 100644 --- a/lib/Service/ExAppStylesService.php +++ b/lib/Service/ExAppStylesService.php @@ -37,10 +37,9 @@ public function applyExAppStyles(string $appId, string $type): void { foreach ($styles as $value) { if (str_starts_with($value['path'], '/')) { // in the future we should allow offload of styles to the NC instance if they start with '/' - $path = 'proxy/css/'. $appId . $value['path']; - } - else { - $path = 'proxy/css/'. $appId . '/' . $value['path']; + $path = 'proxy/'. $appId . $value['path']; + } else { + $path = 'proxy/'. $appId . '/' . $value['path']; } Util::addStyle(Application::APP_ID, $path); } diff --git a/psalm.xml b/psalm.xml index 9ea6e4d5..b014fdd1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -34,6 +34,7 @@ +