diff options
author | Olivier Paroz <github@oparoz.com> | 2015-01-18 04:26:24 +0300 |
---|---|---|
committer | Olivier Paroz <github@oparoz.com> | 2015-01-18 04:26:24 +0300 |
commit | ee5c0a94cb1571f5675fd9caf8a7ca0a77927d20 (patch) | |
tree | 28eec518e6e59d7b438caf327321844869ca9bd7 | |
parent | acfa4c237250cbd4c63cd6e75a1ea42574d34ece (diff) |
Services and middleware refactoring
* HTTP responses have been moved back to the controllers
* The environment service has been split in two
* An environment Class has been created to hold the variables the services need to do their job
* The tokencheckmiddleware is now the envcheckmiddleware and makes sure the token is valid before sending it to an environment object
* Some exceptions have been created in order to bubble up problems
* Services have now status codes compatible with HTTP codes
* ThumbnailService is now a child of PrevewService
-rw-r--r-- | appinfo/application.php | 75 | ||||
-rw-r--r-- | controller/jsonhttperror.php | 16 | ||||
-rw-r--r-- | controller/pagecontroller.php | 18 | ||||
-rw-r--r-- | controller/servicecontroller.php | 126 | ||||
-rw-r--r-- | environment/environment.php | 334 | ||||
-rw-r--r-- | environment/environmentexception.php | 28 | ||||
-rw-r--r-- | environment/notfoundenvexception.php | 26 | ||||
-rw-r--r-- | middleware/checkmiddleware.php | 13 | ||||
-rw-r--r-- | middleware/envcheckmiddleware.php | 316 | ||||
-rw-r--r-- | middleware/sharingcheckmiddleware.php | 8 | ||||
-rw-r--r-- | middleware/tokencheckmiddleware.php | 172 | ||||
-rw-r--r-- | preview/preview.php | 161 | ||||
-rw-r--r-- | service/base64encode.php | 42 | ||||
-rw-r--r-- | service/downloadservice.php | 83 | ||||
-rw-r--r-- | service/environmentservice.php | 385 | ||||
-rw-r--r-- | service/infoservice.php | 112 | ||||
-rw-r--r-- | service/notfoundserviceexception.php | 28 | ||||
-rw-r--r-- | service/previewservice.php | 235 | ||||
-rw-r--r-- | service/service.php | 90 | ||||
-rw-r--r-- | service/serviceexception.php | 9 | ||||
-rw-r--r-- | service/thumbnailservice.php | 111 |
21 files changed, 1285 insertions, 1103 deletions
diff --git a/appinfo/application.php b/appinfo/application.php index f1db99bc..00eb7939 100644 --- a/appinfo/application.php +++ b/appinfo/application.php @@ -12,7 +12,6 @@ namespace OCA\GalleryPlus\AppInfo; - use OCP\IContainer; use OCP\AppFramework\App; @@ -21,17 +20,17 @@ use OCP\AppFramework\IAppContainer; use OCA\GalleryPlus\Controller\PageController; use OCA\GalleryPlus\Controller\ServiceController; use OCA\GalleryPlus\Controller\PublicServiceController; +use OCA\GalleryPlus\Environment\Environment; use OCA\GalleryPlus\Preview\Preview; -use OCA\GalleryPlus\Service\EnvironmentService; use OCA\GalleryPlus\Service\InfoService; use OCA\GalleryPlus\Service\ThumbnailService; use OCA\GalleryPlus\Service\PreviewService; +use OCA\GalleryPlus\Service\DownloadService; use OCA\GalleryPlus\Middleware\SharingCheckMiddleware; -use OCA\GalleryPlus\Middleware\TokenCheckMiddleware; +use OCA\GalleryPlus\Middleware\EnvCheckMiddleware; use OCA\GalleryPlus\Utility\SmarterLogger; use OCA\GalleryPlus\Utility\Normalizer; - /** * Class Application * @@ -67,10 +66,14 @@ class Application extends App { return new ServiceController( $c->query('AppName'), $c->query('Request'), + $c->query('Environment'), $c->query('InfoService'), $c->query('ThumbnailService'), $c->query('PreviewService'), - $c->query('URLGenerator') + $c->query('DownloadService'), + $c->query('URLGenerator'), + $c->getServer() + ->createEventSource() ); } ); @@ -79,10 +82,14 @@ class Application extends App { return new PublicServiceController( $c->query('AppName'), $c->query('Request'), + $c->query('Environment'), $c->query('InfoService'), $c->query('ThumbnailService'), $c->query('PreviewService'), - $c->query('URLGenerator') + $c->query('DownloadService'), + $c->query('URLGenerator'), + $c->getServer() + ->createEventSource() ); } ); @@ -169,6 +176,7 @@ class Application extends App { 'CustomPreviewManager', function (IContainer $c) { return new Preview( $c->query('Config'), + $c->query('PreviewManager'), $c->query('SmarterLogger') ); } @@ -191,26 +199,19 @@ class Application extends App { ->getConfig(); } ); - - /** - * Services - */ - // Everything we need to do to set up the environment before processing the request $container->registerService( - 'Environment', function (IAppContainer $c) { - return new EnvironmentService( + 'Environment', function (IContainer $c) { + return new Environment( $c->query('AppName'), $c->query('UserId'), $c->query('UserFolder'), $c->query('UserManager'), $c->getServer(), - $c->query('Hasher'), - $c->query('Session'), $c->query('SmarterLogger') ); } ); - /*// The same thing as above, but in OC8, hopefully. See https://github.com/owncloud/core/issues/12676 + /*// The same thing as above, but in OC9, hopefully. See https://github.com/owncloud/core/issues/12676 $container->registerService( 'Environment', function (IAppContainer $c) { $token = $c->query('Token'); @@ -220,14 +221,17 @@ class Application extends App { ->getEnvironment($token); } );*/ + + /** + * Services + */ $container->registerService( 'InfoService', function (IContainer $c) { return new InfoService( $c->query('AppName'), - $c->query('UserFolder'), - $c->query('Environment'), - $c->query('SmarterLogger'), - $c->query('PreviewManager') + $c->query('PreviewService'), + $c->query('SmarterLogger') + ); } ); @@ -235,10 +239,10 @@ class Application extends App { 'ThumbnailService', function (IAppContainer $c) { return new ThumbnailService( $c->query('AppName'), - $c->query('SmarterLogger'), - $c->getServer() - ->createEventSource(), - $c->query('PreviewService') + $c->query('Environment'), + $c->query('CustomPreviewManager'), + $c->query('SmarterLogger') + ); } ); @@ -247,8 +251,18 @@ class Application extends App { return new PreviewService( $c->query('AppName'), $c->query('Environment'), - $c->query('SmarterLogger'), - $c->query('CustomPreviewManager') + $c->query('CustomPreviewManager'), + $c->query('SmarterLogger') + + ); + } + ); + $container->registerService( + 'DownloadService', function (IContainer $c) { + return new DownloadService( + $c->query('AppName'), + $c->query('Environment'), + $c->query('SmarterLogger') ); } ); @@ -270,11 +284,13 @@ class Application extends App { } ); $container->registerService( - 'TokenCheckMiddleware', + 'EnvCheckMiddleware', function (IContainer $c) { - return new TokenCheckMiddleware( + return new EnvCheckMiddleware( $c->query('AppName'), $c->query('Request'), + $c->query('Hasher'), + $c->query('Session'), $c->query('Environment'), $c->query('ControllerMethodReflector'), $c->query('URLGenerator'), @@ -285,7 +301,8 @@ class Application extends App { // executed in the order that it is registered $container->registerMiddleware('SharingCheckMiddleware'); - $container->registerMiddleware('TokenCheckMiddleware'); + $container->registerMiddleware('EnvCheckMiddleware'); + } }
\ No newline at end of file diff --git a/controller/jsonhttperror.php b/controller/jsonhttperror.php index 23a88938..6f7fe2b9 100644 --- a/controller/jsonhttperror.php +++ b/controller/jsonhttperror.php @@ -14,8 +14,14 @@ namespace OCA\GalleryPlus\Controller; +use Exception; + +use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCA\GalleryPlus\Environment\NotFoundEnvException; +use OCA\GalleryPlus\Service\NotFoundServiceException; + /** * Our classes extend both Controller and ApiController, so we need to use * traits to add some common methods @@ -27,14 +33,14 @@ trait JsonHttpError { /** * @param \Exception $exception the message that is returned taken from the exception * - * @param int $code the http error code - * * @return JSONResponse */ - public function error(\Exception $exception, $code = 0) { + public function error(Exception $exception) { $message = $exception->getMessage(); - if ($code === 0) { - $code = $exception->getCode(); + $code = Http::STATUS_INTERNAL_SERVER_ERROR; + + if ($exception instanceof NotFoundServiceException || $exception instanceof NotFoundEnvException) { + $code = Http::STATUS_NOT_FOUND; } return new JSONResponse( diff --git a/controller/pagecontroller.php b/controller/pagecontroller.php index 6de271c8..e0d04445 100644 --- a/controller/pagecontroller.php +++ b/controller/pagecontroller.php @@ -20,7 +20,7 @@ use OCP\IRequest; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; -use OCA\GalleryPlus\Service\EnvironmentService; +use OCA\GalleryPlus\Environment\Environment; /** * Generates templates for the landing page from within ownCloud, the public @@ -31,9 +31,9 @@ use OCA\GalleryPlus\Service\EnvironmentService; class PageController extends Controller { /** - * @type EnvironmentService + * @type Environment */ - private $environmentService; + private $environment; /** * @type IURLGenerator */ @@ -44,18 +44,18 @@ class PageController extends Controller { * * @param string $appName * @param IRequest $request - * @param EnvironmentService $environmentService + * @param Environment $environment * @param IURLGenerator $urlGenerator */ public function __construct( $appName, IRequest $request, - EnvironmentService $environmentService, + Environment $environment, IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); - $this->environmentService = $environmentService; + $this->environment = $environment; $this->urlGenerator = $urlGenerator; } @@ -94,10 +94,8 @@ class PageController extends Controller { public function publicIndex() { $appName = $this->appName; $token = $this->request->getParam('token'); - - $env = $this->environmentService->getEnv(); - $displayName = $env['originalOwnerDisplayName']; - $albumName = $env['albumName']; + $displayName = $this->environment->getDisplayName(); + $albumName = $this->environment->getSharedFolderName(); // Parameters sent to the template $params = [ diff --git a/controller/servicecontroller.php b/controller/servicecontroller.php index e0b00be2..24959c58 100644 --- a/controller/servicecontroller.php +++ b/controller/servicecontroller.php @@ -14,16 +14,21 @@ namespace OCA\GalleryPlus\Controller; +use OCP\IEventSource; use OCP\IURLGenerator; use OCP\IRequest; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCA\GalleryPlus\Environment\Environment; +use OCA\GalleryPlus\Environment\EnvironmentException; +use OCA\GalleryPlus\Http\ImageResponse; +use OCA\GalleryPlus\Service\ServiceException; use OCA\GalleryPlus\Service\InfoService; use OCA\GalleryPlus\Service\ThumbnailService; use OCA\GalleryPlus\Service\PreviewService; -use OCA\GalleryPlus\Service\ServiceException; +use OCA\GalleryPlus\Service\DownloadService; /** * Class ServiceController @@ -35,6 +40,10 @@ class ServiceController extends Controller { use JsonHttpError; /** + * @type Environment + */ + private $environment; + /** * @type InfoService */ private $infoService; @@ -47,34 +56,51 @@ class ServiceController extends Controller { */ private $previewService; /** + * @type DownloadService + */ + private $downloadService; + /** * @type IURLGenerator */ private $urlGenerator; + /** + * @type IEventSource + */ + private $eventSource; /** * Constructor * * @param string $appName * @param IRequest $request + * @param Environment $environment * @param InfoService $infoService * @param ThumbnailService $thumbnailService * @param PreviewService $previewService + * @param DownloadService $downloadService * @param IURLGenerator $urlGenerator + * @param IEventSource $eventSource */ public function __construct( $appName, IRequest $request, + Environment $environment, InfoService $infoService, ThumbnailService $thumbnailService, PreviewService $previewService, - IURLGenerator $urlGenerator + DownloadService $downloadService, + IURLGenerator $urlGenerator, + IEventSource $eventSource ) { parent::__construct($appName, $request); + $this->environment = $environment; $this->infoService = $infoService; $this->thumbnailService = $thumbnailService; $this->previewService = $previewService; + $this->downloadService = $downloadService; $this->urlGenerator = $urlGenerator; + $this->eventSource = $eventSource; } /** @@ -86,13 +112,15 @@ class ServiceController extends Controller { * * @param string $albumpath * - * @return array|Http\JSONResponse + * @return array<string,int>|Http\JSONResponse */ public function getAlbumInfo($albumpath) { try { + $nodeInfo = $this->environment->getNodeInfo($albumpath); + // Thanks to the AppFramework, Arrays are automatically JSON encoded - return $this->infoService->getAlbumInfo($albumpath); - } catch (ServiceException $exception) { + return $nodeInfo; + } catch (EnvironmentException $exception) { return $this->error($exception); } } @@ -111,14 +139,27 @@ class ServiceController extends Controller { /** * @NoAdminRequired * - * Returns a list of all images available to the logged-in user + * Returns a list of all images available to the authenticated user + * + * Authentication can be via a login/password or a token/(password) + * + * For private galleries, it returns all images, with the full path from the root folder + * For public galleries, the path starts from the folder the link gives access to * * @return array|Http\JSONResponse */ public function getImages() { try { - return $this->infoService->getImages(); - } catch (ServiceException $exception) { + $imagesFolder = $this->environment->getResourceFromPath(''); + $fromRootToFolder = $this->environment->getFromRootToFolder(); + + $folderData = [ + 'imagesFolder' => $imagesFolder, + 'fromRootToFolder' => $fromRootToFolder, + ]; + + return $this->infoService->getImages($folderData); + } catch (EnvironmentException $exception) { return $this->error($exception); } } @@ -130,34 +171,56 @@ class ServiceController extends Controller { * * Uses EventSource to send thumbnails back as soon as they're created * + * FIXME: @LukasReschke says: The exit is required here because + * otherwise the AppFramework is trying to add headers as well after + * dispatching the request which results in a "Cannot modify header + * information" notice. + * + * WARNING: Returning a JSON response does not work. + * * @param string $images * @param bool $square * @param bool $scale * - * @return Http\JSONResponse|null + * @return null|Http\JSONResponse */ public function getThumbnails($images, $square, $scale) { - try { - $this->thumbnailService->getAlbumThumbnails($images, $square, $scale); - } catch (ServiceException $exception) { - return $this->error($exception); + $imagesArray = explode(';', $images); + + foreach ($imagesArray as $image) { + $thumbnail = $this->getThumbnail($image, $square, $scale); + $this->eventSource->send('preview', $thumbnail); } + $this->eventSource->close(); + exit(); } /** * @NoAdminRequired * - * Shows a large preview of a file + * Sends either a large preview of the requested file or the + * original file itself + * + * If the browser can use the file as-is then we simply let + * the browser download the file, straight from the filesystem * * @param string $file * @param int $x * @param int $y * - * @return \OCA\GalleryPlus\Http\ImageResponse|Http\JSONResponse + * @return ImageResponse|Http\JSONResponse */ public function showPreview($file, $x, $y) { try { - return $this->previewService->showPreview($file, $x, $y); + $animatedPreview = true; + $previewRequired = $this->previewService->isPreviewRequired($file, $animatedPreview); + if ($previewRequired) { + $preview = $this->previewService->createPreview($file, $x, $y); + } else { + $preview = $this->downloadService->downloadFile($file); + } + + return new ImageResponse($preview, $preview['status']); } catch (ServiceException $exception) { return $this->error($exception); } @@ -174,7 +237,36 @@ class ServiceController extends Controller { */ public function downloadPreview($file) { try { - return $this->previewService->downloadPreview($file); + $download = $this->downloadService->downloadFile($file); + + return new ImageResponse($download, $download['status']); + } catch (EnvironmentException $exception) { + return $this->error($exception); + } + } + + /** + * Retrieves the thumbnail to send back to the browser + * + * The thumbnail is either a resized preview of the file or the original file + * + * @param string $image + * @param bool $square + * @param bool $scale + * + * @return array + */ + private function getThumbnail($image, $square, $scale) { + try { + $previewRequired = + $this->previewService->isPreviewRequired($image, $animatedPreview = false); + if ($previewRequired) { + $thumbnail = $this->thumbnailService->createThumbnail($image, $square, $scale); + } else { + $thumbnail = $this->downloadService->downloadFile($image, $base64Encode = true); + } + + return $thumbnail; } catch (ServiceException $exception) { return $this->error($exception); } diff --git a/environment/environment.php b/environment/environment.php new file mode 100644 index 00000000..480b9076 --- /dev/null +++ b/environment/environment.php @@ -0,0 +1,334 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * @author Authors of \OCA\Files_Sharing\Helper + * + * @copyright Olivier Paroz 2015 + * @copyright Authors of \OCA\Files_Sharing\Helper 2014-2015 + */ + +namespace OCA\GalleryPlus\Environment; + +use OCP\IServerContainer; +use OCP\IUserManager; +use OCP\Share; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; + +use OCA\GalleryPlus\Utility\SmarterLogger; + +/** + * Builds the environment so that the services have access to the files and folders' owner + * + * @todo remove the serverContainer once OCP\IUserManager has a getUserFolder() method + * + * @package OCA\GalleryPlus\Environment + */ +class Environment { + + /** + * @type string + */ + private $appName; + /** + * The userId of the logged-in user or the person sharing a folder publicly + * + * @type string + */ + private $userId; + /** + * The userFolder of the logged-in user or the ORIGINAL owner of the files which are shared + * publicly + * + * A share needs to be tracked back to its original owner in order to be able to access the + * resource + * + * @type Folder|null + */ + private $userFolder; + /** + * @type IUserManager + */ + private $userManager; + /** + * @type IServerContainer + */ + private $serverContainer; + /** + * @type SmarterLogger + */ + private $logger; + /** + * The path to the userFolder for users with accounts: /userId/files + * + * For public folders, it's the path from the shared folder to the root folder in the original + * owner's filesystem: /userId/files/parent_folder/shared_folder + * + * @type string + */ + private $fromRootToFolder; + /** + * The name of the shared folder + * + * @type string + */ + private $folderName; + + /*** + * Constructor + * + * @param string $appName + * @param string|null $userId + * @param Folder|null $userFolder + * @param IUserManager $userManager + * @param IServerContainer $serverContainer + * @param SmarterLogger $logger + */ + public function __construct( + $appName, + $userId, + $userFolder, + IUserManager $userManager, + IServerContainer $serverContainer, + SmarterLogger $logger + ) { + $this->appName = $appName; + $this->userId = $userId; + $this->userFolder = $userFolder; + $this->userManager = $userManager; + $this->serverContainer = $serverContainer; + $this->logger = $logger; + } + + /** + * Creates the environment based on the linkItem the token links to + * + * @param array $linkItem + */ + public function setTokenBasedEnv($linkItem) { + // Resolves reshares down to the last real share + $rootLinkItem = Share::resolveReShare($linkItem); + $origShareOwner = $rootLinkItem['uid_owner']; + $this->userFolder = $this->setupFilesystem($origShareOwner); + + $fileSource = $linkItem['file_source']; + $this->fromRootToFolder = $this->buildFromRootToFolder($fileSource); + + $this->folderName = $linkItem['file_target']; + $this->userId = $linkItem['uid_owner']; + } + + /** + * Creates the environment for a logged-in user + * + * userId and userFolder are already known, we define fromRootToFolder + * so that the services can use one method to have access resources + * without having to know whether they're private or public + */ + public function setStandardEnv() { + $this->fromRootToFolder = $this->userFolder->getPath() . '/'; + } + + /** + * Returns the resource located at the given path + * + * The path starts from the user's files folder + * The resource is either a File or a Folder + * + * @param string $subPath + * + * @return Node + */ + public function getResourceFromPath($subPath) { + $path = $this->getImagePathFromFolder($subPath); + $nodeInfo = $this->getNodeInfo($path); + + return $this->getResourceFromId($nodeInfo['fileid']); + } + + /** + * Returns the Node based on the current user's files folder and a given + * path + * + * @param string $path + * + * @return array<string,int>|false + * + * @throws EnvironmentException + */ + public function getNodeInfo($path) { + $nodeInfo = false; + $folder = $this->userFolder; + if ($folder === null) { + $this->logAndThrowNotFound("Could not access the user's folder"); + } else { + try { + $node = $folder->get($path); + $nodeInfo = [ + 'fileid' => $node->getId(), + 'permissions' => $node->getPermissions() + ]; + } catch (NotFoundException $exception) { + $message = 'Could not find anything at: ' . $exception->getMessage(); + $this->logAndThrowNotFound($message); + } + } + + return $nodeInfo; + } + + + /** + * Returns the userId of the currently logged-in user or the sharer + * + * @return string + */ + public function getUserID() { + return $this->userId; + } + + /** + * Returns the name of the user sharing files publicly + * + * @return string + */ + public function getDisplayName() { + $user = null; + $userId = $this->userId; + + if (isset($userId)) { + $user = $this->userManager->get($userId); + } + if ($user === null) { + $this->logAndThrowNotFound('Could not find user'); + } + + return $user->getDisplayName(); + } + + /** + * Returns the name of shared folder + * + * @return string + */ + public function getSharedFolderName() { + return trim($this->folderName, '//'); + } + + /** + * Returns /parent_folder/current_folder/_my_file + * + * getPath() on the file produces a path like: + * '/userId/files/my_folder/my_sub_folder' + * + * So we substract the path to the folder, giving us a relative path + * '/my_folder/my_sub_folder' + * + * @param string $image + * + * @return string + */ + public function getImagePathFromFolder($image) { + $origSharePath = $this->fromRootToFolder; + $folderPath = $this->userFolder->getPath(); + $origShareRelPath = str_replace($folderPath, '', $origSharePath); + $relativePath = $origShareRelPath; + + /*$this->logger->debug( + 'Full Path {origSharePath}, folder path {folderPath}, relative path {relativePath}', + [ + 'origSharePath' => $origSharePath, + 'folderPath' => $folderPath, + 'relativePath' => $relativePath + ] + );*/ + + return $relativePath . '/' . $image; + } + + /** + * Returns fromRootToFolder + * + * @see buildFromRootToFolder + * + * @return string + */ + public function getFromRootToFolder() { + return $this->fromRootToFolder; + } + + /** + * Sets up the filesystem for the original share owner so that we can + * retrieve the files and returns the userFolder for that user + * + * We can't use 'UserFolder' from Application as the user is not known + * at instantiation time + * + * @param $origShareOwner + * + * @return Folder + */ + private function setupFilesystem($origShareOwner) { + \OC_Util::tearDownFS(); // FIXME: Private API + \OC_Util::setupFS($origShareOwner); // FIXME: Private API + + $folder = $this->serverContainer->getUserFolder($origShareOwner); + /*// Alternative which does not exist yet + $user = $this->userManager->get($origShareOwner); + $folder = $user->getUserFolder();*/ + + return $folder; + } + + /** + * Returns the path from the shared folder to the root folder in the original + * owner's filesystem: /userId/files/parent_folder/shared_folder + * + * @param string $fileSource + * + * @return string + */ + private function buildFromRootToFolder($fileSource) { + $resource = $this->getResourceFromId($fileSource); + $fromRootToFolder = $resource->getPath() . '/'; + + return $fromRootToFolder; + } + + /** + * Returns the resource identified by the given ID + * + * @param int $resourceId + * + * @return Node + * + * @throws EnvironmentException + */ + private function getResourceFromId($resourceId) { + $resourcesArray = $this->userFolder->getById($resourceId); + if ($resourcesArray[0] === null) { + $this->logAndThrowNotFound('Could not resolve linkItem'); + } + + return $resourcesArray[0]; + } + + /** + * Logs the error and raises an exception + * + * @param string $message + * + * @throws NotFoundEnvException + */ + private function logAndThrowNotFound($message) { + $this->logger->error($message . ' (404)'); + throw new NotFoundEnvException($message); + } + +}
\ No newline at end of file diff --git a/environment/environmentexception.php b/environment/environmentexception.php new file mode 100644 index 00000000..9481797f --- /dev/null +++ b/environment/environmentexception.php @@ -0,0 +1,28 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * + * @copyright Olivier Paroz 2015 + */ + +namespace OCA\GalleryPlus\Environment; + +use Exception; + +/** + * Thrown when the service cannot reply to a request + */ +class EnvironmentException extends Exception { + + /** + * Constructor + * + * @param string $msg the message contained in the exception + */ + public function __construct($msg) {} +}
\ No newline at end of file diff --git a/environment/notfoundenvexception.php b/environment/notfoundenvexception.php new file mode 100644 index 00000000..9589fa9a --- /dev/null +++ b/environment/notfoundenvexception.php @@ -0,0 +1,26 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * + * @copyright Olivier Paroz 2015 + */ + +namespace OCA\GalleryPlus\Environment; + +/** + * Thrown when the service cannot reply to a request + */ +class NotFoundEnvException extends EnvironmentException { + + /** + * Constructor + * + * @param string $msg the message contained in the exception + */ + public function __construct($msg) {} +}
\ No newline at end of file diff --git a/middleware/checkmiddleware.php b/middleware/checkmiddleware.php index 81e2a30c..e13b14e6 100644 --- a/middleware/checkmiddleware.php +++ b/middleware/checkmiddleware.php @@ -90,6 +90,19 @@ abstract class CheckMiddleware extends Middleware { } /** + * Logs the error and raises an exception + * + * @param string $message + * @param int $code + * + * @throws CheckException + */ + protected function logAndThrow($message, $code) { + $this->logger->error($message . ' (' . $code . ')'); + throw new CheckException($message, $code); + } + + /** * Decides which type of response to send * * @param string $message diff --git a/middleware/envcheckmiddleware.php b/middleware/envcheckmiddleware.php new file mode 100644 index 00000000..1e4de129 --- /dev/null +++ b/middleware/envcheckmiddleware.php @@ -0,0 +1,316 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Authors of \OCA\Files_Sharing\Helper + * + * @copyright Olivier Paroz 2014-2015 + * @copyright Bernhard Posselt 2012-2015 + * @copyright Authors of \OCA\Files_Sharing\Helper 2014-2015 + */ + +namespace OCA\GalleryPlus\Middleware; + +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\ISession; +use OCP\Share; +use OCP\Security\IHasher; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Utility\IControllerMethodReflector; + +use OCA\GalleryPlus\Environment\Environment; +use OCA\GalleryPlus\Service\ServiceException; +use OCA\GalleryPlus\Utility\SmarterLogger; + +/** + * Checks that we have a valid token linked to a valid resource and that the + * user is authorised to access it + * + * Once all checks have been passed, the environment is ready to use + * + * @package OCA\GalleryPlus\Middleware + */ +class EnvCheckMiddleware extends CheckMiddleware { + + /** + * @type IHasher + * */ + private $hasher; + /** + * @type ISession + * */ + private $session; + /** + * @type Environment + */ + private $environment; + /** + * @type IControllerMethodReflector + */ + protected $reflector; + + /*** + * Constructor + * + * @param string $appName + * @param IHasher $hasher + * @param ISession $session + * @param IRequest $request + * @param IControllerMethodReflector $reflector + * @param IURLGenerator $urlGenerator + * @param SmarterLogger $logger + * @param Environment $environment + * @param SmarterLogger $logger + */ + public function __construct( + $appName, + IRequest $request, + IHasher $hasher, + ISession $session, + Environment $environment, + IControllerMethodReflector $reflector, + IURLGenerator $urlGenerator, + SmarterLogger $logger + ) { + parent::__construct( + $appName, + $request, + $urlGenerator, + $logger + ); + + $this->hasher = $hasher; + $this->session = $session; + $this->environment = $environment; + $this->reflector = $reflector; + } + + /** + * Checks that we have a valid token linked to a valid resource and that the + * user is authorised to access it + * + * Inspects the controller method annotations and if PublicPage is found + * it checks that we have a token and an optional password giving access to a valid resource. + * Once that's done, the environment is setup so that our services can find the resources they + * need. + * + * The checks are not performed on "guest" pages and the environment is not setup. Typical + * guest pages are anonymous error ages + * + * @inheritDoc + */ + public function beforeController($controller, $methodName) { + if ($this->reflector->hasAnnotation('Guest')) { + return; + } + $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); + if ($isPublicPage) { + $this->validateAndSetTokenBasedEnv(); + } else { + $this->environment->setStandardEnv(); + } + } + + /** + * Checks that we have a token and an optional password giving access to a + * valid resource. Sets the token based environment after that + */ + private function validateAndSetTokenBasedEnv() { + $token = $this->request->getParam('token'); + if (!$token) { + $this->noTokenFound(); + } else { // We have a token + // Let's see if it's linked to a valid resource + $linkItem = $this->getLinkItem($token); + $password = $this->request->getParam('password'); + // Let's see if the user needs to provide a password + $this->checkAuthorisation($linkItem, $password); + + $this->environment->setTokenBasedEnv($linkItem); + } + } + + /** + * Throws an exception because no token was provided + * + * @throws CheckException + */ + private function noTokenFound() { + $this->logAndThrow( + "Can't access a public resource without a token", Http::STATUS_NOT_FOUND + ); + } + + /** + * Validates a token to make sure its linked to a valid resource + * + * Logic mostly duplicated from @see \OCA\Files_Sharing\Helper + * + * @fixme setIncognitoMode in 8.1 https://github.com/owncloud/core/pull/12912 + * + * @param string $token + * + * @return array|bool + * + * @throws CheckException + */ + private function getLinkItem($token) { + // Allows a logged in user to access public links + \OC_User::setIncognitoMode(true); + + $linkItem = Share::getShareByToken($token, false); + + $this->checkLinkItemExists($linkItem); + $this->checkLinkItemIsValid($linkItem, $token); + $this->checkItemType($linkItem); + + // Checks passed, let's store the linkItem + return $linkItem; + } + + /** + * Makes sure that the token exists + * + * @param array|bool $linkItem + */ + private function checkLinkItemExists($linkItem) { + if ($linkItem === false + || ($linkItem['item_type'] !== 'file' + && $linkItem['item_type'] !== 'folder') + ) { + $message = 'Passed token parameter is not valid'; + $this->logAndThrow($message, Http::STATUS_BAD_REQUEST); + } + } + + /** + * Makes sure that the token contains all the information that we need + * + * @param array|bool $linkItem + * @param string $token + */ + private function checkLinkItemIsValid($linkItem, $token) { + if (!isset($linkItem['uid_owner']) + || !isset($linkItem['file_source']) + ) { + $message = + 'Passed token seems to be valid, but it does not contain all necessary information . ("' + . $token . '")'; + $this->logAndThrow($message, Http::STATUS_NOT_FOUND); + } + } + + /** + * Makes sure an item type was set for that token + * + * @param array|bool $linkItem + */ + private function checkItemType($linkItem) { + if (!isset($linkItem['item_type'])) { + $message = 'No item type set for share id: ' . $linkItem['id']; + $this->logAndThrow($message, Http::STATUS_NOT_FOUND); + } + } + + /** + * Checks if a password is required or if the one supplied is working + * + * @param array|bool $linkItem + * @param string|null $password optional password + * + * @throws CheckException + */ + private function checkAuthorisation($linkItem, $password) { + $passwordRequired = isset($linkItem['share_with']); + + if ($passwordRequired) { + if ($password !== null) { + $this->authenticate($linkItem, $password); + } else { + $this->checkSession($linkItem); + } + } + } + + /** + * Authenticate link item with the given password + * or with the session if no password was given. + * + * @fixme @LukasReschke says: Migrate old hashes to new hash format + * Due to the fact that there is no reasonable functionality to update the password + * of an existing share no migration is yet performed there. + * The only possibility is to update the existing share which will result in a new + * share ID and is a major hack. + * + * In the future the migration should be performed once there is a proper method + * to update the share's password. (for example `$share->updatePassword($password)` + * + * @link https://github.com/owncloud/core/issues/10671 + * + * @param array|bool $linkItem + * @param string $password + * + * @return bool true if authorized, an exception is raised otherwise + * + * @throws ServiceException + */ + private function authenticate($linkItem, $password) { + if ($linkItem['share_type'] == Share::SHARE_TYPE_LINK) { + $this->checkPassword($linkItem, $password); + } else { + $this->logAndThrow( + 'Unknown share type ' . $linkItem['share_type'] . ' for share id ' + . $linkItem['id'], Http::STATUS_NOT_FOUND + ); + } + + return true; + } + + /** + * Validates the given password + * + * @param array|bool $linkItem + * @param string $password + * + * @throws ServiceException + */ + private function checkPassword($linkItem, $password) { + $newHash = ''; + if ($this->hasher->verify($password, $linkItem['share_with'], $newHash)) { + + // Save item id in session for future requests + $this->session->set('public_link_authenticated', $linkItem['id']); + if (!empty($newHash)) { + // For future use + } + } else { + $this->logAndThrow("Wrong password", Http::STATUS_UNAUTHORIZED); + } + } + + /** + * Makes sure the user is already properly authenticated when a password is required and none + * was provided + * + * @param array|bool $linkItem + * + * @throws ServiceException + */ + private function checkSession($linkItem) { + // Not authenticated ? + if (!$this->session->exists('public_link_authenticated') + || $this->session->get('public_link_authenticated') !== $linkItem['id'] + ) { + $this->logAndThrow("Missing password", Http::STATUS_UNAUTHORIZED); + } + } + +}
\ No newline at end of file diff --git a/middleware/sharingcheckmiddleware.php b/middleware/sharingcheckmiddleware.php index d147d427..b623c479 100644 --- a/middleware/sharingcheckmiddleware.php +++ b/middleware/sharingcheckmiddleware.php @@ -82,17 +82,11 @@ class SharingCheckMiddleware extends CheckMiddleware { public function beforeController($controller, $methodName) { $sharingEnabled = $this->isSharingEnabled(); - // This needs to be done here as the Dispatcher does not call our reflector - //$this->reflector->reflect($controller, $methodName); - $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); $isGuest = $this->reflector->hasAnnotation('Guest'); if ($isPublicPage && !$isGuest && !$sharingEnabled) { - throw new CheckException( - 'Sharing is disabled', - Http::STATUS_SERVICE_UNAVAILABLE - ); + $this->logAndThrow("'Sharing is disabled'", Http::STATUS_SERVICE_UNAVAILABLE); } } diff --git a/middleware/tokencheckmiddleware.php b/middleware/tokencheckmiddleware.php deleted file mode 100644 index 5a025b88..00000000 --- a/middleware/tokencheckmiddleware.php +++ /dev/null @@ -1,172 +0,0 @@ -<?php -/** - * ownCloud - galleryplus - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Olivier Paroz <owncloud@interfasys.ch> - * @author Bernhard Posselt <dev@bernhard-posselt.com> - * - * @copyright Olivier Paroz 2014-2015 - * @copyright Bernhard Posselt 2012-2015 - */ - -namespace OCA\GalleryPlus\Middleware; - -use OCP\IRequest; -use OCP\IURLGenerator; - -use OCP\AppFramework\Http; -use OCP\AppFramework\Utility\IControllerMethodReflector; - -use OCA\GalleryPlus\Service\EnvironmentService; -use OCA\GalleryPlus\Service\ServiceException; -use OCA\GalleryPlus\Utility\SmarterLogger; - - -/** - * Checks that we have a valid token linked to a valid resource and that the - * user is authorised to access it - * - * @package OCA\GalleryPlus\Middleware - */ -class TokenCheckMiddleware extends CheckMiddleware { - - /** - * @type EnvironmentService - */ - private $environmentService; - /** - * @type IControllerMethodReflector - */ - protected $reflector; - - /*** - * Constructor - * - * @param string $appName - * @param IRequest $request - * @param EnvironmentService $environmentService - * @param IControllerMethodReflector $reflector - * @param IURLGenerator $urlGenerator - * @param SmarterLogger $logger - */ - public function __construct( - $appName, - IRequest $request, - EnvironmentService $environmentService, - IControllerMethodReflector $reflector, - IURLGenerator $urlGenerator, - SmarterLogger $logger - ) { - parent::__construct( - $appName, - $request, - $urlGenerator, - $logger - ); - - $this->environmentService = $environmentService; - $this->reflector = $reflector; - } - - /** - * Checks that we have a valid token linked to a valid resource and that the - * user is authorised to access it - * - * Inspects the controller method annotations and if PublicPage is found - * it checks that we have token and an optional password giving access to a - * valid resource - * - * The check is not performed on "guest" pages which don't require a token - * - * @inheritDoc - */ - public function beforeController($controller, $methodName) { - $token = $this->request->getParam('token'); - $password = $this->request->getParam('password'); - /*// This needs to be done here as the Dispatcher does not call our reflector - $this->reflector->reflect($controller, $methodName);*/ - $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); - $isGuest = $this->reflector->hasAnnotation('Guest'); - - if ($isPublicPage && !$isGuest) { - if (!$token) { - $this->noTokenFound(); - } else { // We have a token - // Let's see if it's linked to a valid resource - $this->checkToken($token); - // Let's see if the user needs to provide a password - $this->checkAuthorisation($password); - // Let's see if we can set up the environment for the controller - $this->setupTokenBasedEnv(); - } - } - } - - /** - * Throws an exception because no token was provided - * - * @throws CheckException - */ - private function noTokenFound() { - throw new CheckException( - "Can't access a public resource without a token", - Http::STATUS_NOT_FOUND - ); - } - - /** - * Makes sure we have a valid token, linked to a valid resource - * - * @param string $token - * - * @throws CheckException - */ - private function checkToken($token) { - try { - $this->environmentService->checkToken($token); - } catch (ServiceException $exception) { - throw new CheckException( - $exception->getMessage(), - $exception->getCode() - ); - } - } - - /** - * Checks if a password is required or if the one supplied is working - * - * @param $password - * - * @throws CheckException - */ - private function checkAuthorisation($password) { - try { - $this->environmentService->checkAuthorisation($password); - } catch (ServiceException $exception) { - throw new CheckException( - $exception->getMessage(), - $exception->getCode() - ); - } - } - - /** - * Sets up the environment based on the received token - * - * @throws CheckException - */ - private function setupTokenBasedEnv() { - try { - $this->environmentService->setupTokenBasedEnv(); - } catch (ServiceException $exception) { - throw new CheckException( - $exception->getMessage(), - $exception->getCode() - ); - } - } - -}
\ No newline at end of file diff --git a/preview/preview.php b/preview/preview.php index 19fb83ed..feff4a45 100644 --- a/preview/preview.php +++ b/preview/preview.php @@ -15,16 +15,15 @@ namespace OCA\GalleryPlus\Preview; use OCP\IConfig; use OCP\Image; use OCP\Files\File; +use OCP\IPreview; use OCP\Template; -use OCP\AppFramework\Http; - use OCA\GalleryPlus\Utility\SmarterLogger; /** * Generates previews * - * @todo On OC8.1, replace \OC\Preview with OC::$server->getPreviewManager() + * @todo On OC8.1, replace \OC\Preview with IPreview * * @package OCA\GalleryPlus\Preview */ @@ -35,13 +34,17 @@ class Preview { */ private $dataDir; /** + * @type mixed + */ + private $previewManager; + /** * @type SmarterLogger */ private $logger; /** * @type string */ - private $owner; + private $userId; /** * @type \OC\Preview */ @@ -54,131 +57,60 @@ class Preview { * @type int[] */ private $dims; - + /** + * @type bool + */ + private $success = true; /** * Constructor * * @param IConfig $config + * @param IPreview $previewManager * @param SmarterLogger $logger */ public function __construct( IConfig $config, + IPreview $previewManager, SmarterLogger $logger ) { $this->dataDir = $config->getSystemValue('datadirectory'); + $this->previewManager = $previewManager; $this->logger = $logger; } /** - * Initialises the object - * - * @fixme Private API, but can't use the PreviewManager yet as it's incomplete - * - * @param string $owner - * @param File $file - * @param string $imagePathFromFolder - */ - public function setupView($owner, $file, $imagePathFromFolder) { - $this->owner = $owner; - $this->file = $file; - $this->preview = new \OC\Preview($owner, 'files', $imagePathFromFolder); - } - - /** - * Decides if we should download the file instead of generating a preview - * - * @param bool $animatedPreview - * @param bool $download - * - * @return bool - */ - public function previewRequired($animatedPreview, $download) { - $mime = $this->file->getMimeType(); - - if ($mime === 'image/svg+xml') { - return $this->isSvgPreviewRequired(); - } - if ($mime === 'image/gif') { - return $this->isGifPreviewRequired($animatedPreview); - } - - return !$download; - } - - /** - * Decides if we should download the SVG or generate a preview - * - * SVGs are downloaded if the SVG converter is disabled - * Files of any media type are downloaded if requested by the client - * - * @return bool - */ - private function isSvgPreviewRequired() { - if (!$this->preview->isMimeSupported('image/svg+xml')) { - return false; - } - - return true; - } - - /** - * Decides if we should download the GIF or generate a preview - * - * GIFs are downloaded if they're animated and we want to show - * animations + * Returns true if the passed mime type is supported * - * @param bool $animatedPreview + * @param string $mimeType * - * @return bool + * @return boolean */ - private function isGifPreviewRequired($animatedPreview) { - $animatedGif = $this->isGifAnimated(); - - if ($animatedPreview && $animatedGif) { - return false; - } - - return true; + public function isMimeSupported($mimeType = '*') { + return $this->previewManager->isMimeSupported($mimeType); } /** - * Tests if a GIF is animated + * Initialises the view which will be used to access files and generate previews * - * An animated gif contains multiple "frames", with each frame having a - * header made up of: - * * a static 4-byte sequence (\x00\x21\xF9\x04) - * * 4 variable bytes - * * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21) - * - * We read through the file until we reach the end of the file, or we've - * found at least 2 frame headers - * - * @link http://php.net/manual/en/function.imagecreatefromgif.php#104473 + * @fixme Private API, but can't use the PreviewManager yet as it's incomplete * - * @return bool + * @param string $userId + * @param File $file + * @param string $imagePathFromFolder */ - private function isGifAnimated() { - $fileHandle = $this->file->fopen('rb'); - $count = 0; - while (!feof($fileHandle) && $count < 2) { - $chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time - $count += preg_match_all( - '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches - ); - } - - fclose($fileHandle); - - return $count > 1; + public function setupView($userId, $file, $imagePathFromFolder) { + $this->userId = $userId; + $this->file = $file; + $this->preview = new \OC\Preview($userId, 'files', $imagePathFromFolder); } /** * Returns a preview based on OC's preview class and our custom methods * - * We don't throw an exception when the preview generator fails, - * instead, until the Preview class is fixed, we send the mime - * icon along with a 415 error code. + * We check that the preview returned by the Preview class can be used by + * the browser. If not, we send the mime icon and change the status code so + * that the client knows that the process has failed. * * @fixme setKeepAspect is missing from public interface. * https://github.com/owncloud/core/issues/12772 @@ -196,13 +128,11 @@ class Preview { if ($maxX === 200) { // Only fixing the square thumbnails $previewData = $this->previewValidator(); } - $perfectPreview = ['preview' => $previewData, 'status' => Http::STATUS_OK]; + $perfectPreview = ['preview' => $previewData]; } else { $this->logger->debug("[PreviewService] ERROR! Did not get a preview"); - $perfectPreview = [ - 'preview' => $this->getMimeIcon(), - 'status' => Http::STATUS_UNSUPPORTED_MEDIA_TYPE - ]; + $perfectPreview = ['preview' => $this->getMimeIcon()]; + $this->success = false; } $perfectPreview['mimetype'] = 'image/png'; // Previews are always sent as PNG @@ -210,6 +140,15 @@ class Preview { } /** + * Returns true if the preview was successfully generated + * + * @return bool + */ + public function isPreviewValid() { + return $this->success; + } + + /** * Asks core for a preview based on our criteria * * @todo Need to read scaling setting from settings @@ -217,8 +156,6 @@ class Preview { * @param bool $keepAspect * * @return \OC_Image - * - * @throws \Exception */ private function getPreviewFromCore($keepAspect) { $this->logger->debug("[PreviewService] Generating a new preview"); @@ -228,6 +165,8 @@ class Preview { $this->preview->setScalingUp(false); $this->preview->setKeepAspect($keepAspect); + //$this->logger->debug("[PreviewService] preview {preview}", ['preview' => $this->preview]); + return $this->preview->getPreview(); } @@ -235,9 +174,9 @@ class Preview { * Makes sure we return previews of the asked dimensions and fix the cache * if necessary * - * The Preview class of OC7 sometimes return previews which are either - * wider or smaller than the asked dimensions. This happens when one of the - * original dimension is smaller than what is asked for + * The Preview class sometimes return previews which are either wider or + * smaller than the asked dimensions. This happens when one of the original + * dimension is smaller than what is asked for * * @return resource */ @@ -327,7 +266,7 @@ class Preview { * @return mixed */ private function fixPreviewCache($fixedPreview) { - $owner = $this->owner; + $owner = $this->userId; $file = $this->file; $preview = $this->preview; $fixedPreviewObject = new Image($fixedPreview); @@ -360,7 +299,7 @@ class Preview { $iconData = new Image(); $image = $this->dataDir . '/../' . Template::mimetype_icon($mime); - // Alternative + // Alternative which does not exist yet //$image = $this->serverRoot() . Template::mimetype_icon($mime); $iconData->loadFromFile($image); diff --git a/service/base64encode.php b/service/base64encode.php new file mode 100644 index 00000000..c29f6220 --- /dev/null +++ b/service/base64encode.php @@ -0,0 +1,42 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * + * @copyright Olivier Paroz 2015 + */ + +namespace OCA\GalleryPlus\Service; + +/** + * Base64 encoding utility method + * + * @package OCA\GalleryPlus\Service + */ +trait Base64Encode { + + /** + * Returns base64 encoded data of a preview + * + * Using base64_encode for files which are downloaded + * (cached Thumbnails, SVG, GIFs) and using __toStrings + * for the previews which are instances of \OC_Image + * + * @param \OC_Image|string $previewData + * + * @return \OC_Image|string + */ + protected function encode($previewData) { + if ($previewData instanceof \OC_Image) { + $previewData = (string)$previewData; + } else { + $previewData = base64_encode($previewData); + } + + return $previewData; + } +}
\ No newline at end of file diff --git a/service/downloadservice.php b/service/downloadservice.php new file mode 100644 index 00000000..77cf9219 --- /dev/null +++ b/service/downloadservice.php @@ -0,0 +1,83 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * + * @copyright Olivier Paroz 2015 + */ + +namespace OCA\GalleryPlus\Service; + +use OCP\Files\File; + +use OCA\GalleryPlus\Environment\Environment; +use OCA\GalleryPlus\Environment\NotFoundEnvException; +use OCA\GalleryPlus\Utility\SmarterLogger; + +/** + * Prepares the file to download + * + * @package OCA\GalleryPlus\Service + */ +class DownloadService extends Service { + + use Base64Encode; + + /** + * @type Environment + */ + private $environment; + + /** + * Constructor + * + * @param string $appName + * @param Environment $environment + * @param SmarterLogger $logger + */ + public function __construct( + $appName, + Environment $environment, + SmarterLogger $logger + ) { + parent::__construct($appName, $logger); + + $this->environment = $environment; + } + + /** + * Downloads the requested file + * + * @param string $image + * @param bool $base64Encode + * + * @return array + */ + public function downloadFile($image, $base64Encode = false) { + $file = null; + try { + /** @type File $file */ + $file = $this->environment->getResourceFromPath($image); + } catch (NotFoundEnvException $exception) { + $this->logAndThrowNotFound($exception->getMessage()); + } + $this->logger->debug("[DownloadService] File to Download: {file}"); + $download = [ + 'path' => $image, + 'preview' => $file->getContent(), + 'mimetype' => $file->getMimeType(), + 'status' => Service::STATUS_OK + ]; + + if ($base64Encode) { + $download['preview'] = $this->encode($download['preview']); + } + + return $download; + } + +}
\ No newline at end of file diff --git a/service/environmentservice.php b/service/environmentservice.php deleted file mode 100644 index fe264ae4..00000000 --- a/service/environmentservice.php +++ /dev/null @@ -1,385 +0,0 @@ -<?php -/** - * ownCloud - galleryplus - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Olivier Paroz <owncloud@interfasys.ch> - * @author Authors of \OCA\Files_Sharing\Helper - * - * @copyright Olivier Paroz 2014-2015 - * @copyright Authors of \OCA\Files_Sharing\Helper 2014-2015 - */ - -namespace OCA\GalleryPlus\Service; - -use OCP\Files\Folder; -use OCP\IServerContainer; -use OCP\IUser; -use OCP\ISession; -use OCP\Share; -use OCP\IUserManager; -use OCP\Security\IHasher; - -use OCP\AppFramework\Http; - -use OCA\GalleryPlus\Utility\SmarterLogger; - -/** - * Builds the environment so that the services have access to the proper user, - * folder and files - * - * @package OCA\GalleryPlus\Service - */ -class EnvironmentService extends Service { - - /** - * @type string - */ - private $userId; - /** - * @type Folder|null - */ - private $userFolder; - /** - * @type IUserManager - */ - private $userManager; - /** - * @type IServerContainer - */ - private $serverContainer; - /** - * @type IHasher - * */ - private $hasher; - /** - * @type ISession - * */ - private $session; - /** - * @type array - */ - private $linkItem; - /** - * @type array - */ - private $origShareData = []; - - /** - * @param string $appName - * @param string|null $userId - * @param Folder|null $userFolder - * @param IUserManager $userManager - * @param IServerContainer $serverContainer - * @param IHasher $hasher - * @param ISession $session - * @param SmarterLogger $logger - */ - public function __construct( - $appName, - $userId, - $userFolder, - IUserManager $userManager, - IServerContainer $serverContainer, - IHasher $hasher, - ISession $session, - SmarterLogger $logger - ) { - parent::__construct($appName, $logger); - - $this->userId = $userId; - $this->userFolder = $userFolder; - $this->userManager = $userManager; - $this->serverContainer = $serverContainer; - $this->hasher = $hasher; - $this->session = $session; - } - - /** - * Validates a token to make sure its linked to a valid resource - * - * Logic mostly duplicated from @see \OCA\Files_Sharing\Helper - * @fixme setIncognitoMode in 8.1 https://github.com/owncloud/core/pull/12912 - * - * @param string $token - */ - public function checkToken($token) { - // Allows a logged in user to access public links - \OC_User::setIncognitoMode(true); - - $linkItem = Share::getShareByToken($token, false); - - $this->checkLinkItemExists($linkItem); - $this->checkLinkItemIsValid($linkItem, $token); - $this->checkItemType($linkItem); - - // Checks passed, let's store the linkItem - $this->linkItem = $linkItem; - } - - /** - * Checks if a password is required and validates it if it is provided in - * the request - * - * @param string $password optional password - */ - public function checkAuthorisation($password = null) { - $passwordRequired = isset($this->linkItem['share_with']); - - if ($passwordRequired) { - if ($password !== null) { - $this->authenticate($password); - } else { - $this->checkSession(); - } - } - } - - /** - * Sets up the environment based on a token - * - * The token has already been vetted by checkToken via the token checking - * middleware - */ - public function setupTokenBasedEnv() { - $linkItem = $this->linkItem; - // Resolves reshares down to the last real share - $rootLinkItem = Share::resolveReShare($linkItem); - $origShareOwner = $rootLinkItem['uid_owner']; - $user = $this->getUser($origShareOwner); - $origOwnerDisplayName = $user->getDisplayName(); - // Setup FS for user - \OC_Util::tearDownFS(); // FIXME: Private API - \OC_Util::setupFS($origShareOwner); // FIXME: Private API - $fileSource = $linkItem['file_source']; - $origShareRelPath = $this->getPath($origShareOwner, $fileSource); - - // Checks passed, let's store the data - $this->origShareData = [ - 'origShareOwner' => $origShareOwner, - 'origOwnerDisplayName' => $origOwnerDisplayName, - 'origShareRelPath' => $origShareRelPath - ]; - } - - /** - * Returns an array with details about the environment - * - * @return array various environment variables - */ - public function getEnv() { - $linkItem = $this->linkItem; - - if (isset($linkItem)) { - $env = $this->getTokenBasedEnv($linkItem); - } else { - $env = [ - 'owner' => $this->userId, - 'relativePath' => '/', - 'folder' => $this->userFolder, - ]; - } - - return $env; - } - - /** - * Returns the environment for a token - * - * @param $linkItem - * - * @return array - */ - private function getTokenBasedEnv($linkItem) { - $origShareOwner = $this->origShareData['origShareOwner']; - $origShareRelPath = $this->origShareData['origShareRelPath']; - // Displayed in the top right corner of the gallery - $origOwnerDisplayName = $this->origShareData['origOwnerDisplayName']; - - $shareOwner = $linkItem['uid_owner']; - $folder = $this->serverContainer->getUserFolder($shareOwner); - - $albumName = trim($linkItem['file_target'], '//'); - - return [ - 'owner' => $shareOwner, - 'relativePath' => $origShareRelPath . '/', - 'folder' => $folder, - 'albumName' => $albumName, - 'originalShareOwner' => $origShareOwner, - 'originalOwnerDisplayName' => $origOwnerDisplayName, - ]; - } - - /** - * Makes sure that the token exists - * - * @param bool|array $linkItem - */ - private function checkLinkItemExists($linkItem) { - if ($linkItem === false - || ($linkItem['item_type'] !== 'file' - && $linkItem['item_type'] !== 'folder') - ) { - $message = 'Passed token parameter is not valid'; - $this->kaBoom($message, Http::STATUS_BAD_REQUEST); - } - } - - /** - * Makes sure that the token contains all the information that we need - * - * @param array $linkItem - * @param string $token - */ - private function checkLinkItemIsValid($linkItem, $token) { - if (!isset($linkItem['uid_owner']) - || !isset($linkItem['file_source']) - ) { - $message = - 'Passed token seems to be valid, but it does not contain all necessary information . ("' - . $token . '")'; - $this->kaBoom($message, Http::STATUS_NOT_FOUND); - } - } - - /** - * Makes sure an item type was set for that token - * - * @param array $linkItem - */ - private function checkItemType($linkItem) { - if (!isset($linkItem['item_type'])) { - $message = 'No item type set for share id: ' . $linkItem['id']; - $this->kaBoom($message, Http::STATUS_NOT_FOUND); - } - } - - /** - * Authenticate link item with the given password - * or with the session if no password was given. - * - * @fixme Migrate old hashes to new hash format - * Due to the fact that there is no reasonable functionality to update the password - * of an existing share no migration is yet performed there. - * The only possibility is to update the existing share which will result in a new - * share ID and is a major hack. - * - * In the future the migration should be performed once there is a proper method - * to update the share's password. (for example `$share->updatePassword($password)` - * @link https://github.com/owncloud/core/issues/10671 - * - * @param string $password - * - * @return bool true if authorized, an exception is raised otherwise - * - * @throws ServiceException - */ - private function authenticate($password) { - $linkItem = $this->linkItem; - - if ($linkItem['share_type'] == Share::SHARE_TYPE_LINK) { - $this->checkPassword($password); - } else { - $this->kaBoom( - 'Unknown share type ' . $linkItem['share_type'] . ' for share id ' - . $linkItem['id'], Http::STATUS_NOT_FOUND - ); - } - - return true; - } - - /** - * Validates the given password - * - * @param string $password - * - * @throws ServiceException - */ - private function checkPassword($password) { - $linkItem = $this->linkItem; - $newHash = ''; - if ($this->hasher->verify($password, $linkItem['share_with'], $newHash)) { - - // Save item id in session for future requests - $this->session->set('public_link_authenticated', $linkItem['id']); - if (!empty($newHash)) { - // For future use - } - } else { - $this->kaBoom("Wrong password", Http::STATUS_UNAUTHORIZED); - } - } - - /** - * Makes sure the user is already properly authenticated when a password is required and none - * was provided - * - * @throws ServiceException - */ - private function checkSession() { - // not authenticated ? - if (!$this->session->exists('public_link_authenticated') - || $this->session->get('public_link_authenticated') !== $this->linkItem['id'] - ) { - $this->kaBoom("Missing password", Http::STATUS_UNAUTHORIZED); - } - } - - /** - * Returns an instance of the user - * - * @param string $origShareOwner the user the share belongs to - * - * @return IUser an instance of the user - */ - private function getUser($origShareOwner) { - $user = null; - - if (isset($origShareOwner)) { - $user = $this->userManager->get($origShareOwner); - } - if ($user === null) { - $this->kaBoom('Could not find user', Http::STATUS_NOT_FOUND); - } - - return $user; - } - - /** - * Returns the path the token gives access to - * - * getPath() on the file produces a path like: - * '/owner/files/my_folder/my_sub_folder' - * - * So we substract the path to the folder, giving us a relative path - * '/my_folder/my_sub_folder' - * - * @param string $origShareOwner - * @param int $fileSource - * - * @return string the path, relative to the folder - */ - private function getPath($origShareOwner, $fileSource) { - $folder = $this->serverContainer->getUserFolder($origShareOwner); - $resource = $this->getResourceFromId($folder, $fileSource); - - $origSharePath = $resource->getPath(); - $folderPath = $folder->getPath(); - $origShareRelPath = str_replace($folderPath, '', $origSharePath); - - /*$this->logger->debug( - 'Full Path {origSharePath}, relative path {origShareRelPath}', - [ - 'origSharePath' => $origSharePath, - 'origShareRelPath' => $origShareRelPath - ] - );*/ - - return $origShareRelPath; - } - -}
\ No newline at end of file diff --git a/service/infoservice.php b/service/infoservice.php index 40b044d6..0e8d82ee 100644 --- a/service/infoservice.php +++ b/service/infoservice.php @@ -14,9 +14,6 @@ namespace OCA\GalleryPlus\Service; use OCP\Files\Folder; use OCP\Files\File; -use OCP\IPreview; - -use OCP\AppFramework\Http; use OCA\GalleryPlus\Utility\SmarterLogger; @@ -30,23 +27,15 @@ use OCA\GalleryPlus\Utility\SmarterLogger; class InfoService extends Service { /** - * @type Folder|null - */ - private $userFolder; - /** - * @type EnvironmentService - */ - private $environmentService; - /** * @type mixed */ - private $previewManager; + private $previewService; /** * @todo This hard-coded array could be replaced by admin settings * * @type string[] */ - private static $baseMimeTypes = [ + private $baseMimeTypes = [ 'image/png', 'image/jpeg', 'image/gif', @@ -64,7 +53,7 @@ class InfoService extends Service { * * @type string[] */ - private static $slideshowMimeTypes = [ + private $slideshowMimeTypes = [ 'application/font-sfnt', 'application/x-font', ]; @@ -73,47 +62,18 @@ class InfoService extends Service { * Constructor * * @param string $appName - * @param Folder|null $userFolder - * @param EnvironmentService $environmentService * @param SmarterLogger $logger - * @param IPreview $previewManager + * @param PreviewService $previewManager */ public function __construct( $appName, - $userFolder, - EnvironmentService $environmentService, - SmarterLogger $logger, - IPreview $previewManager + PreviewService $previewManager, + SmarterLogger $logger + ) { parent::__construct($appName, $logger); - $this->userFolder = $userFolder; - $this->environmentService = $environmentService; - $this->previewManager = $previewManager; - } - - /** - * Returns information about an album, based on its path - * - * Used to see if we have access to the folder or not - * - * @param string $albumpath - * - * @return array<string,int>|false information about the given path - */ - public function getAlbumInfo($albumpath) { - $userFolder = $this->userFolder; - $nodeInfo = false; - - if ($userFolder !== null) { - $nodeInfo = $this->getNodeInfo($userFolder, $albumpath); - } else { - $message = "Could not access the user's folder"; - $code = Http::STATUS_NOT_FOUND; - $this->kaBoom($message, $code); - } - - return $nodeInfo; + $this->previewService = $previewManager; } /** @@ -127,16 +87,15 @@ class InfoService extends Service { */ public function getSupportedMimes($slideshow = true) { $supportedMimes = []; - $wantedMimes = self::$baseMimeTypes; + $wantedMimes = $this->baseMimeTypes; if ($slideshow) { - $wantedMimes = array_merge($wantedMimes, self::$slideshowMimeTypes); + $wantedMimes = array_merge($wantedMimes, $this->slideshowMimeTypes); } foreach ($wantedMimes as $wantedMime) { // Let's see if a preview of files of that media type can be generated - $preview = $this->previewManager; - if ($preview->isMimeSupported($wantedMime)) { + if ($this->previewService->isMimeSupported($wantedMime)) { $supportedMimes[] = $wantedMime; // We add it to the list of supported media types } } @@ -148,49 +107,21 @@ class InfoService extends Service { } /** - * This returns the list of all images which can be shown + * This returns the list of all images which can be shown starting from the given folder * - * For private galleries, it returns all images - * For public galleries, it starts from the folder the link gives access to + * @param array $folderData * * @return array all the images we could find */ - public function getImages() { - $folderData = $this->getImagesFolder(); - - $imagesFolder = $folderData['imagesFolder']; - $images = $this->searchByMime($imagesFolder); - + public function getImages($folderData) { + $images = $this->searchByMime($folderData['imagesFolder']); $fromRootToFolder = $folderData['fromRootToFolder']; - $result = $this->fixImagePath($images, $fromRootToFolder); - /*$this->logger->debug("Images array: {images}",['images' => $result]);*/ + $result = $this->prepareImagesArray($images, $fromRootToFolder); - return $result; - } + //$this->logger->debug("Images array: {images}", ['images' => $result]); - /** - * Returns the folder where we need to look for files, as well as the path - * starting from it and going up to the user's root folder - * - * @return array<string,Folder|string> - */ - private function getImagesFolder() { - $env = $this->environmentService->getEnv(); - $pathRelativeToFolder = $env['relativePath']; - /** @type Folder $folder */ - $folder = $env['folder']; - $folderPath = $folder->getPath(); - /** @type Folder $imagesFolder */ - $imagesFolder = $this->getResourceFromPath($folder, $pathRelativeToFolder); - $fromRootToFolder = $folderPath . $pathRelativeToFolder; - - $folderData = [ - 'imagesFolder' => $imagesFolder, - 'fromRootToFolder' => $fromRootToFolder, - ]; - - return $folderData; + return $result; } /** @@ -225,6 +156,7 @@ class InfoService extends Service { * We remove the part which goes from the user's root to the current * folder and we also remove the current folder for public galleries * + * @todo Test this on OC8 * On OC7, we fix searchByMime which returns images from the rubbish bin... * https://github.com/owncloud/core/issues/4903 * @@ -244,15 +176,13 @@ class InfoService extends Service { * * @return array */ - private function fixImagePath($images, $fromRootToFolder) { + private function prepareImagesArray($images, $fromRootToFolder) { $result = []; /** @type File $image */ foreach ($images as $image) { $imagePath = $image->getPath(); $mimeType = $image->getMimetype(); - $fixedPath = str_replace( - $fromRootToFolder, '', $imagePath - ); + $fixedPath = str_replace($fromRootToFolder, '', $imagePath); if (substr($fixedPath, 0, 9) === "_trashbin") { continue; } diff --git a/service/notfoundserviceexception.php b/service/notfoundserviceexception.php new file mode 100644 index 00000000..298f7bbd --- /dev/null +++ b/service/notfoundserviceexception.php @@ -0,0 +1,28 @@ +<?php +/** + * ownCloud - galleryplus + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Olivier Paroz <owncloud@interfasys.ch> + * + * @copyright Olivier Paroz 2014-2015 + */ + +namespace OCA\GalleryPlus\Service; + +/** + * Thrown when the service cannot reply to a request + */ +class NotFoundServiceException extends ServiceException { + + /** + * Constructor + * + * @param string $msg the message contained in the exception + */ + public function __construct($msg) { + parent::__construct($msg); + } +}
\ No newline at end of file diff --git a/service/previewservice.php b/service/previewservice.php index 23b1c9d4..b8d5ba85 100644 --- a/service/previewservice.php +++ b/service/previewservice.php @@ -6,19 +6,16 @@ * later. See the COPYING file. * * @author Olivier Paroz <owncloud@interfasys.ch> - * @author Robin Appelman <icewind@owncloud.com> * * @copyright Olivier Paroz 2014-2015 - * @copyright Robin Appelman 2012-2015 */ namespace OCA\GalleryPlus\Service; use OCP\Files\File; -use OCP\AppFramework\Http; - -use OCA\GalleryPlus\Http\ImageResponse; +use OCA\GalleryPlus\Environment\Environment; +use OCA\GalleryPlus\Environment\NotFoundEnvException; use OCA\GalleryPlus\Preview\Preview; use OCA\GalleryPlus\Utility\SmarterLogger; @@ -29,112 +26,83 @@ use OCA\GalleryPlus\Utility\SmarterLogger; */ class PreviewService extends Service { + use Base64Encode; + /** - * @type EnvironmentService + * @type Environment */ - private $environmentService; + private $environment; /** * @type Preview */ private $previewManager; - /** - * @type bool - */ - private $animatedPreview = true; - /** - * @type bool - */ - private $keepAspect = true; - /** - * @type bool - */ - private $base64Encode = false; - /** - * @type bool - */ - private $download = false; /** * Constructor * * @param string $appName - * @param EnvironmentService $environmentService - * @param SmarterLogger $logger + * @param Environment $environment * @param Preview $previewManager + * @param SmarterLogger $logger */ public function __construct( $appName, - EnvironmentService $environmentService, - SmarterLogger $logger, - Preview $previewManager + Environment $environment, + Preview $previewManager, + SmarterLogger $logger ) { parent::__construct($appName, $logger); - $this->environmentService = $environmentService; + $this->environment = $environment; $this->previewManager = $previewManager; } /** - * @param string $image - * @param int $maxX - * @param int $maxY - * @param bool $keepAspect - * - * @return array<string,\OC_Image|string> preview data - */ - public function createThumbnails($image, $maxX, $maxY, $keepAspect) { - $this->animatedPreview = false; - $this->base64Encode = true; - $this->keepAspect = $keepAspect; - - return $this->createPreview($image, $maxX, $maxY); - } - - - /** - * Sends either a large preview of the requested file or the original file - * itself + * Returns true if the passed mime type is supported * - * @param string $image - * @param int $maxX - * @param int $maxY + * @param string $mimeType * - * @return ImageResponse + * @return boolean */ - public function showPreview($image, $maxX, $maxY) { - $preview = $this->createPreview($image, $maxX, $maxY); - - return new ImageResponse($preview, $preview['status']); + public function isMimeSupported($mimeType = '*') { + return $this->previewManager->isMimeSupported($mimeType); } /** - * Downloads the requested file + * Decides if we should download the file instead of generating a preview * * @param string $image + * @param bool $animatedPreview * - * @return ImageResponse + * @return bool */ - public function downloadPreview($image) { - $this->download = true; + public function isPreviewRequired($image, $animatedPreview) { + $file = null; + try { + /** @type File $file */ + $file = $this->environment->getResourceFromPath($image); + + } catch (NotFoundEnvException $exception) { + $this->logAndThrowNotFound($exception->getMessage()); + } + $mime = $file->getMimeType(); + if ($mime === 'image/svg+xml') { + return $this->isSvgPreviewRequired(); + } + if ($mime === 'image/gif') { + return $this->isGifPreviewRequired($file, $animatedPreview); + } - return $this->showPreview($image, null, null); + return true; } /** - * Creates an array containing everything needed to render a preview in the - * browser - * - * If the browser can use the file as-is or if we're asked to send it - * as-is, then we simply let the browser download the file, straight from - * Files + * Returns an array containing everything needed by the client to be able to display a preview * - * Some files are base64 encoded. Explicitly for files which are downloaded - * (cached Thumbnails, SVG, GIFs) and via __toStrings for the previews - * which are instances of \OC_Image - * - * We check that the preview returned by the Preview class can be used by - * the browser. If not, we send the mime icon and change the status code so - * that the client knows that the process failed + * * path: the given path to the file + * * mimetype: the file's media type + * * preview: the preview's content + * * status: a code indicating whether the conversion process was successful or not * * Example logger * $this->logger->debug( @@ -151,69 +119,100 @@ class PreviewService extends Service { * @param string $image path to the image, relative to the user folder * @param int $maxX asked width for the preview * @param int $maxY asked height for the preview + * @param bool $keepAspect + * + * @return array <string,\OC_Image|string> preview data + */ + public function createPreview($image, $maxX = 0, $maxY = 0, $keepAspect = true) { + $file = null; + try { + /** @type File $file */ + $file = $this->environment->getResourceFromPath($image); + } catch (NotFoundEnvException $exception) { + $this->logAndThrowNotFound($exception->getMessage()); + } + $userId = $this->environment->getUserID(); + $imagePathFromFolder = $this->environment->getImagePathFromFolder($image); + $this->previewManager->setupView($userId, $file, $imagePathFromFolder); + + $preview = $this->previewManager->preparePreview($maxX, $maxY, $keepAspect); + $preview['path'] = $image; + $preview['status'] = Service::STATUS_OK; + if (!$this->previewManager->isPreviewValid()) { + $preview['status'] = Service::STATUS_UNSUPPORTED_MEDIA_TYPE; + } + + return $preview; + } + + /** + * Decides if we should download the SVG or generate a preview + * + * SVGs are downloaded if the SVG converter is disabled + * Files of any media type are downloaded if requested by the client * - * @return array<string,\OC_Image|string> preview data + * @return bool */ - private function createPreview($image, $maxX = 0, $maxY = 0) { - $env = $this->environmentService->getEnv(); - $owner = $env['owner']; - $folder = $env['folder']; - $imagePathFromFolder = $env['relativePath'] . $image; - /** @type File $file */ - $file = $this->getResourceFromPath($folder, $imagePathFromFolder); - $this->previewManager->setupView($owner, $file, $imagePathFromFolder); - $previewRequired = - $this->previewManager->previewRequired($this->animatedPreview, $this->download); - - if ($previewRequired) { - $perfectPreview = - $this->previewManager->preparePreview($maxX, $maxY, $this->keepAspect); - } else { - $perfectPreview = $this->prepareDownload($file, $image); + private function isSvgPreviewRequired() { + if (!$this->isMimeSupported('image/svg+xml')) { + return false; } - $perfectPreview['preview'] = $this->base64EncodeCheck($perfectPreview['preview']); - $perfectPreview['path'] = $image; - return $perfectPreview; + return true; } /** - * Returns the data needed to make a file available for download + * Decides if we should download the GIF or generate a preview + * + * GIFs are downloaded if they're animated and we want to show + * animations * * @param File $file - * @param string $image + * @param bool $animatedPreview * - * @return array + * @return bool */ - private function prepareDownload($file, $image) { - $this->logger->debug("[PreviewService] Downloading file {file} as-is", ['file' => $image]); - - return [ - 'preview' => $file->getContent(), - 'mimetype' => $file->getMimeType(), - 'status' => Http::STATUS_OK - ]; + private function isGifPreviewRequired($file, $animatedPreview) { + $animatedGif = $this->isGifAnimated($file); + + if ($animatedPreview && $animatedGif) { + return false; + } + + return true; } /** - * Returns base64 encoded data of a preview + * Tests if a GIF is animated + * + * An animated gif contains multiple "frames", with each frame having a + * header made up of: + * * a static 4-byte sequence (\x00\x21\xF9\x04) + * * 4 variable bytes + * * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21) + * + * We read through the file until we reach the end of the file, or we've + * found at least 2 frame headers * - * @param \OC_Image|string $previewData + * @link http://php.net/manual/en/function.imagecreatefromgif.php#104473 * - * @return \OC_Image|string + * @param File $file + * + * @return bool */ - private function base64EncodeCheck($previewData) { - $base64Encode = $this->base64Encode; - - if ($base64Encode === true) { - if ($previewData instanceof \OC_Image) { - $previewData = (string)$previewData; - } else { - $previewData = base64_encode($previewData); - } + private function isGifAnimated($file) { + $fileHandle = $file->fopen('rb'); + $count = 0; + while (!feof($fileHandle) && $count < 2) { + $chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time + $count += preg_match_all( + '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches + ); } - return $previewData; + fclose($fileHandle); + + return $count > 1; } }
\ No newline at end of file diff --git a/service/service.php b/service/service.php index 725cc015..de142dab 100644 --- a/service/service.php +++ b/service/service.php @@ -12,12 +12,6 @@ namespace OCA\GalleryPlus\Service; -use OCP\Files\Folder; -use OCP\Files\Node; -use OCP\Files\NotFoundException; - -use OCP\AppFramework\Http; - use OCA\GalleryPlus\Utility\SmarterLogger; /** @@ -27,10 +21,16 @@ use OCA\GalleryPlus\Utility\SmarterLogger; */ abstract class Service { + const STATUS_OK = 200; + const STATUS_NOT_FOUND = 404; + const STATUS_UNSUPPORTED_MEDIA_TYPE = 415; + const STATUS_UNPROCESSABLE_ENTITY = 422; + const STATUS_INTERNAL_SERVER_ERROR = 500; + /** * @type string */ - protected $appName; + private $appName; /** * @type SmarterLogger */ @@ -42,86 +42,24 @@ abstract class Service { * @param string $appName * @param SmarterLogger $logger */ - public function __construct($appName, SmarterLogger $logger) { + public function __construct( + $appName, + SmarterLogger $logger + ) { $this->appName = $appName; $this->logger = $logger; } /** - * Returns the resource identified by the given ID - * - * @param Folder $folder - * @param int $resourceId - * - * @return Node - * @throws ServiceException - */ - protected function getResourceFromId($folder, $resourceId) { - $resourcesArray = $folder->getById($resourceId); - if ($resourcesArray[0] === null) { - $this->kaBoom('Could not resolve linkItem', Http::STATUS_NOT_FOUND); - } - - return $resourcesArray[0]; - } - - /** - * Returns the resource located at the given path - * - * The path starts from the user's files folder - * The resource is either a File or a Folder - * - * @param Folder $folder - * @param string $path - * - * @return Node - */ - protected function getResourceFromPath($folder, $path) { - $nodeInfo = $this->getNodeInfo($folder, $path); - - return $this->getResourceFromId($folder, $nodeInfo['fileid']); - } - - /** - * Returns the Node based on the current user's files folder and a given - * path - * - * @param Folder $folder - * @param string $path - * - * @return int[]|false - */ - protected function getNodeInfo($folder, $path) { - $nodeInfo = false; - try { - $node = $folder->get($path); - $nodeInfo = [ - 'fileid' => $node->getId(), - 'permissions' => $node->getPermissions() - ]; - } catch (NotFoundException $exception) { - $message = $exception->getMessage(); - $code = Http::STATUS_NOT_FOUND; - $this->kaBoom($message, $code); - } - - return $nodeInfo; - } - - /** * Logs the error and raises an exception * * @param string $message - * @param int $code * * @throws ServiceException */ - protected function kaBoom($message, $code) { - $this->logger->error($message . ' (' . $code . ')'); + protected function logAndThrowNotFound($message) { + $this->logger->error($message . ' (404)'); - throw new ServiceException( - $message, - $code - ); + throw new NotFoundServiceException($message); } }
\ No newline at end of file diff --git a/service/serviceexception.php b/service/serviceexception.php index be78273a..81f24079 100644 --- a/service/serviceexception.php +++ b/service/serviceexception.php @@ -12,18 +12,19 @@ namespace OCA\GalleryPlus\Service; +use Exception; + /** * Thrown when the service cannot reply to a request */ -class ServiceException extends \Exception { +class ServiceException extends Exception { /** * Constructor * * @param string $msg the message contained in the exception - * @param int $code the HTTP status code */ - public function __construct($msg, $code = 0) { - parent::__construct($msg, $code); + public function __construct($msg) { + parent::__construct($msg); } }
\ No newline at end of file diff --git a/service/thumbnailservice.php b/service/thumbnailservice.php index 4a4980db..86922927 100644 --- a/service/thumbnailservice.php +++ b/service/thumbnailservice.php @@ -6,101 +6,54 @@ * later. See the COPYING file. * * @author Olivier Paroz <owncloud@interfasys.ch> - * @author Robin Appelman <icewind@owncloud.com> * * @copyright Olivier Paroz 2014-2015 - * @copyright Robin Appelman 2012-2015 */ namespace OCA\GalleryPlus\Service; -use OCP\IEventSource; - +use OCA\GalleryPlus\Environment\Environment; +use OCA\GalleryPlus\Preview\Preview; use OCA\GalleryPlus\Utility\SmarterLogger; /** - * Collects and returns thumbnails for the list of images which is submitted to + * Creates thumbnails for the list of images which is submitted to * the service * * Uses EventSource to send back thumbnails as soon as they're ready * * @package OCA\GalleryPlus\Service */ -class ThumbnailService { - - /** - * @type string - */ - private $appName; - /** - * @type SmarterLogger - */ - private $logger; - /** - * @type IEventSource - */ - private $eventSource; - /** - * @type PreviewService - */ - private $previewService; - +class ThumbnailService extends PreviewService { /** * Constructor * * @param string $appName + * @param Environment $environment + * @param Preview $previewManager * @param SmarterLogger $logger - * @param IEventSource $eventSource - * @param PreviewService $previewService */ public function __construct( $appName, - SmarterLogger $logger, - IEventSource $eventSource, - PreviewService $previewService + Environment $environment, + Preview $previewManager, + SmarterLogger $logger ) { - $this->appName = $appName; - $this->logger = $logger; - $this->previewService = $previewService; - $this->eventSource = $eventSource; - } - - /** - * Sends previews of each image contained in the $images array using - * EventSource - * - * The data is generated by the PreviewService and is base64 encoded - * - * WARNING: We can't close the session here because public galleries - * of encrypted files would not get their previews - * - * FIXME: @LukasReschke says: The exit is required here because - * otherwise the AppFramework is trying to add headers as well after - * dispatching the request which results in a "Cannot modify header - * information" notice. - * WARNING: Returning a JSON response does not work. - * - * @param string $images - * @param bool $square - * @param bool $scale - */ - public function getAlbumThumbnails($images, $square, $scale) { - $imagesArray = explode(';', $images); - - $this->createThumbnails($imagesArray, $square, $scale); - - $this->eventSource->close(); - - exit(); + parent::__construct( + $appName, + $environment, + $previewManager, + $logger + ); } /** - * Asks the Thumbnail service to send us thumbnails + * Creates thumbnails of asked dimensions and aspect * - * Album thumnails need to be 200x200 and some will be resized by the - * browser to 200x100 or 100x100. - * Standard thumbnails are 400x200. + * * Album thumbnails need to be 200x200 and some will be resized by the + * browser to 200x100 or 100x100. + * * Standard thumbnails are 400x200. * * Sample logger * We can't just send previewData as it can be quite a large stream @@ -113,21 +66,23 @@ class ThumbnailService { * ] * ); * - * @param string[] $imagesArray + * @param string $image * @param bool $square * @param bool $scale + * + * @return array */ - private function createThumbnails($imagesArray, $square, $scale) { - foreach ($imagesArray as $image) { - $height = 200 * $scale; - if ($square) { - $width = $height; - } else { - $width = 2 * $height; - } - - $preview = $this->previewService->createThumbnails($image, $width, $height, !$square); - $this->eventSource->send('preview', $preview); + public function createThumbnail($image, $square, $scale) { + $height = 200 * $scale; + if ($square) { + $width = $height; + } else { + $width = 2 * $height; } + $preview = $this->createPreview($image, $width, $height, !$square); + $preview['preview'] = $this->encode($preview['preview']); + + return $preview; } + }
\ No newline at end of file |