From 2c3d9ae8d984ec985b2607a25b9f53243a55c042 Mon Sep 17 00:00:00 2001 From: Pranav Kant Date: Wed, 22 Nov 2017 20:53:55 +0530 Subject: [PATCH] Add support for PutRelativeFile for Save As. (#136) --- appinfo/routes.php | 1 + js/documents.js | 20 +++++++ lib/Controller/WopiController.php | 96 +++++++++++++++++++++++++++---- lib/TokenManager.php | 7 ++- 4 files changed, 112 insertions(+), 12 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index a92dc45d..604f335f 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -25,6 +25,7 @@ return [ ['name' => 'wopi#checkFileInfo', 'url' => 'wopi/files/{fileId}', 'verb' => 'GET'], ['name' => 'wopi#getFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'GET'], ['name' => 'wopi#putFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'POST'], + ['name' => 'wopi#putRelativeFile', 'url' => 'wopi/files/{fileId}', 'verb' => 'POST'], //settings ['name' => 'settings#setSettings', 'url' => 'ajax/admin.php', 'verb' => 'POST'], diff --git a/js/documents.js b/js/documents.js index 709c6b45..925b2f60 100644 --- a/js/documents.js +++ b/js/documents.js @@ -373,6 +373,26 @@ var documentsMain = { return; documentsMain.UI.showRevHistory(documentsMain.fullPath); + } else if (msgId === 'UI_SaveAs') { + // TODO it's not possible to enter the + // filename into the OC.dialogs.filepicker; so + // it will be necessary to use an own tree + // view or something :-( + //OC.dialogs.filepicker(t('richdocuments', 'Save As'), + // function(val) { + // console.log(val); + // documentsMain.WOPIPostMessage($('#loleafletframe')[0], Action_SaveAs', {'Filename': val}); + // }, false, null, true); + OC.dialogs.prompt(t('richdocuments', 'Please enter filename to which this document should be stored.'), + t('richdocuments', 'Save As'), + function(result, value) { + if (result === true) { + documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_SaveAs', {'Filename': value}); + } + }, + true, + t('richdocuments', 'New filename'), + false); } }); diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php index 69a216ef..865db26d 100644 --- a/lib/Controller/WopiController.php +++ b/lib/Controller/WopiController.php @@ -22,6 +22,7 @@ namespace OCA\Richdocuments\Controller; use OC\Files\View; +use OCA\Richdocuments\TokenManager; use OCA\Richdocuments\Db\Wopi; use OCA\Richdocuments\Helper; use OCP\AppFramework\Controller; @@ -42,6 +43,8 @@ class WopiController extends Controller { private $urlGenerator; /** @var IConfig */ private $config; + /** @var ITokenManager */ + private $tokenManager; /** @var IUserManager */ private $userManager; @@ -55,6 +58,7 @@ class WopiController extends Controller { * @param IRootFolder $rootFolder * @param IURLGenerator $urlGenerator * @param IConfig $config + * @param ITokenManager $tokenManager * @param IUserManager $userManager */ public function __construct($appName, @@ -63,11 +67,13 @@ class WopiController extends Controller { IRootFolder $rootFolder, IURLGenerator $urlGenerator, IConfig $config, + TokenManager $tokenManager, IUserManager $userManager) { parent::__construct($appName, $request); $this->rootFolder = $rootFolder; $this->urlGenerator = $urlGenerator; $this->config = $config; + $this->tokenManager = $tokenManager; $this->userManager = $userManager; } @@ -114,6 +120,7 @@ class WopiController extends Controller { 'UserExtraInfo' => [ ], 'UserCanWrite' => $res['canwrite'] ? true : false, + 'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() ? true : false, 'PostMessageOrigin' => $res['server_host'], 'LastModifiedTime' => Helper::toISO8601($file->getMtime()) ]; @@ -192,6 +199,7 @@ class WopiController extends Controller { public function putFile($fileId, $access_token) { list($fileId, , $version) = Helper::parseFileId($fileId); + $isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE'); $row = new Wopi(); $row->loadBy('token', $access_token); @@ -201,21 +209,55 @@ class WopiController extends Controller { return new JSONResponse([], Http::STATUS_FORBIDDEN); } - - try { /** @var File $file */ $userFolder = $this->rootFolder->getUserFolder($res['owner']); $file = $userFolder->getById($fileId)[0]; - $wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp'); - if (!is_null($wopiHeaderTime) && $wopiHeaderTime != Helper::toISO8601($file->getMTime())) { - \OC::$server->getLogger()->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [ - 'headerTime' => $wopiHeaderTime, - 'storageTime' => Helper::toISO8601($file->getMtime()) - ]); - // Tell WOPI client about this conflict. - return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT); + if ($isPutRelative) { + $suggested = $this->request->getHeader('X-WOPI-SuggestedTarget'); + $suggested = iconv('utf-7', 'utf-8', $suggested); + + $path = ''; + if ($suggested[0] === '.') { + $path = dirname($file->getPath()) . '/New File' . $suggested; + } + else if ($suggested[0] !== '/') { + $path = dirname($file->getPath()) . '/' . $suggested; + } + else { + $path = $userFolder->getPath() . $suggested; + } + + if ($path === '') { + return array( + 'status' => 'error', + 'message' => 'Cannot create the file' + ); + } + + $root = \OC::$server->getRootFolder(); + + // create the folder first + if (!$root->nodeExists(dirname($path))) { + $root->newFolder(dirname($path)); + } + + // create a unique new file + $path = $root->getNonExistingName($path); + $root->newFile($path); + $file = $root->get($path); + } + else { + $wopiHeaderTime = $this->request->getHeader('X-LOOL-WOPI-Timestamp'); + if (!is_null($wopiHeaderTime) && $wopiHeaderTime != Helper::toISO8601($file->getMTime())) { + \OC::$server->getLogger()->debug('Document timestamp mismatch ! WOPI client says mtime {headerTime} but storage says {storageTime}', [ + 'headerTime' => $wopiHeaderTime, + 'storageTime' => Helper::toISO8601($file->getMtime()) + ]); + // Tell WOPI client about this conflict. + return new JSONResponse(['LOOLStatusCode' => self::LOOL_STATUS_DOC_CHANGED], Http::STATUS_CONFLICT); + } } $content = fopen('php://input', 'rb'); @@ -230,9 +272,41 @@ class WopiController extends Controller { } $file->putContent($content); - return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMtime())]); + + if ($isPutRelative) { + // generate a token for the new file (the user still has to be + // logged in) + $serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost(); + list(, $wopiToken) = $this->tokenManager->getToken($file->getId()); + + $wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken; + $url = \OC::$server->getURLGenerator()->getAbsoluteURL($wopi); + + return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK); + } + else { + return new JSONResponse(['LastModifiedTime' => Helper::toISO8601($file->getMtime())]); + } } catch (\Exception $e) { return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); } } + + /** + * Given an access token and a fileId, replaces the files with the request body. + * Expects a valid token in access_token parameter. + * Just actually routes to the PutFile, the implementation of PutFile + * handles both saving and saving as.* Given an access token and a fileId, replaces the files with the request body. + * + * @PublicPage + * @NoCSRFRequired + * + * @param string $fileId + * @param string $access_token + * @return JSONResponse + */ + public function putRelativeFile($fileId, + $access_token) { + return $this->putFile($fileId, $access_token); + } } diff --git a/lib/TokenManager.php b/lib/TokenManager.php index 38fbe2a5..2b12aee6 100644 --- a/lib/TokenManager.php +++ b/lib/TokenManager.php @@ -76,7 +76,7 @@ class TokenManager { $share = $this->shareManager->getShareByToken($shareToken); $updatable = (bool)($share->getPermissions() & \OCP\Constants::PERMISSION_UPDATE); $owneruid = $share->getShareOwner(); - } else { + } else if (!is_null($this->userId)) { try { /** @var File $file */ $rootFolder = $this->rootFolder->getUserFolder($this->userId); @@ -100,6 +100,11 @@ class TokenManager { } catch (\Exception $e) { throw $e; } + } else { + // no active user login while generating the token + // this is required during WopiPutRelativeFile + $rootFolder = $this->rootFolder; + $updatable = true; } /** @var File $file */ $file = $rootFolder->getById($fileId)[0];