From 75af90b4f196014ecbf333229431147009bf8950 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Tue, 6 Dec 2016 21:09:40 +0100 Subject: Cleanup source code Signed-off-by: Lukas Reschke --- lib/Controller/DocumentController.php | 166 +++++++++++++++++++++++ lib/Controller/SettingsController.php | 99 ++++++++++++++ lib/Controller/WopiController.php | 245 ++++++++++++++++++++++++++++++++++ lib/WOPI/DiscoveryManager.php | 117 ++++++++++++++++ lib/WOPI/Parser.php | 58 ++++++++ lib/db/member.php | 130 ------------------ lib/db/op.php | 190 -------------------------- lib/db/session.php | 212 ----------------------------- lib/db/wopi.php | 6 - lib/downloadresponse.php | 123 ----------------- lib/file.php | 200 --------------------------- lib/genesis.php | 97 -------------- lib/helper.php | 106 --------------- lib/storage.php | 1 - 14 files changed, 685 insertions(+), 1065 deletions(-) create mode 100644 lib/Controller/DocumentController.php create mode 100644 lib/Controller/SettingsController.php create mode 100644 lib/Controller/WopiController.php create mode 100644 lib/WOPI/DiscoveryManager.php create mode 100644 lib/WOPI/Parser.php delete mode 100644 lib/db/member.php delete mode 100644 lib/db/op.php delete mode 100644 lib/db/session.php delete mode 100644 lib/downloadresponse.php delete mode 100644 lib/file.php delete mode 100644 lib/genesis.php (limited to 'lib') diff --git a/lib/Controller/DocumentController.php b/lib/Controller/DocumentController.php new file mode 100644 index 00000000..b961b126 --- /dev/null +++ b/lib/Controller/DocumentController.php @@ -0,0 +1,166 @@ +uid = $UserId; + $this->l10n = $l10n; + $this->settings = $settings; + $this->appConfig = $appConfig; + $this->cache = $cache->create($appName); + $this->discoveryManager = $discoveryManager; + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function index(){ + $response = new TemplateResponse('richdocuments', 'documents'); + $policy = new ContentSecurityPolicy(); + $policy->addAllowedFrameDomain($this->appConfig->getAppValue('wopi_url')); + $response->setContentSecurityPolicy($policy); + return $response; + } + + /** + * @NoAdminRequired + */ + public function create(){ + $mimetype = $this->request->post['mimetype']; + $filename = $this->request->post['filename']; + $dir = $this->request->post['dir']; + + $view = new View('/' . $this->uid . '/files'); + if (!$dir){ + $dir = '/'; + } + + $basename = $this->l10n->t('New Document.odt'); + switch ($mimetype) { + case 'application/vnd.oasis.opendocument.spreadsheet': + $basename = $this->l10n->t('New Spreadsheet.ods'); + break; + case 'application/vnd.oasis.opendocument.presentation': + $basename = $this->l10n->t('New Presentation.odp'); + break; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + $basename = $this->l10n->t('New Document.docx'); + break; + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + $basename = $this->l10n->t('New Spreadsheet.xlsx'); + break; + case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': + $basename = $this->l10n->t('New Presentation.pptx'); + break; + default: + // to be safe + $mimetype = 'application/vnd.oasis.opendocument.text'; + break; + } + + if (!$filename){ + $path = Helper::getNewFileName($view, $dir . '/' . $basename); + } else { + $path = $dir . '/' . $filename; + } + + $content = ''; + if (class_exists('\OC\Files\Type\TemplateManager')){ + $manager = \OC_Helper::getFileTemplateManager(); + $content = $manager->getTemplate($mimetype); + } + + if (!$content){ + $content = file_get_contents(dirname(__DIR__) . self::ODT_TEMPLATE_PATH); + } + + $discovery_parsed = null; + try { + $discovery = $this->discoveryManager->get(); + + $loadEntities = libxml_disable_entity_loader(true); + $discovery_parsed = simplexml_load_string($discovery); + libxml_disable_entity_loader($loadEntities); + + if ($discovery_parsed === false) { + $this->cache->remove('discovery.xml'); + $wopiRemote = $this->getWopiUrl(false); + + return array( + 'status' => 'error', + 'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)), + 'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote)) + ); + } + } + catch (ResponseException $e) { + return array( + 'status' => 'error', + 'message' => $e->getMessage(), + 'hint' => $e->getHint() + ); + } + + if ($content && $view->file_put_contents($path, $content)){ + $info = $view->getFileInfo($path); + $ret = $this->getWopiSrcUrl($discovery_parsed, $mimetype); + $response = array( + 'status' => 'success', + 'fileid' => $info['fileid'], + 'urlsrc' => $ret['urlsrc'], + 'action' => $ret['action'], + 'lolang' => $this->settings->getUserValue($this->uid, 'core', 'lang', 'en'), + 'data' => \OCA\Files\Helper::formatFileInfo($info) + ); + } else { + $response = array( + 'status' => 'error', + 'message' => (string) $this->l10n->t('Can\'t create document') + ); + } + return $response; + } +} diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php new file mode 100644 index 00000000..69b28084 --- /dev/null +++ b/lib/Controller/SettingsController.php @@ -0,0 +1,99 @@ +l10n = $l10n; + $this->appConfig = $appConfig; + } + + /** + * @NoAdminRequired + */ + public function getSupportedMimes(){ + return array( + 'status' => 'success', + 'mimes' => Filter::getAll() + ); + } + + /** + * @NoAdminRequired + * + * @return JSONResponse + */ + public function getSettings() { + return new JSONResponse([ + 'doc_format' => $this->appConfig->getAppValue('doc_format'), + 'wopi_url' => $this->appConfig->getAppValue('wopi_url'), + ]); + } + + /** + * @param string $wopi_url + * @param string $doc_format + * @return JSONResponse + */ + public function setSettings($wopi_url, + $doc_format){ + $message = $this->l10n->t('Saved'); + + if ($wopi_url !== null){ + $this->appConfig->setAppValue('wopi_url', $wopi_url); + + $colon = strpos($wopi_url, ':', 0); + if (\OC::$server->getRequest()->getServerProtocol() !== substr($wopi_url, 0, $colon)){ + $message = $this->l10n->t('Saved with error: Collabora Online should use the same protocol as the server installation.'); + } + } + + if ($doc_format !== null) { + $this->appConfig->setAppValue('doc_format', $doc_format); + } + + $richMemCache = \OC::$server->getMemCacheFactory()->create('richdocuments'); + $richMemCache->clear('discovery.xml'); + + $response = array( + 'status' => 'success', + 'data' => array('message' => (string) $message) + ); + + return new JSONResponse($response); + } +} diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php new file mode 100644 index 00000000..676fb0e9 --- /dev/null +++ b/lib/Controller/WopiController.php @@ -0,0 +1,245 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Richdocuments\Controller; + +use OCA\Richdocuments\Db\Wopi; +use OCA\Richdocuments\WOPI\Parser; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\AppFramework\Http\StreamResponse; + +class WopiController extends Controller { + /** @var IRootFolder */ + private $rootFolder; + /** @var string */ + private $userId; + /** @var IUserManager */ + private $userManager; + /** @var Parser */ + private $wopiParser; + + /** + * @param string $appName + * @param IRequest $request + * @param IRootFolder $rootFolder + * @param string $UserId + * @param IUserManager $userManager + * @param Parser $wopiParser + */ + public function __construct($appName, + $UserId, + IRequest $request, + IRootFolder $rootFolder, + IUserManager $userManager, + Parser $wopiParser) { + parent::__construct($appName, $request); + $this->rootFolder = $rootFolder; + $this->userId = $UserId; + $this->userManager = $userManager; + $this->wopiParser = $wopiParser; + } + + /** + * Generates and returns an access token for a given fileId + * + * @NoAdminRequired + * + * @param string $fileId + * @return JSONResponse + */ + public function getToken($fileId) { + $arr = explode('_', $fileId, 2); + $version = '0'; + if (count($arr) === 2) { + list($fileId, $version) = $arr; + } + + try { + /** @var File $file */ + $file = $this->rootFolder->getUserFolder($this->userId)->getById($fileId)[0]; + $updatable = $file->isUpdateable(); + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + // If token is for some versioned file + if ($version !== '0') { + $updatable = false; + } + + $row = new Wopi(); + $serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost(); + $token = $row->generateFileToken($fileId, $version, $updatable, $serverHost); + + try { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + /** @var File $file */ + $file = $userFolder->getById($fileId)[0]; + $sessionData['title'] = basename($file->getPath()); + $sessionData['permissions'] = $file->getPermissions(); + $sessionData['file_id'] = $file->getId(); + + $sessionData['documents'] = [ + 0 => [ + 'urlsrc' => $this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'], + 'path' => $file->getPath(), + 'token' => $token, + ], + ]; + + return new JSONResponse($sessionData); + } catch (\Exception $e){ + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + } + + /** + * Returns general info about a file. + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * + * @param string $fileId + * @return JSONResponse + */ + public function checkFileInfo($fileId) { + $token = $this->request->getParam('access_token'); + + $arr = explode('_', $fileId, 2); + $version = '0'; + if (count($arr) === 2) { + list($fileId, $version) = $arr; + } + + $row = new Wopi(); + $row->loadBy('token', $token); + + $res = $row->getPathForToken($fileId, $version, $token); + if ($res === false) { + return new JSONResponse(); + } + + // Login the user to see his mount locations + try { + /** @var File $file */ + $userFolder = $this->rootFolder->getUserFolder($res['editor']); + $file = $userFolder->getById($fileId)[0]; + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + return new JSONResponse( + [ + 'BaseFileName' => $file->getName(), + 'Size' => $file->getSize(), + 'Version' => $version, + 'UserId' => $res['editor'], + 'UserFriendlyName' => $this->userManager->get($res['editor'])->getDisplayName(), + 'UserCanWrite' => $res['canwrite'] ? true : false, + 'PostMessageOrigin' => $res['server_host'], + ] + ); + } + + /** + * Given an access token and a fileId, returns the contents of the file. + * Expects a valid token in access_token parameter. + * + * @PublicPage + * @NoCSRFRequired + * + * @param string $fileId + * @param string $access_token + * @return Http\Response + */ + public function getFile($fileId, + $access_token) { + $arr = explode('_', $fileId, 2); + $version = '0'; + if (count($arr) === 2) { + list($fileId, $version) = $arr; + } + + $row = new Wopi(); + $row->loadBy('token', $access_token); + + $res = $row->getPathForToken($fileId, $version, $access_token); + + try { + /** @var File $file */ + $userFolder = $this->rootFolder->getUserFolder($res['editor']); + $file = $userFolder->getById($fileId)[0]; + $response = new StreamResponse($file->fopen('rb')); + $response->addHeader('Content-Disposition', 'attachment'); + $response->addHeader('Content-Type', 'application/octet-stream'); + return $response; + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + } + + + + /** + * Given an access token and a fileId, replaces the files with the request body. + * Expects a valid token in access_token parameter. + * + * @PublicPage + * @NoCSRFRequired + * + * @param string $fileId + * @param string $access_token + * @return JSONResponse + */ + public function putFile($fileId, $access_token) { + $arr = explode('_', $fileId, 2); + $version = '0'; + if (count($arr) === 2) { + list($fileId, $version) = $arr; + } + + $row = new Wopi(); + $row->loadBy('token', $access_token); + + $res = $row->getPathForToken($fileId, $version, $access_token); + if (!$res['canwrite']) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + try { + /** @var File $file */ + $userFolder = $this->rootFolder->getUserFolder($res['editor']); + $file = $userFolder->getById($fileId)[0]; + $content = fopen('php://input', 'rb'); + $file->putContent($content); + return new JSONResponse(); + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/lib/WOPI/DiscoveryManager.php b/lib/WOPI/DiscoveryManager.php new file mode 100644 index 00000000..39326c17 --- /dev/null +++ b/lib/WOPI/DiscoveryManager.php @@ -0,0 +1,117 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Richdocuments\WOPI; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\IL10N; +use OCP\Notification\IApp; + +class DiscoveryManager { + /** @var IClientService */ + private $clientService; + /** @var ISimpleFolder */ + private $appData; + /** @var IConfig */ + private $config; + /** @var IL10N */ + private $l10n; + /** @var ITimeFactory */ + private $timeFactory; + + /** + * @param IClientService $clientService + * @param IAppData $appData + * @param IConfig $config + * @param IL10N $l10n + * @param ITimeFactory $timeFactory + */ + public function __construct(IClientService $clientService, + IAppData $appData, + IConfig $config, + IL10N $l10n, + ITimeFactory $timeFactory) { + $this->clientService = $clientService; + try { + $this->appData = $appData->getFolder('richdocuments'); + } catch (NotFoundException $e) { + $this->appData = $appData->newFolder('richdocuments'); + } + $this->config = $config; + $this->l10n = $l10n; + $this->timeFactory = $timeFactory; + } + + public function get() { + // First check if there is a local valid discovery file + try { + $file = $this->appData->getFile('discovery.xml'); + $decodedFile = json_decode($file->getContent(), true); + if($decodedFile['timestamp'] + 3600 > $this->timeFactory->getTime()) { + return $decodedFile['data']; + } + } catch (NotFoundException $e) { + $file = $this->appData->newFile('discovery.xml'); + } + + $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url'); + $wopiDiscovery = $remoteHost . '/hosting/discovery'; + + $client = $this->clientService->newClient(); + try { + $response = $client->get($wopiDiscovery); + } catch (\Exception $e) { + $error_message = $e->getMessage(); + if (preg_match('/^cURL error ([0-9]*):/', $error_message, $matches)) { + $admin_check = $this->l10n->t('Please ask your administrator to check the Collabora Online server setting. The exact error message was: ') . $error_message; + + $curl_error = $matches[1]; + switch ($curl_error) { + case '1': + throw new ResponseException($this->l10n->t('Collabora Online: The protocol specified in "%s" is not allowed.', array($wopiRemote)), $admin_check); + case '3': + throw new ResponseException($this->l10n->t('Collabora Online: Malformed URL "%s".', array($wopiRemote)), $admin_check); + case '6': + throw new ResponseException($this->l10n->t('Collabora Online: Cannot resolve the host "%s".', array($wopiRemote)), $admin_check); + case '7': + throw new ResponseException($this->l10n->t('Collabora Online: Cannot connect to the host "%s".', array($wopiRemote)), $admin_check); + case '60': + throw new ResponseException($this->l10n->t('Collabora Online: SSL certificate is not installed.'), $this->l10n->t('Please ask your administrator to add ca-chain.cert.pem to the ca-bundle.crt, for example "cat /etc/loolwsd/ca-chain.cert.pem >> /resources/config/ca-bundle.crt" . The exact error message was: ') . $error_message); + } + } + throw new ResponseException($this->l10n->t('Collabora Online unknown error: ') . $error_message, $contact_admin); + } + + $responseBody = $response->getBody(); + $file->putContent( + json_encode([ + 'data' => $responseBody, + 'timestamp' => $this->timeFactory->getTime(), + ]) + ); + return $responseBody; + } +} diff --git a/lib/WOPI/Parser.php b/lib/WOPI/Parser.php new file mode 100644 index 00000000..934ead69 --- /dev/null +++ b/lib/WOPI/Parser.php @@ -0,0 +1,58 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Richdocuments\WOPI; + +class Parser { + /** @var DiscoveryManager */ + private $discoveryManager; + + /** + * @param DiscoveryManager $discoveryManager + */ + public function __construct(DiscoveryManager $discoveryManager) { + $this->discoveryManager = $discoveryManager; + } + + /** + * @param $mimetype + * @return array + * @throws \Exception + */ + public function getUrlSrc($mimetype) { + $discovery = $this->discoveryManager->get(); + $loadEntities = libxml_disable_entity_loader(true); + $discoveryParsed = simplexml_load_string($discovery); + libxml_disable_entity_loader($loadEntities); + + $result = $discoveryParsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype)); + if ($result && count($result) > 0) { + return [ + 'urlsrc' => (string)$result[0]['urlsrc'], + 'action' => (string)$result[0]['name'], + ]; + } + + throw new \Exception('Could not find urlsrc in WOPI'); + + } + +} \ No newline at end of file diff --git a/lib/db/member.php b/lib/db/member.php deleted file mode 100644 index d44f420a..00000000 --- a/lib/db/member.php +++ /dev/null @@ -1,130 +0,0 @@ -getL10n('richdocuments')->t('guest') . ')'; - } - - - public function updateActivity($memberId){ - return $this->execute( - 'UPDATE ' . $this->tableName . ' SET `last_activity`=?, `status`=? WHERE `member_id`=?', - array( - time(), - self::MEMBER_STATUS_ACTIVE, - $memberId - ) - ); - } - - - public function getActiveCollection($esId){ - $result = $this->execute(' - SELECT `es_id`, `member_id` - FROM ' . self::DB_TABLE . ' - WHERE `es_id`= ? - AND `status`=? - ', - array( - $esId, - self::MEMBER_STATUS_ACTIVE - ) - ); - $members = $result->fetchAll(); - if (!is_array($members)){ - $members = array(); - } - return $members; - - } - - /** - * Mark members as inactive - * @param string $esId - session Id - * @return array - list of memberId that were marked as inactive - */ - public function updateByTimeout($esId){ - $time = $this->getInactivityPeriod(); - - $result = $this->execute(' - SELECT `member_id` - FROM ' . self::DB_TABLE . ' - WHERE `es_id`= ? - AND `last_activity`fetchAll(); - if (is_array($deactivated) && count($deactivated)){ - $deactivated = array_map( - function($x){ - return ($x['member_id']); - }, $deactivated - ); - $this->deactivate($deactivated); - } else { - $deactivated = array(); - } - - return $deactivated; - } - - /** - * Update members to inactive state - * @param array $memberIds - */ - public function deactivate($memberIds){ - $stmt = $this->buildInQuery('member_id', $memberIds); - array_unshift($memberIds, self::MEMBER_STATUS_INACTIVE); - $this->execute(' - UPDATE ' . $this->tableName . ' - SET `status`=? - WHERE ' . $stmt, - $memberIds - ); - } - - protected function getInactivityPeriod(){ - return time() - self::ACTIVITY_THRESHOLD; - } -} diff --git a/lib/db/op.php b/lib/db/op.php deleted file mode 100644 index ee8cf760..00000000 --- a/lib/db/op.php +++ /dev/null @@ -1,190 +0,0 @@ -canInsertOp($esId, $memberId, $op)){ - continue; - } - $opObj->setData(array( - $esId, - $op['optype'], - $memberId, - json_encode($op) - )); - $opObj->insert(); - } - - return $opObj->getHeadSeq($esId); - } - - /** - * @returns "" when there are no Ops, or the seq of the last Op - */ - public function getHeadSeq($esId){ - $query = \OC::$server->getDatabaseConnection()->prepare(' - SELECT `seq` - FROM ' . $this->tableName . ' - WHERE `es_id`=? - ORDER BY `seq` DESC - ', 1); - $result = $query->execute([$esId]); - return !$result ? "" : $query->fetchColumn(); - } - - public function getOpsAfterJson($esId, $seq){ - $ops = $this->getOpsAfter($esId, $seq); - if (!is_array($ops)){ - $ops = array(); - } - $ops = array_map( - function($x){ - $decoded = json_decode($x['opspec'], true); - $decoded['memberid'] = strval($decoded['memberid']); - return $decoded; - }, - $ops - ); - return $ops; - } - - public function getOpsAfter($esId, $seq){ - if ($seq == ""){ - $seq = -1; - } - $query = \OC::$server->getDatabaseConnection()->prepare(' - SELECT `opspec` - FROM ' . self::DB_TABLE . ' - WHERE `es_id`=? - AND `seq`>? - ORDER BY `seq` ASC - '); - $query->execute(array($esId, $seq)); - return $query->fetchAll(); - } - - public function addMember($esId, $memberId, $fullName, $userId, $color, $imageUrl){ - $op = array( - 'optype' => 'AddMember', - 'memberid' => (string) $memberId, - 'timestamp' => $this->getMillisecondsAsString(), - 'setProperties' => array( - 'fullName' => $fullName, - 'color' => $color, - 'imageUrl' => $imageUrl, - 'uid' => $userId, - ) - ); - $this->insertOp($esId, $memberId, $op); - } - - public function removeCursor($esId, $memberId){ - $op = array( - 'optype' => 'RemoveCursor', - 'memberid' => (string) $memberId, - 'reason' => 'server-idle', - 'timestamp' => $this->getMillisecondsAsString() - ); - $this->insertOp($esId, $memberId, $op); - } - - public function removeMember($esId, $memberId){ - $op = array( - 'optype' => 'RemoveMember', - 'memberid' => (string) $memberId, - 'timestamp' => $this->getMillisecondsAsString() - ); - $this->insertOp($esId, $memberId, $op); - } - - //TODO: Implement https://github.com/kogmbh/WebODF/blob/master/webodf/lib/ops/OpUpdateMember.js#L95 - public function changeNick($esId, $memberId, $fullName){ - $op = array( - 'optype' => 'UpdateMember', - 'memberid' => (string) $memberId, - 'timestamp' => $this->getMillisecondsAsString(), - 'setProperties' => array( - 'fullName' => $fullName, - ) - ); - $this->insertOp($esId, $memberId, $op); - } - - protected function insertOp($esId, $memberId, $op){ - if ($this->canInsertOp($esId, $memberId, $op)){ - $op = new Op(array( - $esId, - $op['optype'], - $memberId, - json_encode($op) - )); - $op->insert(); - } - } - - protected function canInsertOp($esId, $memberId, $op){ - $cursorOps = array('AddCursor', 'RemoveCursor'); - $memberOps = array('AddMember', 'RemoveMember'); - $result = true; - - switch ($op['optype']){ - case 'AddCursor': - $ops = $this->getFilteredMemberOps($esId, $memberId, $cursorOps); - $result = !count($ops) || $ops[0]['optype'] === 'RemoveCursor'; - break; - case 'RemoveCursor': - $ops = $this->getFilteredMemberOps($esId, $memberId, $cursorOps); - $result = count($ops) && $ops[0]['optype'] === 'AddCursor'; - break; - case 'AddMember': - $ops = $this->getFilteredMemberOps($esId, $memberId, $memberOps); - $result = !count($ops) || $ops[0]['optype'] === 'RemoveMember'; - break; - case 'RemoveMember': - $ops = $this->getFilteredMemberOps($esId, $memberId, $memberOps); - $result = count($ops) && $ops[0]['optype'] === 'AddMember'; - break; - } - return $result; - } - - protected function getFilteredMemberOps($esId, $memberId, $targetOps){ - $stmt = $this->buildInQuery('optype', $targetOps); - $result = $this->execute(' - SELECT `optype` FROM ' . $this->tableName . ' - WHERE es_id=? AND member=? AND ' . $stmt . 'ORDER BY `seq` DESC', - array_merge(array($esId, $memberId), $targetOps) - ); - $ops = $result->fetchAll(); - if (!is_array($ops)){ - $ops = array(); - } - return $ops; - } - - protected function getMillisecondsAsString(){ - $microtime = microtime(); - list($usec, $sec) = explode(" ", $microtime); - $milliseconds = $sec.substr($usec, 2, 3); - return $milliseconds; - } -} diff --git a/lib/db/session.php b/lib/db/session.php deleted file mode 100644 index 583cd342..00000000 --- a/lib/db/session.php +++ /dev/null @@ -1,212 +0,0 @@ -loadBy('file_id', $file->getFileId()); - - //If there is no existing session we need to start a new one - if (!$oldSession->hasData()){ - $newSession = new Session(array( - $genesis->getPath(), - $genesis->getHash(), - $file->getOwner(), - $file->getFileId() - )); - - if (!$newSession->insert()){ - throw new \Exception('Failed to add session into database'); - } - } - - $sessionData = $oldSession - ->loadBy('file_id', $file->getFileId()) - ->getData() - ; - - $memberColor = \OCA\Richdocuments\Helper::getMemberColor($uid); - $member = new \OCA\Richdocuments\Db\Member([ - $sessionData['es_id'], - $uid, - $memberColor, - time(), - intval($file->isPublicShare()), - $file->getToken() - ]); - - if (!$member->insert()){ - throw new \Exception('Failed to add member into database'); - } - $sessionData['member_id'] = (string) $member->getLastInsertId(); - - // Do we have OC_Avatar in out disposal? - if (\OC::$server->getConfig()->getSystemValue('enable_avatars', true) !== true){ - $imageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=='; - } else { - $imageUrl = $uid; - } - - $displayName = $file->isPublicShare() - ? $uid . ' ' . \OCA\Richdocuments\Db\Member::getGuestPostfix() - : \OC::$server->getUserSession()->getUser()->getDisplayName($uid) - ; - $userId = $file->isPublicShare() ? $displayName : \OC::$server->getUserSession()->getUser()->getUID(); - $op = new \OCA\Richdocuments\Db\Op(); - $op->addMember( - $sessionData['es_id'], - $sessionData['member_id'], - $displayName, - $userId, - $memberColor, - $imageUrl - ); - - $sessionData['title'] = basename($file->getPath()); - $sessionData['permissions'] = $file->getPermissions(); - - return $sessionData; - } - - public static function cleanUp($esId){ - $session = new Session(); - $session->deleteBy('es_id', $esId); - - $member = new \OCA\Richdocuments\Db\Member(); - $member->deleteBy('es_id', $esId); - - $op= new \OCA\Richdocuments\Db\Op(); - $op->deleteBy('es_id', $esId); - } - - - public function syncOps($memberId, $currentHead, $clientHead, $clientOps){ - $hasOps = count($clientOps)>0; - $op = new \OCA\Richdocuments\Db\Op(); - - // TODO handle the case ($currentHead == "") && ($seqHead != "") - if ($clientHead == $currentHead) { - // matching heads - if ($hasOps) { - // incoming ops without conflict - // Add incoming ops, respond with a new head - $newHead = \OCA\Richdocuments\Db\Op::addOpsArray($this->getEsId(), $memberId, $clientOps); - $result = array( - 'result' => 'added', - 'head_seq' => $newHead ? $newHead : $currentHead - ); - } else { - // no incoming ops (just checking for new ops...) - $result = array( - 'result' => 'new_ops', - 'ops' => array(), - 'head_seq' => $currentHead - ); - } - } else { // HEADs do not match - $result = array( - 'result' => $hasOps ? 'conflict' : 'new_ops', - 'ops' => $op->getOpsAfterJson($this->getEsId(), $clientHead), - 'head_seq' => $currentHead, - ); - } - - return $result; - } - - public function insert(){ - $esId = $this->getUniqueSessionId(); - array_unshift($this->data, $esId); - return parent::insert(); - } - - public function updateGenesisHash($esId, $genesisHash){ - return $this->execute( - 'UPDATE `*PREFIX*richdocuments_session` SET `genesis_hash`=? WHERE `es_id`=?', - array( - $genesisHash, $esId - ) - ); - } - - public function getInfo($esId){ - $result = $this->execute(' - SELECT `s`.*, COUNT(`m`.`member_id`) AS `users` - FROM ' . $this->tableName . ' AS `s` - LEFT JOIN `*PREFIX*richdocuments_member` AS `m` ON `s`.`es_id`=`m`.`es_id` - AND `m`.`status`=' . Db\Member::MEMBER_STATUS_ACTIVE . ' - AND `m`.`uid` != ? - WHERE `s`.`es_id` = ? - GROUP BY `m`.`es_id` - ', - [ - \OC::$server->getUserSession()->getUser()->getUID(), - $esId - ] - ); - - $info = $result->fetch(); - if (!is_array($info)){ - $info = array(); - } - return $info; - } - - protected function getUniqueSessionId(){ - $testSession = new Session(); - do{ - $id = \OC::$server->getSecureRandom() - ->getMediumStrengthGenerator() - ->generate(30, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); - } while ($testSession->load($id)->hasData()); - - return $id; - } -} diff --git a/lib/db/wopi.php b/lib/db/wopi.php index 36cda195..e30de4cf 100644 --- a/lib/db/wopi.php +++ b/lib/db/wopi.php @@ -1,5 +1,4 @@ request = $request; - $this->user = $user; - $this->path = $path; - - $this->view = new View('/' . $user); - if (!$this->view->file_exists($path)){ - $this->setStatus(Http::STATUS_NOT_FOUND); - } - } - - public function render(){ - if ($this->getStatus() === Http::STATUS_NOT_FOUND){ - return ''; - } - $info = $this->view->getFileInfo($this->path); - $this->ETag = $info['etag']; - - $content = $this->view->file_get_contents($this->path); - $data = \OCA\Richdocuments\Filter::read($content, $info['mimetype']); - $size = strlen($data['content']); - - - if (isset($this->request->server['HTTP_RANGE']) && !is_null($this->request->server['HTTP_RANGE'])){ - $isValidRange = preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/', $this->request->server['HTTP_RANGE']); - if (!$isValidRange){ - return $this->sendRangeNotSatisfiable($size); - } - - $ranges = explode(',', substr($this->request->server['HTTP_RANGE'], 6)); - foreach ($ranges as $range){ - $parts = explode('-', $range); - - if ($parts[0]==='' && $parts[1]=='') { - $this->sendNotSatisfiable($size); - } - if ($parts[0]==='') { - $start = $size - $parts[1]; - $end = $size - 1; - } else { - $start = $parts[0]; - $end = ($parts[1]==='') ? $size - 1 : $parts[1]; - } - - if ($start > $end){ - $this->sendNotSatisfiable($size); - } - - $buffer = substr($data['content'], $start, $end - $start); - $md5Sum = md5($buffer); - - // send the headers and data - $this->addHeader('Content-Length', $end - $start); - $this->addHeader('Content-md5', $md5Sum); - $this->addHeader('Accept-Ranges', 'bytes'); - $this->addHeader('Content-Range', 'bytes ' . $start . '-' . ($end) . '/' . $size); - $this->addHeader('Connection', 'close'); - $this->addHeader('Content-Type', $data['mimetype']); - $this->addContentDispositionHeader(); - return $buffer; - } - } - - $this->addHeader('Content-Type', $data['mimetype']); - $this->addContentDispositionHeader(); - $this->addHeader('Content-Length', $size); - - return $data['content']; - } - - /** - * Send 416 if we can't satisfy the requested ranges - * @param integer $filesize - */ - protected function sendRangeNotSatisfiable($filesize){ - $this->setStatus(Http::STATUS_REQUEST_RANGE_NOT_SATISFIABLE); - $this->addHeader('Content-Range', 'bytes */' . $filesize); // Required in 416. - return ''; - } - - protected function addContentDispositionHeader(){ - $encodedName = rawurlencode(basename($this->path)); - $isIE = preg_match("/MSIE/", $this->request->server["HTTP_USER_AGENT"]); - if ($isIE){ - $this->addHeader( - 'Content-Disposition', - 'attachment; filename="' . $encodedName . '"' - ); - } else { - $this->addHeader( - 'Content-Disposition', - 'attachment; filename*=UTF-8\'\'' . $encodedName . '; filepath="' . $encodedName . '"' - ); - } - } -} diff --git a/lib/file.php b/lib/file.php deleted file mode 100644 index 4668e569..00000000 --- a/lib/file.php +++ /dev/null @@ -1,200 +0,0 @@ -. - * - */ - -namespace OCA\Richdocuments; - -use \OC\Files\View; - -class File { - protected $fileId; - protected $owner; - protected $sharing; - protected $token; - protected $passwordProtected = false; - protected $ownerView; - protected $ownerViewFiles; - protected $path; - protected $pathFiles; - - public function __construct($fileId, $shareOps = null, $token = ''){ - if (!$fileId){ - throw new \Exception('No valid file has been passed'); - } - - $this->fileId = $fileId; - $this->sharing = $shareOps; - $this->token = $token; - - if ($this->isPublicShare()) { - if (isset($this->sharing['uid_owner'])){ - $this->owner = $this->sharing['uid_owner']; - if (!\OC::$server->getUserManager()->userExists($this->sharing['uid_owner'])) { - throw new \Exception('Share owner' . $this->sharing['uid_owner'] . ' does not exist '); - } - - \OC_Util::tearDownFS(); - \OC_Util::setupFS($this->sharing['uid_owner']); - } else { - throw new \Exception($this->fileId . ' is a broken share'); - } - } else { - $this->owner = \OC::$server->getUserSession()->getUser()->getUID(); - } - $this->initViews(); - } - - - public static function getByShareToken($token){ - $linkItem = \OCP\Share::getShareByToken($token, false); - if (is_array($linkItem) && isset($linkItem['uid_owner'])) { - // seems to be a valid share - $rootLinkItem = \OCP\Share::resolveReShare($linkItem); - } else { - throw new \Exception('This file was probably unshared'); - } - - $file = new File($rootLinkItem['file_source'], $rootLinkItem, $token); - - if (isset($linkItem['share_with']) && !empty($linkItem['share_with'])){ - $file->setPasswordProtected(true); - } - - return $file; - } - - public function getToken(){ - return $this->token; - } - - public function getFileId(){ - return $this->fileId; - } - - public function setToken($token){ - $this->token = $token; - } - - public function isPublicShare(){ - return !empty($this->token); - } - - public function isPasswordProtected(){ - return $this->passwordProtected; - } - - /** - * @param string $password - * @return boolean - */ - public function checkPassword($password){ - $shareId = $this->sharing['id']; - if (!$this->isPasswordProtected() - || (\OC::$server->getSession()->exists('public_link_authenticated') - && \OC::$server->getSession()->get('public_link_authenticated') === $shareId - ) - ){ - return true; - } - - // Check Password - $newHash = ''; - if(\OC::$server->getHasher()->verify($password, $this->getPassword(), $newHash)) { - \OC::$server->getSession()->set('public_link_authenticated', $shareId); - - /** - * 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 - */ - if(!empty($newHash)) { - - } - - return true; - } - return false; - } - - /** - * @param boolean $value - */ - public function setPasswordProtected($value){ - $this->passwordProtected = $value; - } - - public function getOwner(){ - return $this->owner; - } - - public function getOwnerView($relativeToFiles = false){ - return $relativeToFiles ? $this->ownerViewFiles : $this->ownerView; - } - - public function getPath($relativeToFiles = false){ - return $relativeToFiles ? $this->pathFiles : $this->path; - } - - public function getPermissions(){ - $fileInfo = $this->ownerView->getFileInfo($this->path); - return $fileInfo->getPermissions(); - } - - protected function initViews(){ - $this->ownerView = new View('/' . $this->owner); - $this->ownerViewFiles = new View('/' . $this->owner . '/files'); - $this->path = $this->ownerView->getPath($this->fileId); - $this->pathFiles = $this->ownerViewFiles->getPath($this->fileId); - - if (!$this->path || !$this->pathFiles) { - throw new \Exception($this->fileId . ' can not be resolved'); - } - - if (!$this->ownerView->file_exists($this->path)) { - throw new \Exception($this->path . ' doesn\'t exist'); - } - - if (!$this->ownerViewFiles->file_exists($this->pathFiles)) { - throw new \Exception($this->pathFiles . ' doesn\'t exist'); - } - - if (!$this->ownerView->is_file($this->path)){ - throw new \Exception('Object ' . $this->path . ' is not a file.'); - } - //TODO check if it is a valid odt - - $mimetype = $this->ownerView->getMimeType($this->path); - if (!Filter::isSupportedMimetype($mimetype)){ - throw new \Exception( $this->path . ' is ' . $mimetype . ' and is not supported by RichDocuments app'); - } - } - - protected function getPassword(){ - return $this->sharing['share_with']; - } -} diff --git a/lib/genesis.php b/lib/genesis.php deleted file mode 100644 index de0e0c87..00000000 --- a/lib/genesis.php +++ /dev/null @@ -1,97 +0,0 @@ -. - * - */ - -namespace OCA\Richdocuments; - -use \OC\Files\View; - -class Genesis { - - const DOCUMENTS_DIRNAME='/documents'; - - protected $view; - - protected $path; - - protected $hash; - - - /** - * Create new genesis document - * @param File $file - * */ - public function __construct(File $file){ - $view = $file->getOwnerView(); - $path = $file->getPath(); - $owner = $file->getOwner(); - - $this->view = new View('/' . $owner); - - if (!$this->view->file_exists(self::DOCUMENTS_DIRNAME)){ - $this->view->mkdir(self::DOCUMENTS_DIRNAME ); - } - $this->validate($view, $path); - - $this->hash = $view->hash('sha1', $path, false); - $this->path = self::DOCUMENTS_DIRNAME . '/' . $this->hash . '.odt'; - if (!$this->view->file_exists($this->path)){ - //copy new genesis to /user/documents/{hash}.odt - // get decrypted content - $content = $view->file_get_contents($path); - $mimetype = $view->getMimeType($path); - - $data = Filter::read($content, $mimetype); - $this->view->file_put_contents($this->path, $data['content']); - } - - try { - $this->validate($this->view, $this->path); - } catch (\Exception $e){ - throw new \Exception('Failed to copy genesis'); - } - } - - public function getPath(){ - return $this->path; - } - - public function getHash(){ - return $this->hash; - } - - /** - * Check if genesis is valid - * @param \OC\Files\View $view - * @param string $path relative to the view - * @throws \Exception - */ - protected function validate($view, $path){ - if (!$view->file_exists($path)){ - throw new \Exception('Document not found ' . $path); - } - if (!$view->is_file($path)){ - throw new \Exception('Object ' . $path . ' is not a file.'); - } - //TODO check if it is a valid odt - } - -} diff --git a/lib/helper.php b/lib/helper.php index db61a6ad..35866e8d 100644 --- a/lib/helper.php +++ b/lib/helper.php @@ -1,5 +1,4 @@ 360){ - $iH = 360; // 0-360 - } - if ($iS < 0){ - $iS = 0; // Saturation: - } - if ($iS > 100){ - $iS = 100; // 0-100 - } - if ($iV < 0){ - $iV = 0; // Lightness: - } - if ($iV > 100){ - $iV = 100; // 0-100 - } - - $dS = $iS / 100.0; // Saturation: 0.0-1.0 - $dV = $iV / 100.0; // Lightness: 0.0-1.0 - $dC = $dV * $dS; // Chroma: 0.0-1.0 - $dH = $iH / 60.0; // H-Prime: 0.0-6.0 - $dT = $dH; // Temp variable - - while ($dT >= 2.0) - $dT -= 2.0; // php modulus does not work with float - $dX = $dC * (1 - abs($dT - 1)); // as used in the Wikipedia link - - switch ($dH){ - case($dH >= 0.0 && $dH < 1.0): - $dR = $dC; - $dG = $dX; - $dB = 0.0; - break; - case($dH >= 1.0 && $dH < 2.0): - $dR = $dX; - $dG = $dC; - $dB = 0.0; - break; - case($dH >= 2.0 && $dH < 3.0): - $dR = 0.0; - $dG = $dC; - $dB = $dX; - break; - case($dH >= 3.0 && $dH < 4.0): - $dR = 0.0; - $dG = $dX; - $dB = $dC; - break; - case($dH >= 4.0 && $dH < 5.0): - $dR = $dX; - $dG = 0.0; - $dB = $dC; - break; - case($dH >= 5.0 && $dH < 6.0): - $dR = $dC; - $dG = 0.0; - $dB = $dX; - break; - default: - $dR = 0.0; - $dG = 0.0; - $dB = 0.0; - break; - } - - $dM = $dV - $dC; - $dR += $dM; - $dG += $dM; - $dB += $dM; - $dR *= 255; - $dG *= 255; - $dB *= 255; - - $dR = str_pad(dechex(round($dR)), 2, "0", STR_PAD_LEFT); - $dG = str_pad(dechex(round($dG)), 2, "0", STR_PAD_LEFT); - $dB = str_pad(dechex(round($dB)), 2, "0", STR_PAD_LEFT); - return $dR.$dG.$dB; - } - } diff --git a/lib/storage.php b/lib/storage.php index cf888ed9..fc450fa1 100644 --- a/lib/storage.php +++ b/lib/storage.php @@ -136,7 +136,6 @@ class Storage { } } - Db\Session::cleanUp($session->getEsId()); } private static function processDocuments($rawDocuments){ -- cgit v1.2.3