summaryrefslogtreecommitdiffstats
path: root/apps/dav/lib
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2015-12-16 17:35:53 +0100
committerRoeland Jago Douma <roeland@famdouma.nl>2016-10-24 21:45:00 +0200
commit59c5be1cc572793a8d50e87ab589e1cc4cf2ed12 (patch)
tree97b096fee075115bac45d69f2e0da7af5ffb41f2 /apps/dav/lib
parent4d01f23978549c2a33e9fdfdc3b9308cc9dc1078 (diff)
downloadnextcloud-server-59c5be1cc572793a8d50e87ab589e1cc4cf2ed12.tar.gz
nextcloud-server-59c5be1cc572793a8d50e87ab589e1cc4cf2ed12.zip
Use Webdav PUT for uploads in the web browser
- uses PUT method with jquery.fileupload for regular and public file lists - for IE and browsers that don't support it, use POST with iframe transport - implemented Sabre plugin to handle iframe transport and redirect the embedded PUT request to the proper handler - added RFC5995 POST to file collection with "add-member" property to make it possible to auto-rename conflicting file names - remove obsolete ajax/upload.php and obsolete ajax routes Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Diffstat (limited to 'apps/dav/lib')
-rw-r--r--apps/dav/lib/Connector/Sabre/FilesPlugin.php51
-rw-r--r--apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php188
-rw-r--r--apps/dav/lib/Connector/Sabre/ServerFactory.php1
3 files changed, 240 insertions, 0 deletions
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index aa5bacea5bb..39d15e0c6e9 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -46,6 +46,8 @@ use \Sabre\HTTP\ResponseInterface;
use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
use OCP\IRequest;
+use Sabre\DAV\Exception\BadRequest;
+use OCA\DAV\Connector\Sabre\Directory;
class FilesPlugin extends ServerPlugin {
@@ -170,6 +172,8 @@ class FilesPlugin extends ServerPlugin {
$this->server = $server;
$this->server->on('propFind', array($this, 'handleGetProperties'));
$this->server->on('propPatch', array($this, 'handleUpdateProperties'));
+ // RFC5995 to add file to the collection with a suggested name
+ $this->server->on('method:POST', [$this, 'httpPost']);
$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
$this->server->on('afterMethod:GET', [$this,'httpGet']);
@@ -432,4 +436,51 @@ class FilesPlugin extends ServerPlugin {
}
}
+ /**
+ * POST operation on directories to create a new file
+ * with suggested name
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ public function httpPost(RequestInterface $request, ResponseInterface $response) {
+ // TODO: move this to another plugin ?
+ if (!\OC::$CLI && !\OC::$server->getRequest()->passesCSRFCheck()) {
+ throw new BadRequest('Invalid CSRF token');
+ }
+
+ list($parentPath, $name) = \Sabre\HTTP\URLUtil::splitPath($request->getPath());
+
+ // Making sure the parent node exists and is a directory
+ $node = $this->tree->getNodeForPath($parentPath);
+
+ if ($node instanceof Directory) {
+ // no Add-Member found
+ if (empty($name) || $name[0] !== '&') {
+ // suggested name required
+ throw new BadRequest('Missing suggested file name');
+ }
+
+ $name = substr($name, 1);
+
+ if (empty($name)) {
+ // suggested name required
+ throw new BadRequest('Missing suggested file name');
+ }
+
+ // make sure the name is unique
+ $name = basename(\OC_Helper::buildNotExistingFileNameForView($parentPath, $name, $this->fileView));
+
+ $node->createFile($name, $request->getBodyAsStream());
+
+ list($parentUrl, ) = \Sabre\HTTP\URLUtil::splitPath($request->getUrl());
+
+ $response->setHeader('Content-Location', $parentUrl . '/' . rawurlencode($name));
+
+ // created
+ $response->setStatus(201);
+ return false;
+ }
+ }
}
diff --git a/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php b/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
new file mode 100644
index 00000000000..af6e5a62a5e
--- /dev/null
+++ b/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre;
+
+use Sabre\DAV\IFile;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\DAV\Exception\BadRequest;
+
+/**
+ * Plugin to receive Webdav PUT through POST,
+ * mostly used as a workaround for browsers that
+ * do not support PUT upload.
+ */
+class IFrameTransportPlugin extends \Sabre\DAV\ServerPlugin {
+
+ /**
+ * @var \Sabre\DAV\Server $server
+ */
+ private $server;
+
+ /**
+ * This initializes the plugin.
+ *
+ * @param \Sabre\DAV\Server $server
+ * @return void
+ */
+ public function initialize(\Sabre\DAV\Server $server) {
+ $this->server = $server;
+ $this->server->on('method:POST', [$this, 'handlePost']);
+ }
+
+ /**
+ * POST operation
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ public function handlePost(RequestInterface $request, ResponseInterface $response) {
+ try {
+ return $this->processUpload($request, $response);
+ } catch (\Sabre\DAV\Exception $e) {
+ $response->setStatus($e->getHTTPCode());
+ $response->setBody(['message' => $e->getMessage()]);
+ $this->convertResponse($response);
+ return false;
+ }
+ }
+
+ /**
+ * Wrap and send response in JSON format
+ *
+ * @param ResponseInterface $response response object
+ */
+ private function convertResponse(ResponseInterface $response) {
+ if (is_resource($response->getBody())) {
+ throw new BadRequest('Cannot request binary data with iframe transport');
+ }
+
+ $responseData = json_encode([
+ 'status' => $response->getStatus(),
+ 'headers' => $response->getHeaders(),
+ 'data' => $response->getBody(),
+ ]);
+
+ // IE needs this content type
+ $response->setHeader('Content-Type', 'text/plain');
+ $response->setHeader('Content-Length', strlen($responseData));
+ $response->setStatus(200);
+ $response->setBody($responseData);
+ }
+
+ /**
+ * Process upload
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ private function processUpload(RequestInterface $request, ResponseInterface $response) {
+ $queryParams = $request->getQueryParameters();
+
+ if (!isset($queryParams['_method'])) {
+ return null;
+ }
+
+ $method = $queryParams['_method'];
+ if ($method !== 'PUT' && $method !== 'POST') {
+ return null;
+ }
+
+ $contentType = $request->getHeader('Content-Type');
+ list($contentType) = explode(';', $contentType);
+ if ($contentType !== 'application/x-www-form-urlencoded'
+ && $contentType !== 'multipart/form-data'
+ ) {
+ return null;
+ }
+
+ if (!isset($_FILES['files'])) {
+ return null;
+ }
+
+ // TODO: move this to another plugin ?
+ if (!\OC::$CLI && !\OC::$server->getRequest()->passesCSRFCheck()) {
+ throw new BadRequest('Invalid CSRF token');
+ }
+
+ if ($_FILES) {
+ $file = current($_FILES);
+ } else {
+ return null;
+ }
+
+ if ($file['error'][0] !== 0) {
+ throw new BadRequest('Error during upload, code ' . $file['error'][0]);
+ }
+
+ if (!\OC::$CLI && !is_uploaded_file($file['tmp_name'][0])) {
+ return null;
+ }
+
+ if (count($file['tmp_name']) > 1) {
+ throw new BadRequest('Only a single file can be uploaded');
+ }
+
+ $postData = $request->getPostData();
+ if (isset($postData['headers'])) {
+ $headers = json_decode($postData['headers'], true);
+
+ // copy safe headers into the request
+ $allowedHeaders = [
+ 'If',
+ 'If-Match',
+ 'If-None-Match',
+ 'If-Modified-Since',
+ 'If-Unmodified-Since',
+ 'Authorization',
+ ];
+
+ foreach ($allowedHeaders as $allowedHeader) {
+ if (isset($headers[$allowedHeader])) {
+ $request->setHeader($allowedHeader, $headers[$allowedHeader]);
+ }
+ }
+ }
+
+ // MEGAHACK, because the Sabre File impl reads this property directly
+ $_SERVER['CONTENT_LENGTH'] = $file['size'][0];
+ $request->setHeader('Content-Length', $file['size'][0]);
+
+ $tmpFile = $file['tmp_name'][0];
+ $resource = fopen($tmpFile, 'r');
+
+ $request->setBody($resource);
+ $request->setMethod($method);
+
+ $this->server->invokeMethod($request, $response, false);
+
+ fclose($resource);
+ unlink($tmpFile);
+
+ $this->convertResponse($response);
+
+ return false;
+ }
+
+}
diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index 6d9f9b1bc8b..da541abc199 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -114,6 +114,7 @@ class ServerFactory {
// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
$server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger));
+ $server->addPlugin(new \OCA\DAV\Connector\Sabre\IFrameTransportPlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
// Some WebDAV clients do require Class 2 WebDAV support (locking), since
// we do not provide locking we emulate it using a fake locking plugin.