mirror of
https://github.com/nextcloud/server.git
synced 2025-03-16 17:24:10 +00:00
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>
This commit is contained in:
parent
4d01f23978
commit
59c5be1cc5
16 changed files with 1516 additions and 802 deletions
apps
dav
lib/Connector/Sabre
tests/unit/Connector/Sabre
files
ajax
appinfo
js
templates
tests/js
files_sharing
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
188
apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
Normal file
188
apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -123,7 +123,7 @@ class FilesPluginTest extends TestCase {
|
|||
* @param string $class
|
||||
* @return \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private function createTestNode($class) {
|
||||
private function createTestNode($class, $path = '/dummypath') {
|
||||
$node = $this->getMockBuilder($class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
@ -134,7 +134,7 @@ class FilesPluginTest extends TestCase {
|
|||
|
||||
$this->tree->expects($this->any())
|
||||
->method('getNodeForPath')
|
||||
->with('/dummypath')
|
||||
->with($path)
|
||||
->will($this->returnValue($node));
|
||||
|
||||
$node->expects($this->any())
|
||||
|
@ -547,4 +547,85 @@ class FilesPluginTest extends TestCase {
|
|||
|
||||
$this->assertEquals("false", $propFind->get(self::HAS_PREVIEW_PROPERTYNAME));
|
||||
}
|
||||
|
||||
public function postCreateFileProvider() {
|
||||
$baseUrl = 'http://example.com/owncloud/remote.php/webdav/subdir/';
|
||||
return [
|
||||
['test.txt', 'some file.txt', 'some file.txt', $baseUrl . 'some%20file.txt'],
|
||||
['some file.txt', 'some file.txt', 'some file (2).txt', $baseUrl . 'some%20file%20%282%29.txt'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider postCreateFileProvider
|
||||
*/
|
||||
public function testPostWithAddMember($existingFile, $wantedName, $deduplicatedName, $expectedLocation) {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('getUrl')
|
||||
->will($this->returnValue('http://example.com/owncloud/remote.php/webdav/subdir/&' . $wantedName));
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('getPath')
|
||||
->will($this->returnValue('/subdir/&' . $wantedName));
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getBodyAsStream')
|
||||
->will($this->returnValue(fopen('data://text/plain,hello', 'r')));
|
||||
|
||||
$this->view->expects($this->any())
|
||||
->method('file_exists')
|
||||
->will($this->returnCallback(function($path) use ($existingFile) {
|
||||
return ($path === '/subdir/' . $existingFile);
|
||||
}));
|
||||
|
||||
$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory', '/subdir');
|
||||
|
||||
$node->expects($this->once())
|
||||
->method('createFile')
|
||||
->with($deduplicatedName, $this->isType('resource'));
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('setStatus')
|
||||
->with(201);
|
||||
$response->expects($this->once())
|
||||
->method('setHeader')
|
||||
->with('Content-Location', $expectedLocation);
|
||||
|
||||
$this->assertFalse($this->plugin->httpPost($request, $response));
|
||||
}
|
||||
|
||||
public function testPostOnNonDirectory() {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('getPath')
|
||||
->will($this->returnValue('/subdir/test.txt/&abc'));
|
||||
|
||||
$this->createTestNode('\OCA\DAV\Connector\Sabre\File', '/subdir/test.txt');
|
||||
|
||||
$this->assertNull($this->plugin->httpPost($request, $response));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Sabre\DAV\Exception\BadRequest
|
||||
*/
|
||||
public function testPostWithoutAddMember() {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('getPath')
|
||||
->will($this->returnValue('/subdir/&'));
|
||||
|
||||
$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory', '/subdir');
|
||||
|
||||
$node->expects($this->never())
|
||||
->method('createFile');
|
||||
|
||||
$this->plugin->httpPost($request, $response);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
class IFrameTransportPluginTest extends \Test\TestCase {
|
||||
|
||||
/**
|
||||
* @var \Sabre\DAV\Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* @var \OCA\DAV\Connector\Sabre\IFrameTransportPlugin
|
||||
*/
|
||||
private $plugin;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->server = $this->getMockBuilder('\Sabre\DAV\Server')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->plugin = new \OCA\DAV\Connector\Sabre\IFrameTransportPlugin();
|
||||
$this->plugin->initialize($this->server);
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$_FILES = null;
|
||||
unset($_SERVER['CONTENT_LENGTH']);
|
||||
}
|
||||
|
||||
public function testPutConversion() {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getQueryParameters')
|
||||
->will($this->returnValue(['_method' => 'PUT']));
|
||||
|
||||
$postData = [
|
||||
'headers' => json_encode([
|
||||
'If-None-Match' => '*',
|
||||
'Disallowed-Header' => 'test',
|
||||
]),
|
||||
];
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getPostData')
|
||||
->will($this->returnValue($postData));
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getHeader')
|
||||
->with('Content-Type')
|
||||
->will($this->returnValue('multipart/form-data'));
|
||||
|
||||
$tmpFileName = tempnam(sys_get_temp_dir(), 'tmpfile');
|
||||
$fh = fopen($tmpFileName, 'w');
|
||||
fwrite($fh, 'hello');
|
||||
fclose($fh);
|
||||
|
||||
$_FILES = ['files' => [
|
||||
'error' => [0],
|
||||
'tmp_name' => [$tmpFileName],
|
||||
'size' => [5],
|
||||
]];
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('setHeader')
|
||||
->withConsecutive(
|
||||
['If-None-Match', '*'],
|
||||
['Content-Length', 5]
|
||||
);
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('setMethod')
|
||||
->with('PUT');
|
||||
|
||||
$this->server->expects($this->once())
|
||||
->method('invokeMethod')
|
||||
->with($request, $response);
|
||||
|
||||
// response data before conversion
|
||||
$response->expects($this->once())
|
||||
->method('getHeaders')
|
||||
->will($this->returnValue(['Test-Response-Header' => [123]]));
|
||||
|
||||
$response->expects($this->any())
|
||||
->method('getBody')
|
||||
->will($this->returnValue('test'));
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getStatus')
|
||||
->will($this->returnValue(201));
|
||||
|
||||
$responseBody = json_encode([
|
||||
'status' => 201,
|
||||
'headers' => ['Test-Response-Header' => [123]],
|
||||
'data' => 'test',
|
||||
]);
|
||||
|
||||
// response data after conversion
|
||||
$response->expects($this->once())
|
||||
->method('setBody')
|
||||
->with($responseBody);
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('setStatus')
|
||||
->with(200);
|
||||
|
||||
$response->expects($this->any())
|
||||
->method('setHeader')
|
||||
->withConsecutive(
|
||||
['Content-Type', 'text/plain'],
|
||||
['Content-Length', strlen($responseBody)]
|
||||
);
|
||||
|
||||
$this->assertFalse($this->plugin->handlePost($request, $response));
|
||||
|
||||
$this->assertEquals(5, $_SERVER['CONTENT_LENGTH']);
|
||||
|
||||
$this->assertFalse(file_exists($tmpFileName));
|
||||
}
|
||||
|
||||
public function testIgnoreNonPut() {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getQueryParameters')
|
||||
->will($this->returnValue(['_method' => 'PROPFIND']));
|
||||
|
||||
$this->server->expects($this->never())
|
||||
->method('invokeMethod')
|
||||
->with($request, $response);
|
||||
|
||||
$this->assertNull($this->plugin->handlePost($request, $response));
|
||||
}
|
||||
|
||||
public function testIgnoreMismatchedContentType() {
|
||||
$request = $this->getMock('Sabre\HTTP\RequestInterface');
|
||||
$response = $this->getMock('Sabre\HTTP\ResponseInterface');
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getQueryParameters')
|
||||
->will($this->returnValue(['_method' => 'PUT']));
|
||||
|
||||
$request->expects($this->once())
|
||||
->method('getHeader')
|
||||
->with('Content-Type')
|
||||
->will($this->returnValue('text/plain'));
|
||||
|
||||
$this->server->expects($this->never())
|
||||
->method('invokeMethod')
|
||||
->with($request, $response);
|
||||
|
||||
$this->assertNull($this->plugin->handlePost($request, $response));
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
* @author Bart Visscher <bartv@thisnet.nl>
|
||||
* @author Björn Schießle <bjoern@schiessle.org>
|
||||
* @author Clark Tomlinson <fallen013@gmail.com>
|
||||
* @author Florian Pritz <bluewind@xinu.at>
|
||||
* @author Frank Karlitschek <frank@karlitschek.de>
|
||||
* @author Individual IT Services <info@individual-it.net>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Luke Policinski <lpolicinski@gmail.com>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roman Geber <rgeber@owncloudapps.com>
|
||||
* @author TheSFReader <TheSFReader@gmail.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
// Firefox and Konqueror tries to download application/json for me. --Arthur
|
||||
OCP\JSON::setContentTypeHeader('text/plain');
|
||||
|
||||
// If a directory token is sent along check if public upload is permitted.
|
||||
// If not, check the login.
|
||||
// If no token is sent along, rely on login only
|
||||
|
||||
$errorCode = null;
|
||||
$errorFileName = null;
|
||||
|
||||
$l = \OC::$server->getL10N('files');
|
||||
if (empty($_POST['dirToken'])) {
|
||||
// The standard case, files are uploaded through logged in users :)
|
||||
OCP\JSON::checkLoggedIn();
|
||||
$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
|
||||
if (!$dir || empty($dir) || $dir === false) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
|
||||
die();
|
||||
}
|
||||
} else {
|
||||
// TODO: ideally this code should be in files_sharing/ajax/upload.php
|
||||
// and the upload/file transfer code needs to be refactored into a utility method
|
||||
// that could be used there
|
||||
|
||||
\OC_User::setIncognitoMode(true);
|
||||
|
||||
$publicDirectory = !empty($_POST['subdir']) ? (string)$_POST['subdir'] : '/';
|
||||
|
||||
$linkItem = OCP\Share::getShareByToken((string)$_POST['dirToken']);
|
||||
if ($linkItem === false) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Invalid Token')))));
|
||||
die();
|
||||
}
|
||||
|
||||
if (!($linkItem['permissions'] & \OCP\Constants::PERMISSION_CREATE)) {
|
||||
OCP\JSON::checkLoggedIn();
|
||||
} else {
|
||||
// resolve reshares
|
||||
$rootLinkItem = OCP\Share::resolveReShare($linkItem);
|
||||
|
||||
OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
|
||||
// Setup FS with owner
|
||||
OC_Util::tearDownFS();
|
||||
OC_Util::setupFS($rootLinkItem['uid_owner']);
|
||||
|
||||
// The token defines the target directory (security reasons)
|
||||
$path = \OC\Files\Filesystem::getPath($linkItem['file_source']);
|
||||
if($path === null) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
|
||||
die();
|
||||
}
|
||||
$dir = sprintf(
|
||||
"/%s/%s",
|
||||
$path,
|
||||
$publicDirectory
|
||||
);
|
||||
|
||||
if (!$dir || empty($dir) || $dir === false) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
|
||||
die();
|
||||
}
|
||||
|
||||
$dir = rtrim($dir, '/');
|
||||
}
|
||||
}
|
||||
|
||||
OCP\JSON::callCheck();
|
||||
|
||||
// get array with current storage stats (e.g. max file size)
|
||||
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
|
||||
|
||||
if (!isset($_FILES['files'])) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('No file was uploaded. Unknown error')), $storageStats)));
|
||||
exit();
|
||||
}
|
||||
|
||||
foreach ($_FILES['files']['error'] as $error) {
|
||||
if ($error != 0) {
|
||||
$errors = array(
|
||||
UPLOAD_ERR_OK => $l->t('There is no error, the file uploaded with success'),
|
||||
UPLOAD_ERR_INI_SIZE => $l->t('The uploaded file exceeds the upload_max_filesize directive in php.ini: ')
|
||||
. OC::$server->getIniWrapper()->getNumeric('upload_max_filesize'),
|
||||
UPLOAD_ERR_FORM_SIZE => $l->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
|
||||
UPLOAD_ERR_PARTIAL => $l->t('The uploaded file was only partially uploaded'),
|
||||
UPLOAD_ERR_NO_FILE => $l->t('No file was uploaded'),
|
||||
UPLOAD_ERR_NO_TMP_DIR => $l->t('Missing a temporary folder'),
|
||||
UPLOAD_ERR_CANT_WRITE => $l->t('Failed to write to disk'),
|
||||
);
|
||||
$errorMessage = $errors[$error];
|
||||
\OC::$server->getLogger()->alert("Upload error: $error - $errorMessage", array('app' => 'files'));
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $errorMessage), $storageStats)));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
$files = $_FILES['files'];
|
||||
|
||||
$error = false;
|
||||
|
||||
$maxUploadFileSize = $storageStats['uploadMaxFilesize'];
|
||||
$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
|
||||
|
||||
$totalSize = 0;
|
||||
$isReceivedShare = \OC::$server->getRequest()->getParam('isReceivedShare', false) === 'true';
|
||||
// defer quota check for received shares
|
||||
if (!$isReceivedShare && $storageStats['freeSpace'] >= 0) {
|
||||
foreach ($files['size'] as $size) {
|
||||
$totalSize += $size;
|
||||
}
|
||||
}
|
||||
if ($maxUploadFileSize >= 0 and $totalSize > $maxUploadFileSize) {
|
||||
OCP\JSON::error(array('data' => array('message' => $l->t('Not enough storage available'),
|
||||
'uploadMaxFilesize' => $maxUploadFileSize,
|
||||
'maxHumanFilesize' => $maxHumanFileSize)));
|
||||
exit();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
if (\OC\Files\Filesystem::isValidPath($dir) === true) {
|
||||
$fileCount = count($files['name']);
|
||||
for ($i = 0; $i < $fileCount; $i++) {
|
||||
|
||||
if (isset($_POST['resolution'])) {
|
||||
$resolution = $_POST['resolution'];
|
||||
} else {
|
||||
$resolution = null;
|
||||
}
|
||||
|
||||
if(isset($_POST['dirToken'])) {
|
||||
// If it is a read only share the resolution will always be autorename
|
||||
$shareManager = \OC::$server->getShareManager();
|
||||
$share = $shareManager->getShareByToken((string)$_POST['dirToken']);
|
||||
if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
|
||||
$resolution = 'autorename';
|
||||
}
|
||||
}
|
||||
|
||||
// target directory for when uploading folders
|
||||
$relativePath = '';
|
||||
if(!empty($_POST['file_directory'])) {
|
||||
$relativePath = '/'.$_POST['file_directory'];
|
||||
}
|
||||
|
||||
// $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder
|
||||
if ($resolution === 'autorename') {
|
||||
// append a number in brackets like 'filename (2).ext'
|
||||
$target = OCP\Files::buildNotExistingFileName($dir . $relativePath, $files['name'][$i]);
|
||||
} else {
|
||||
$target = \OC\Files\Filesystem::normalizePath($dir . $relativePath.'/'.$files['name'][$i]);
|
||||
}
|
||||
|
||||
// relative dir to return to the client
|
||||
if (isset($publicDirectory)) {
|
||||
// path relative to the public root
|
||||
$returnedDir = $publicDirectory . $relativePath;
|
||||
} else {
|
||||
// full path
|
||||
$returnedDir = $dir . $relativePath;
|
||||
}
|
||||
$returnedDir = \OC\Files\Filesystem::normalizePath($returnedDir);
|
||||
|
||||
|
||||
$exists = \OC\Files\Filesystem::file_exists($target);
|
||||
if ($exists) {
|
||||
$updatable = \OC\Files\Filesystem::isUpdatable($target);
|
||||
}
|
||||
if ( ! $exists || ($updatable && $resolution === 'replace' ) ) {
|
||||
// upload and overwrite file
|
||||
try
|
||||
{
|
||||
if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
|
||||
|
||||
// updated max file size after upload
|
||||
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
|
||||
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
if ($meta === false) {
|
||||
$error = $l->t('The target folder has been moved or deleted.');
|
||||
$errorCode = 'targetnotfound';
|
||||
} else {
|
||||
$data = \OCA\Files\Helper::formatFileInfo($meta);
|
||||
$data['status'] = 'success';
|
||||
$data['originalname'] = $files['name'][$i];
|
||||
$data['uploadMaxFilesize'] = $maxUploadFileSize;
|
||||
$data['maxHumanFilesize'] = $maxHumanFileSize;
|
||||
$data['permissions'] = $meta['permissions'];
|
||||
$data['directory'] = $returnedDir;
|
||||
$result[] = $data;
|
||||
}
|
||||
|
||||
} else {
|
||||
$error = $l->t('Upload failed. Could not find uploaded file');
|
||||
$errorFileName = $files['name'][$i];
|
||||
}
|
||||
} catch(Exception $ex) {
|
||||
$error = $ex->getMessage();
|
||||
}
|
||||
|
||||
} else {
|
||||
// file already exists
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
if ($meta === false) {
|
||||
$error = $l->t('Upload failed. Could not get file info.');
|
||||
} else {
|
||||
$data = \OCA\Files\Helper::formatFileInfo($meta);
|
||||
if ($updatable) {
|
||||
$data['status'] = 'existserror';
|
||||
} else {
|
||||
$data['status'] = 'readonly';
|
||||
}
|
||||
$data['originalname'] = $files['name'][$i];
|
||||
$data['uploadMaxFilesize'] = $maxUploadFileSize;
|
||||
$data['maxHumanFilesize'] = $maxHumanFileSize;
|
||||
$data['permissions'] = $meta['permissions'];
|
||||
$data['directory'] = $returnedDir;
|
||||
$result[] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error = $l->t('Invalid directory.');
|
||||
}
|
||||
|
||||
if ($error === false) {
|
||||
// Do not leak file information if it is a read-only share
|
||||
if(isset($_POST['dirToken'])) {
|
||||
$shareManager = \OC::$server->getShareManager();
|
||||
$share = $shareManager->getShareByToken((string)$_POST['dirToken']);
|
||||
if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
|
||||
$newResults = [];
|
||||
foreach($result as $singleResult) {
|
||||
$fileName = $singleResult['originalname'];
|
||||
$newResults['filename'] = $fileName;
|
||||
$newResults['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileName);
|
||||
}
|
||||
$result = $newResults;
|
||||
}
|
||||
}
|
||||
OCP\JSON::encodedPrint($result);
|
||||
} else {
|
||||
OCP\JSON::error(array(array('data' => array_merge(array(
|
||||
'message' => $error,
|
||||
'code' => $errorCode,
|
||||
'filename' => $errorFileName
|
||||
), $storageStats))));
|
||||
}
|
|
@ -75,24 +75,12 @@ $application->registerRoutes(
|
|||
|
||||
/** @var $this \OC\Route\Router */
|
||||
|
||||
$this->create('files_ajax_delete', 'ajax/delete.php')
|
||||
->actionInclude('files/ajax/delete.php');
|
||||
$this->create('files_ajax_download', 'ajax/download.php')
|
||||
->actionInclude('files/ajax/download.php');
|
||||
$this->create('files_ajax_getstoragestats', 'ajax/getstoragestats.php')
|
||||
->actionInclude('files/ajax/getstoragestats.php');
|
||||
$this->create('files_ajax_list', 'ajax/list.php')
|
||||
->actionInclude('files/ajax/list.php');
|
||||
$this->create('files_ajax_move', 'ajax/move.php')
|
||||
->actionInclude('files/ajax/move.php');
|
||||
$this->create('files_ajax_newfile', 'ajax/newfile.php')
|
||||
->actionInclude('files/ajax/newfile.php');
|
||||
$this->create('files_ajax_newfolder', 'ajax/newfolder.php')
|
||||
->actionInclude('files/ajax/newfolder.php');
|
||||
$this->create('files_ajax_rename', 'ajax/rename.php')
|
||||
->actionInclude('files/ajax/rename.php');
|
||||
$this->create('files_ajax_upload', 'ajax/upload.php')
|
||||
->actionInclude('files/ajax/upload.php');
|
||||
|
||||
$this->create('download', 'download{file}')
|
||||
->requirements(array('file' => '.*'))
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
direction: $('#defaultFileSortingDirection').val()
|
||||
},
|
||||
config: this._filesConfig,
|
||||
enableUpload: true
|
||||
}
|
||||
);
|
||||
this.files.initialize();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,7 @@
|
|||
* @param {Object} [options.dragOptions] drag options, disabled by default
|
||||
* @param {Object} [options.folderDropOptions] folder drop options, disabled by default
|
||||
* @param {boolean} [options.detailsViewEnabled=true] whether to enable details view
|
||||
* @param {boolean} [options.enableUpload=false] whether to enable uploader
|
||||
* @param {OC.Files.Client} [options.filesClient] files client to use
|
||||
*/
|
||||
var FileList = function($el, options) {
|
||||
|
@ -188,6 +189,11 @@
|
|||
_dragOptions: null,
|
||||
_folderDropOptions: null,
|
||||
|
||||
/**
|
||||
* @type OC.Uploader
|
||||
*/
|
||||
_uploader: null,
|
||||
|
||||
/**
|
||||
* Initialize the file list and its components
|
||||
*
|
||||
|
@ -328,8 +334,6 @@
|
|||
|
||||
this.$el.find('.selectedActions a').tooltip({placement:'top'});
|
||||
|
||||
this.setupUploadEvents();
|
||||
|
||||
this.$container.on('scroll', _.bind(this._onScroll, this));
|
||||
|
||||
if (options.scrollTo) {
|
||||
|
@ -338,6 +342,20 @@
|
|||
});
|
||||
}
|
||||
|
||||
if (options.enableUpload) {
|
||||
// TODO: auto-create this element
|
||||
var $uploadEl = this.$el.find('#file_upload_start');
|
||||
if ($uploadEl.exists()) {
|
||||
this._uploader = new OC.Uploader($uploadEl, {
|
||||
fileList: this,
|
||||
filesClient: this.filesClient,
|
||||
dropZone: $('#content')
|
||||
});
|
||||
|
||||
this.setupUploadEvents(this._uploader);
|
||||
}
|
||||
}
|
||||
|
||||
OC.Plugins.attach('OCA.Files.FileList', this);
|
||||
},
|
||||
|
||||
|
@ -1420,7 +1438,10 @@
|
|||
return;
|
||||
}
|
||||
this._setCurrentDir(targetDir, changeUrl, fileId);
|
||||
return this.reload().then(function(success){
|
||||
|
||||
// discard finished uploads list, we'll get it through a regular reload
|
||||
this._uploads = {};
|
||||
this.reload().then(function(success){
|
||||
if (!success) {
|
||||
self.changeDirectory(currentDir, true);
|
||||
}
|
||||
|
@ -1660,6 +1681,24 @@
|
|||
return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory(), isDir);
|
||||
},
|
||||
|
||||
getUploadUrl: function(fileName, dir) {
|
||||
if (_.isUndefined(dir)) {
|
||||
dir = this.getCurrentDirectory();
|
||||
}
|
||||
|
||||
var pathSections = dir.split('/');
|
||||
if (!_.isUndefined(fileName)) {
|
||||
pathSections.push(fileName);
|
||||
}
|
||||
var encodedPath = '';
|
||||
_.each(pathSections, function(section) {
|
||||
if (section !== '') {
|
||||
encodedPath += '/' + encodeURIComponent(section);
|
||||
}
|
||||
});
|
||||
return OC.linkToRemoteBase('webdav') + encodedPath;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a preview URL based on the URL space.
|
||||
* @param urlSpec attributes for the URL
|
||||
|
@ -2121,19 +2160,11 @@
|
|||
)
|
||||
.done(function() {
|
||||
// TODO: error handling / conflicts
|
||||
self.filesClient.getFileInfo(
|
||||
targetPath, {
|
||||
properties: self._getWebdavProperties()
|
||||
}
|
||||
)
|
||||
.then(function(status, data) {
|
||||
self.add(data, {animate: true, scrollTo: true});
|
||||
deferred.resolve(status, data);
|
||||
})
|
||||
.fail(function(status) {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
|
||||
deferred.reject(status);
|
||||
});
|
||||
self.addAndFetchFileInfo(targetPath, '', {scrollTo: true}).then(function(status, data) {
|
||||
deferred.resolve(status, data);
|
||||
}, function() {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
|
||||
});
|
||||
})
|
||||
.fail(function(status) {
|
||||
if (status === 412) {
|
||||
|
@ -2174,32 +2205,19 @@
|
|||
var targetPath = this.getCurrentDirectory() + '/' + name;
|
||||
|
||||
this.filesClient.createDirectory(targetPath)
|
||||
.done(function(createStatus) {
|
||||
self.filesClient.getFileInfo(
|
||||
targetPath, {
|
||||
properties: self._getWebdavProperties()
|
||||
}
|
||||
)
|
||||
.done(function(status, data) {
|
||||
self.add(data, {animate: true, scrollTo: true});
|
||||
deferred.resolve(status, data);
|
||||
})
|
||||
.fail(function() {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
|
||||
deferred.reject(createStatus);
|
||||
});
|
||||
.done(function() {
|
||||
self.addAndFetchFileInfo(targetPath, '', {scrollTo:true}).then(function(status, data) {
|
||||
deferred.resolve(status, data);
|
||||
}, function() {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
|
||||
});
|
||||
})
|
||||
.fail(function(createStatus) {
|
||||
// method not allowed, folder might exist already
|
||||
if (createStatus === 405) {
|
||||
self.filesClient.getFileInfo(
|
||||
targetPath, {
|
||||
properties: self._getWebdavProperties()
|
||||
}
|
||||
)
|
||||
// add it to the list, for completeness
|
||||
self.addAndFetchFileInfo(targetPath, '', {scrollTo:true})
|
||||
.done(function(status, data) {
|
||||
// add it to the list, for completeness
|
||||
self.add(data, {animate: true, scrollTo: true});
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not create folder "{dir}" because it already exists', {dir: name})
|
||||
);
|
||||
|
@ -2221,6 +2239,60 @@
|
|||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add file into the list by fetching its information from the server first.
|
||||
*
|
||||
* If the given directory does not match the current directory, nothing will
|
||||
* be fetched.
|
||||
*
|
||||
* @param {String} fileName file name
|
||||
* @param {String} [dir] optional directory, defaults to the current one
|
||||
* @param {Object} options same options as #add
|
||||
* @return {Promise} promise that resolves with the file info, or an
|
||||
* already resolved Promise if no info was fetched. The promise rejects
|
||||
* if the file was not found or an error occurred.
|
||||
*
|
||||
* @since 9.0
|
||||
*/
|
||||
addAndFetchFileInfo: function(fileName, dir, options) {
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
if (_.isUndefined(dir)) {
|
||||
dir = this.getCurrentDirectory();
|
||||
} else {
|
||||
dir = dir || '/';
|
||||
}
|
||||
|
||||
var targetPath = OC.joinPaths(dir, fileName);
|
||||
|
||||
if ((OC.dirname(targetPath) || '/') !== this.getCurrentDirectory()) {
|
||||
// no need to fetch information
|
||||
deferred.resolve();
|
||||
return deferred.promise();
|
||||
}
|
||||
|
||||
var addOptions = _.extend({
|
||||
animate: true,
|
||||
scrollTo: false
|
||||
}, options || {});
|
||||
|
||||
this.filesClient.getFileInfo(targetPath, {
|
||||
properties: this._getWebdavProperties()
|
||||
})
|
||||
.then(function(status, data) {
|
||||
// remove first to avoid duplicates
|
||||
self.remove(data.name);
|
||||
self.add(data, addOptions);
|
||||
deferred.resolve(status, data);
|
||||
})
|
||||
.fail(function(status) {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
|
||||
deferred.reject(status);
|
||||
});
|
||||
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether the given file name exists in the list
|
||||
*
|
||||
|
@ -2590,18 +2662,16 @@
|
|||
/**
|
||||
* Setup file upload events related to the file-upload plugin
|
||||
*/
|
||||
setupUploadEvents: function() {
|
||||
setupUploadEvents: function($uploadEl) {
|
||||
var self = this;
|
||||
|
||||
// handle upload events
|
||||
var fileUploadStart = this.$el;
|
||||
var delegatedElement = '#file_upload_start';
|
||||
self._uploads = {};
|
||||
|
||||
// detect the progress bar resize
|
||||
fileUploadStart.on('resized', this._onResize);
|
||||
$uploadEl.on('resized', this._onResize);
|
||||
|
||||
fileUploadStart.on('fileuploaddrop', delegatedElement, function(e, data) {
|
||||
OC.Upload.log('filelist handle fileuploaddrop', e, data);
|
||||
$uploadEl.on('fileuploaddrop', function(e, data) {
|
||||
self._uploader.log('filelist handle fileuploaddrop', e, data);
|
||||
|
||||
if (self.$el.hasClass('hidden')) {
|
||||
// do not upload to invisible lists
|
||||
|
@ -2664,13 +2734,8 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
fileUploadStart.on('fileuploadadd', function(e, data) {
|
||||
OC.Upload.log('filelist handle fileuploadadd', e, data);
|
||||
|
||||
//finish delete if we are uploading a deleted file
|
||||
if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) {
|
||||
self.finishDelete(null, true); //delete file before continuing
|
||||
}
|
||||
$uploadEl.on('fileuploadadd', function(e, data) {
|
||||
self._uploader.log('filelist handle fileuploadadd', e, data);
|
||||
|
||||
// add ui visualization to existing folder
|
||||
if (data.context && data.context.data('type') === 'dir') {
|
||||
|
@ -2692,126 +2757,57 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (!data.targetDir) {
|
||||
data.targetDir = self.getCurrentDirectory();
|
||||
}
|
||||
|
||||
});
|
||||
/*
|
||||
* when file upload done successfully add row to filelist
|
||||
* update counter when uploading to sub folder
|
||||
*/
|
||||
fileUploadStart.on('fileuploaddone', function(e, data) {
|
||||
OC.Upload.log('filelist handle fileuploaddone', e, data);
|
||||
$uploadEl.on('fileuploaddone', function(e, data) {
|
||||
self._uploader.log('filelist handle fileuploaddone', e, data);
|
||||
|
||||
var response;
|
||||
if (typeof data.result === 'string') {
|
||||
response = data.result;
|
||||
} else {
|
||||
// fetch response from iframe
|
||||
response = data.result[0].body.innerText;
|
||||
var status = data.jqXHR.status;
|
||||
if (status < 200 || status >= 300) {
|
||||
// error was handled in OC.Uploads already
|
||||
return;
|
||||
}
|
||||
var result = JSON.parse(response);
|
||||
|
||||
if (typeof result[0] !== 'undefined' && result[0].status === 'success') {
|
||||
var file = result[0];
|
||||
var size = 0;
|
||||
|
||||
if (data.context && data.context.data('type') === 'dir') {
|
||||
|
||||
// update upload counter ui
|
||||
var uploadText = data.context.find('.uploadtext');
|
||||
var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
|
||||
currentUploads -= 1;
|
||||
uploadText.attr('currentUploads', currentUploads);
|
||||
var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
|
||||
if (currentUploads === 0) {
|
||||
self.showFileBusyState(uploadText.closest('tr'), false);
|
||||
uploadText.text(translatedText);
|
||||
uploadText.hide();
|
||||
} else {
|
||||
uploadText.text(translatedText);
|
||||
}
|
||||
|
||||
// update folder size
|
||||
size = parseInt(data.context.data('size'), 10);
|
||||
size += parseInt(file.size, 10);
|
||||
data.context.attr('data-size', size);
|
||||
data.context.find('td.filesize').text(humanFileSize(size));
|
||||
} else {
|
||||
// only append new file if uploaded into the current folder
|
||||
if (file.directory !== self.getCurrentDirectory()) {
|
||||
// Uploading folders actually uploads a list of files
|
||||
// for which the target directory (file.directory) might lie deeper
|
||||
// than the current directory
|
||||
|
||||
var fileDirectory = file.directory.replace('/','').replace(/\/$/, "");
|
||||
var currentDirectory = self.getCurrentDirectory().replace('/','').replace(/\/$/, "") + '/';
|
||||
|
||||
if (currentDirectory !== '/') {
|
||||
// abort if fileDirectory does not start with current one
|
||||
if (fileDirectory.indexOf(currentDirectory) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the current directory part
|
||||
fileDirectory = fileDirectory.substr(currentDirectory.length);
|
||||
}
|
||||
|
||||
// only take the first section of the path
|
||||
fileDirectory = fileDirectory.split('/');
|
||||
|
||||
var fd;
|
||||
// if the first section exists / is a subdir
|
||||
if (fileDirectory.length) {
|
||||
fileDirectory = fileDirectory[0];
|
||||
|
||||
// See whether it is already in the list
|
||||
fd = self.findFileEl(fileDirectory);
|
||||
if (fd.length === 0) {
|
||||
var dir = {
|
||||
name: fileDirectory,
|
||||
type: 'dir',
|
||||
mimetype: 'httpd/unix-directory',
|
||||
permissions: file.permissions,
|
||||
size: 0,
|
||||
id: file.parentId
|
||||
};
|
||||
fd = self.add(dir, {insert: true});
|
||||
}
|
||||
|
||||
// update folder size
|
||||
size = parseInt(fd.attr('data-size'), 10);
|
||||
size += parseInt(file.size, 10);
|
||||
fd.attr('data-size', size);
|
||||
fd.find('td.filesize').text(OC.Util.humanFileSize(size));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// add as stand-alone row to filelist
|
||||
size = t('files', 'Pending');
|
||||
if (data.files[0].size>=0) {
|
||||
size=data.files[0].size;
|
||||
}
|
||||
//should the file exist in the list remove it
|
||||
self.remove(file.name);
|
||||
|
||||
// create new file context
|
||||
data.context = self.add(file, {animate: true});
|
||||
}
|
||||
var upload = self._uploader.getUpload(data);
|
||||
var fileName = upload.getFileName();
|
||||
var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
|
||||
if (!self._uploads) {
|
||||
self._uploads = {};
|
||||
}
|
||||
if (OC.isSamePath(OC.dirname(upload.getFullPath() + '/'), self.getCurrentDirectory())) {
|
||||
self._uploads[fileName] = fetchInfoPromise;
|
||||
}
|
||||
});
|
||||
fileUploadStart.on('fileuploadstop', function() {
|
||||
OC.Upload.log('filelist handle fileuploadstop');
|
||||
$uploadEl.on('fileuploadcreatedfolder', function(e, fullPath) {
|
||||
self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
|
||||
});
|
||||
$uploadEl.on('fileuploadstop', function() {
|
||||
self._uploader.log('filelist handle fileuploadstop');
|
||||
|
||||
//cleanup uploading to a dir
|
||||
var uploadText = self.$fileList.find('tr .uploadtext');
|
||||
self.showFileBusyState(uploadText.closest('tr'), false);
|
||||
uploadText.fadeOut();
|
||||
uploadText.attr('currentUploads', 0);
|
||||
// prepare list of uploaded file names in the current directory
|
||||
// and discard the other ones
|
||||
var promises = _.values(self._uploads);
|
||||
var fileNames = _.keys(self._uploads);
|
||||
self._uploads = [];
|
||||
|
||||
// as soon as all info is fetched
|
||||
$.when.apply($, promises).then(function() {
|
||||
// highlight uploaded files
|
||||
self.highlightFiles(fileNames);
|
||||
});
|
||||
self.updateStorageStatistics();
|
||||
});
|
||||
fileUploadStart.on('fileuploadfail', function(e, data) {
|
||||
OC.Upload.log('filelist handle fileuploadfail', e, data);
|
||||
$uploadEl.on('fileuploadfail', function(e, data) {
|
||||
self._uploader.log('filelist handle fileuploadfail', e, data);
|
||||
|
||||
self._uploads = [];
|
||||
|
||||
//if user pressed cancel hide upload chrome
|
||||
if (data.errorThrown === 'abort') {
|
||||
|
|
|
@ -226,17 +226,6 @@
|
|||
// TODO: move file list related code (upload) to OCA.Files.FileList
|
||||
$('#file_action_panel').attr('activeAction', false);
|
||||
|
||||
// Triggers invisible file input
|
||||
$('#upload a').on('click', function() {
|
||||
$(this).parent().children('#file_upload_start').trigger('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Trigger cancelling of file upload
|
||||
$('#uploadprogresswrapper .stop').on('click', function() {
|
||||
OC.Upload.cancelUploads();
|
||||
});
|
||||
|
||||
// drag&drop support using jquery.fileupload
|
||||
// TODO use OC.dialogs
|
||||
$(document).bind('drop dragover', function (e) {
|
||||
|
|
|
@ -75,8 +75,7 @@
|
|||
</table>
|
||||
<input type="hidden" name="dir" id="dir" value="" />
|
||||
<div class="hiddenuploadfield">
|
||||
<input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]"
|
||||
data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" />
|
||||
<input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]" />
|
||||
</div>
|
||||
<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
|
||||
<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* global FileList */
|
||||
|
||||
describe('OC.Upload tests', function() {
|
||||
var $dummyUploader;
|
||||
var testFile;
|
||||
var uploader;
|
||||
var failStub;
|
||||
|
||||
beforeEach(function() {
|
||||
testFile = {
|
||||
|
@ -46,59 +46,64 @@ describe('OC.Upload tests', function() {
|
|||
'</div>'
|
||||
);
|
||||
$dummyUploader = $('#file_upload_start');
|
||||
uploader = new OC.Uploader($dummyUploader);
|
||||
failStub = sinon.stub();
|
||||
$dummyUploader.on('fileuploadfail', failStub);
|
||||
});
|
||||
afterEach(function() {
|
||||
delete window.file_upload_param;
|
||||
$dummyUploader = undefined;
|
||||
failStub = undefined;
|
||||
});
|
||||
describe('Adding files for upload', function() {
|
||||
var params;
|
||||
var failStub;
|
||||
|
||||
beforeEach(function() {
|
||||
params = OC.Upload.init();
|
||||
failStub = sinon.stub();
|
||||
$dummyUploader.on('fileuploadfail', failStub);
|
||||
});
|
||||
afterEach(function() {
|
||||
params = undefined;
|
||||
failStub = undefined;
|
||||
});
|
||||
|
||||
/**
|
||||
* Add file for upload
|
||||
* @param file file data
|
||||
*/
|
||||
function addFile(file) {
|
||||
return params.add.call(
|
||||
/**
|
||||
* Add file for upload
|
||||
* @param {Array.<File>} files array of file data to simulate upload
|
||||
* @return {Array.<Object>} array of uploadinfo or null if add() returned false
|
||||
*/
|
||||
function addFiles(uploader, files) {
|
||||
return _.map(files, function(file) {
|
||||
var jqXHR = {status: 200};
|
||||
var uploadInfo = {
|
||||
originalFiles: files,
|
||||
files: [file],
|
||||
jqXHR: jqXHR,
|
||||
response: sinon.stub.returns(jqXHR),
|
||||
submit: sinon.stub()
|
||||
};
|
||||
if (uploader.fileUploadParam.add.call(
|
||||
$dummyUploader[0],
|
||||
{},
|
||||
{
|
||||
originalFiles: {},
|
||||
files: [file]
|
||||
});
|
||||
}
|
||||
uploadInfo
|
||||
)) {
|
||||
return uploadInfo;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
describe('Adding files for upload', function() {
|
||||
it('adds file when size is below limits', function() {
|
||||
var result = addFile(testFile);
|
||||
expect(result).toEqual(true);
|
||||
var result = addFiles(uploader, [testFile]);
|
||||
expect(result[0]).not.toEqual(null);
|
||||
expect(result[0].submit.calledOnce).toEqual(true);
|
||||
});
|
||||
it('adds file when free space is unknown', function() {
|
||||
var result;
|
||||
$('#free_space').val(-2);
|
||||
|
||||
result = addFile(testFile);
|
||||
result = addFiles(uploader, [testFile]);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
expect(result[0]).not.toEqual(null);
|
||||
expect(result[0].submit.calledOnce).toEqual(true);
|
||||
expect(failStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('does not add file if it exceeds upload limit', function() {
|
||||
var result;
|
||||
$('#upload_limit').val(1000);
|
||||
|
||||
result = addFile(testFile);
|
||||
result = addFiles(uploader, [testFile]);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
expect(result[0]).toEqual(null);
|
||||
expect(failStub.calledOnce).toEqual(true);
|
||||
expect(failStub.getCall(0).args[1].textStatus).toEqual('sizeexceedlimit');
|
||||
expect(failStub.getCall(0).args[1].errorThrown).toEqual(
|
||||
|
@ -109,9 +114,9 @@ describe('OC.Upload tests', function() {
|
|||
var result;
|
||||
$('#free_space').val(1000);
|
||||
|
||||
result = addFile(testFile);
|
||||
result = addFiles(uploader, [testFile]);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
expect(result[0]).toEqual(null);
|
||||
expect(failStub.calledOnce).toEqual(true);
|
||||
expect(failStub.getCall(0).args[1].textStatus).toEqual('notenoughspace');
|
||||
expect(failStub.getCall(0).args[1].errorThrown).toEqual(
|
||||
|
@ -120,12 +125,10 @@ describe('OC.Upload tests', function() {
|
|||
});
|
||||
});
|
||||
describe('Upload conflicts', function() {
|
||||
var oldFileList;
|
||||
var conflictDialogStub;
|
||||
var callbacks;
|
||||
var fileList;
|
||||
|
||||
beforeEach(function() {
|
||||
oldFileList = FileList;
|
||||
$('#testArea').append(
|
||||
'<div id="tableContainer">' +
|
||||
'<table id="filestable">' +
|
||||
|
@ -145,74 +148,56 @@ describe('OC.Upload tests', function() {
|
|||
'</table>' +
|
||||
'</div>'
|
||||
);
|
||||
FileList = new OCA.Files.FileList($('#tableContainer'));
|
||||
fileList = new OCA.Files.FileList($('#tableContainer'));
|
||||
|
||||
FileList.add({name: 'conflict.txt', mimetype: 'text/plain'});
|
||||
FileList.add({name: 'conflict2.txt', mimetype: 'text/plain'});
|
||||
fileList.add({name: 'conflict.txt', mimetype: 'text/plain'});
|
||||
fileList.add({name: 'conflict2.txt', mimetype: 'text/plain'});
|
||||
|
||||
conflictDialogStub = sinon.stub(OC.dialogs, 'fileexists');
|
||||
callbacks = {
|
||||
onNoConflicts: sinon.stub()
|
||||
};
|
||||
|
||||
uploader = new OC.Uploader($dummyUploader, {
|
||||
fileList: fileList
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
conflictDialogStub.restore();
|
||||
|
||||
FileList.destroy();
|
||||
FileList = oldFileList;
|
||||
fileList.destroy();
|
||||
});
|
||||
it('does not show conflict dialog when no client side conflict', function() {
|
||||
var selection = {
|
||||
// yes, the format of uploads is weird...
|
||||
uploads: [
|
||||
{files: [{name: 'noconflict.txt'}]},
|
||||
{files: [{name: 'noconflict2.txt'}]}
|
||||
]
|
||||
};
|
||||
|
||||
OC.Upload.checkExistingFiles(selection, callbacks);
|
||||
var result = addFiles(uploader, [{name: 'noconflict.txt'}, {name: 'noconflict2.txt'}]);
|
||||
|
||||
expect(conflictDialogStub.notCalled).toEqual(true);
|
||||
expect(callbacks.onNoConflicts.calledOnce).toEqual(true);
|
||||
expect(callbacks.onNoConflicts.calledWith(selection)).toEqual(true);
|
||||
expect(result[0].submit.calledOnce).toEqual(true);
|
||||
expect(result[1].submit.calledOnce).toEqual(true);
|
||||
});
|
||||
it('shows conflict dialog when no client side conflict', function() {
|
||||
var selection = {
|
||||
// yes, the format of uploads is weird...
|
||||
uploads: [
|
||||
{files: [{name: 'conflict.txt'}]},
|
||||
{files: [{name: 'conflict2.txt'}]},
|
||||
{files: [{name: 'noconflict.txt'}]}
|
||||
]
|
||||
};
|
||||
|
||||
var deferred = $.Deferred();
|
||||
conflictDialogStub.returns(deferred.promise());
|
||||
deferred.resolve();
|
||||
|
||||
OC.Upload.checkExistingFiles(selection, callbacks);
|
||||
var result = addFiles(uploader, [
|
||||
{name: 'conflict.txt'},
|
||||
{name: 'conflict2.txt'},
|
||||
{name: 'noconflict.txt'}
|
||||
]);
|
||||
|
||||
expect(conflictDialogStub.callCount).toEqual(3);
|
||||
expect(conflictDialogStub.getCall(1).args[0])
|
||||
.toEqual({files: [ { name: 'conflict.txt' } ]});
|
||||
expect(conflictDialogStub.getCall(1).args[0].getFileName())
|
||||
.toEqual('conflict.txt');
|
||||
expect(conflictDialogStub.getCall(1).args[1])
|
||||
.toEqual({ name: 'conflict.txt', mimetype: 'text/plain', directory: '/' });
|
||||
expect(conflictDialogStub.getCall(1).args[2]).toEqual({ name: 'conflict.txt' });
|
||||
|
||||
// yes, the dialog must be called several times...
|
||||
expect(conflictDialogStub.getCall(2).args[0]).toEqual({
|
||||
files: [ { name: 'conflict2.txt' } ]
|
||||
});
|
||||
expect(conflictDialogStub.getCall(2).args[0].getFileName()).toEqual('conflict2.txt');
|
||||
expect(conflictDialogStub.getCall(2).args[1])
|
||||
.toEqual({ name: 'conflict2.txt', mimetype: 'text/plain', directory: '/' });
|
||||
expect(conflictDialogStub.getCall(2).args[2]).toEqual({ name: 'conflict2.txt' });
|
||||
|
||||
expect(callbacks.onNoConflicts.calledOnce).toEqual(true);
|
||||
expect(callbacks.onNoConflicts.calledWith({
|
||||
uploads: [
|
||||
{files: [{name: 'noconflict.txt'}]}
|
||||
]
|
||||
})).toEqual(true);
|
||||
expect(result[0].submit.calledOnce).toEqual(false);
|
||||
expect(result[1].submit.calledOnce).toEqual(false);
|
||||
expect(result[2].submit.calledOnce).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -159,7 +159,8 @@ describe('OCA.Files.FileList tests', function() {
|
|||
pageSizeStub = sinon.stub(OCA.Files.FileList.prototype, 'pageSize').returns(20);
|
||||
fileList = new OCA.Files.FileList($('#app-content-files'), {
|
||||
filesClient: filesClient,
|
||||
config: filesConfig
|
||||
config: filesConfig,
|
||||
enableUpload: true
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
|
@ -2441,7 +2442,7 @@ describe('OCA.Files.FileList tests', function() {
|
|||
|
||||
deferredInfo.resolve(
|
||||
200,
|
||||
new FileInfo({
|
||||
new FileInfo({
|
||||
path: '/subdir',
|
||||
name: 'test.txt',
|
||||
mimetype: 'text/plain'
|
||||
|
@ -2501,12 +2502,70 @@ describe('OCA.Files.FileList tests', function() {
|
|||
// TODO: error cases
|
||||
// TODO: unique name cases
|
||||
});
|
||||
describe('addAndFetchFileInfo', function() {
|
||||
var getFileInfoStub;
|
||||
var getFileInfoDeferred;
|
||||
|
||||
beforeEach(function() {
|
||||
getFileInfoDeferred = $.Deferred();
|
||||
getFileInfoStub = sinon.stub(OC.Files.Client.prototype, 'getFileInfo');
|
||||
getFileInfoStub.returns(getFileInfoDeferred.promise());
|
||||
});
|
||||
afterEach(function() {
|
||||
getFileInfoStub.restore();
|
||||
});
|
||||
it('does not fetch if the given folder is not the current one', function() {
|
||||
var promise = fileList.addAndFetchFileInfo('testfile.txt', '/another');
|
||||
expect(getFileInfoStub.notCalled).toEqual(true);
|
||||
|
||||
expect(promise.state()).toEqual('resolved');
|
||||
});
|
||||
it('fetches info when folder is the current one', function() {
|
||||
fileList.addAndFetchFileInfo('testfile.txt', '/subdir');
|
||||
expect(getFileInfoStub.calledOnce).toEqual(true);
|
||||
expect(getFileInfoStub.getCall(0).args[0]).toEqual('/subdir/testfile.txt');
|
||||
});
|
||||
it('adds file data to list when fetching is done', function() {
|
||||
fileList.addAndFetchFileInfo('testfile.txt', '/subdir');
|
||||
getFileInfoDeferred.resolve(200, {
|
||||
name: 'testfile.txt',
|
||||
size: 100
|
||||
});
|
||||
expect(fileList.findFileEl('testfile.txt').attr('data-size')).toEqual('100');
|
||||
});
|
||||
it('replaces file data to list when fetching is done', function() {
|
||||
fileList.addAndFetchFileInfo('testfile.txt', '/subdir', {replace: true});
|
||||
fileList.add({
|
||||
name: 'testfile.txt',
|
||||
size: 95
|
||||
});
|
||||
getFileInfoDeferred.resolve(200, {
|
||||
name: 'testfile.txt',
|
||||
size: 100
|
||||
});
|
||||
expect(fileList.findFileEl('testfile.txt').attr('data-size')).toEqual('100');
|
||||
});
|
||||
it('resolves promise with file data when fetching is done', function() {
|
||||
var promise = fileList.addAndFetchFileInfo('testfile.txt', '/subdir', {replace: true});
|
||||
getFileInfoDeferred.resolve(200, {
|
||||
name: 'testfile.txt',
|
||||
size: 100
|
||||
});
|
||||
expect(promise.state()).toEqual('resolved');
|
||||
promise.then(function(status, data) {
|
||||
expect(status).toEqual(200);
|
||||
expect(data.name).toEqual('testfile.txt');
|
||||
expect(data.size).toEqual(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Test upload mostly by testing the code inside the event handlers
|
||||
* that were registered on the magic upload object
|
||||
*/
|
||||
describe('file upload', function() {
|
||||
var $uploader;
|
||||
var uploadData;
|
||||
|
||||
beforeEach(function() {
|
||||
// note: this isn't the real blueimp file uploader from jquery.fileupload
|
||||
|
@ -2514,14 +2573,52 @@ describe('OCA.Files.FileList tests', function() {
|
|||
// test the response of the handlers
|
||||
$uploader = $('#file_upload_start');
|
||||
fileList.setFiles(testFiles);
|
||||
// simulate data structure from jquery.upload
|
||||
uploadData = {
|
||||
files: [{
|
||||
name: 'upload.txt'
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$uploader = null;
|
||||
uploadData = null;
|
||||
});
|
||||
|
||||
describe('enableupload', function() {
|
||||
it('sets up uploader when enableUpload is true', function() {
|
||||
expect(fileList._uploader).toBeDefined();
|
||||
});
|
||||
it('does not sets up uploader when enableUpload is false', function() {
|
||||
fileList.destroy();
|
||||
fileList = new OCA.Files.FileList($('#app-content-files'), {
|
||||
filesClient: filesClient
|
||||
});
|
||||
expect(fileList._uploader).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding files for upload', function() {
|
||||
/**
|
||||
* Simulate add event on the given target
|
||||
*
|
||||
* @return event object including the result
|
||||
*/
|
||||
function addFile(data) {
|
||||
var ev = new $.Event('fileuploadadd', {});
|
||||
// using triggerHandler instead of trigger so we can pass
|
||||
// extra data
|
||||
$uploader.triggerHandler(ev, data || {});
|
||||
return ev;
|
||||
}
|
||||
|
||||
it('sets target dir to the current directory', function() {
|
||||
addFile(uploadData);
|
||||
expect(uploadData.targetDir).toEqual('/subdir');
|
||||
});
|
||||
});
|
||||
describe('dropping external files', function() {
|
||||
var uploadData;
|
||||
|
||||
/**
|
||||
* Simulate drop event on the given target
|
||||
|
@ -2540,17 +2637,6 @@ describe('OCA.Files.FileList tests', function() {
|
|||
return ev;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
// simulate data structure from jquery.upload
|
||||
uploadData = {
|
||||
files: [{
|
||||
relativePath: 'fileToUpload.txt'
|
||||
}]
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
uploadData = null;
|
||||
});
|
||||
it('drop on a tr or crumb outside file list does not trigger upload', function() {
|
||||
var $anotherTable = $('<table><tbody><tr><td>outside<div class="crumb">crumb</div></td></tr></table>');
|
||||
var ev;
|
||||
|
@ -2574,12 +2660,14 @@ describe('OCA.Files.FileList tests', function() {
|
|||
ev = dropOn(fileList.$fileList.find('th:first'), uploadData);
|
||||
|
||||
expect(ev.result).not.toEqual(false);
|
||||
expect(uploadData.targetDir).toEqual('/subdir');
|
||||
});
|
||||
it('drop on an element on the table container triggers upload', function() {
|
||||
var ev;
|
||||
ev = dropOn($('#app-content-files'), uploadData);
|
||||
|
||||
expect(ev.result).not.toEqual(false);
|
||||
expect(uploadData.targetDir).toEqual('/subdir');
|
||||
});
|
||||
it('drop on an element inside the table does not trigger upload if no upload permission', function() {
|
||||
$('#permissions').val(0);
|
||||
|
@ -2603,6 +2691,7 @@ describe('OCA.Files.FileList tests', function() {
|
|||
ev = dropOn(fileList.findFileEl('One.txt').find('td:first'), uploadData);
|
||||
|
||||
expect(ev.result).not.toEqual(false);
|
||||
expect(uploadData.targetDir).toEqual('/subdir');
|
||||
});
|
||||
it('drop on a folder row inside the table triggers upload to target folder', function() {
|
||||
var ev;
|
||||
|
@ -2635,6 +2724,97 @@ describe('OCA.Files.FileList tests', function() {
|
|||
expect(fileList.findFileEl('afile.txt').find('.uploadtext').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after folder creation due to folder upload', function() {
|
||||
it('fetches folder info', function() {
|
||||
var fetchInfoStub = sinon.stub(fileList, 'addAndFetchFileInfo');
|
||||
|
||||
var ev = new $.Event('fileuploadcreatedfolder', {});
|
||||
$uploader.triggerHandler(ev, '/subdir/newfolder');
|
||||
|
||||
expect(fetchInfoStub.calledOnce).toEqual(true);
|
||||
expect(fetchInfoStub.getCall(0).args[0]).toEqual('newfolder');
|
||||
expect(fetchInfoStub.getCall(0).args[1]).toEqual('/subdir');
|
||||
|
||||
fetchInfoStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('after upload', function() {
|
||||
var fetchInfoStub;
|
||||
|
||||
beforeEach(function() {
|
||||
fetchInfoStub = sinon.stub(fileList, 'addAndFetchFileInfo');
|
||||
|
||||
});
|
||||
afterEach(function() {
|
||||
fetchInfoStub.restore();
|
||||
});
|
||||
|
||||
|
||||
function createUpload(name, dir) {
|
||||
var data = {
|
||||
files: [{
|
||||
name: name
|
||||
}],
|
||||
upload: {
|
||||
getFileName: sinon.stub().returns(name),
|
||||
getFullPath: sinon.stub().returns(dir)
|
||||
},
|
||||
jqXHR: {
|
||||
status: 200
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate add event on the given target
|
||||
*
|
||||
* @return event object including the result
|
||||
*/
|
||||
function addFile(data) {
|
||||
var ev = new $.Event('fileuploaddone', {});
|
||||
// using triggerHandler instead of trigger so we can pass
|
||||
// extra data
|
||||
var deferred = $.Deferred();
|
||||
fetchInfoStub.returns(deferred.promise());
|
||||
$uploader.triggerHandler(ev, data || {});
|
||||
return deferred;
|
||||
}
|
||||
|
||||
it('fetches file info', function() {
|
||||
addFile(createUpload('upload.txt', '/subdir'));
|
||||
expect(fetchInfoStub.calledOnce).toEqual(true);
|
||||
expect(fetchInfoStub.getCall(0).args[0]).toEqual('upload.txt');
|
||||
expect(fetchInfoStub.getCall(0).args[1]).toEqual('/subdir');
|
||||
});
|
||||
it('highlights all uploaded files after all fetches are done', function() {
|
||||
var highlightStub = sinon.stub(fileList, 'highlightFiles');
|
||||
var def1 = addFile(createUpload('upload.txt', '/subdir'));
|
||||
var def2 = addFile(createUpload('upload2.txt', '/subdir'));
|
||||
var def3 = addFile(createUpload('upload3.txt', '/another'));
|
||||
$uploader.triggerHandler(new $.Event('fileuploadstop'));
|
||||
|
||||
expect(highlightStub.notCalled).toEqual(true);
|
||||
def1.resolve();
|
||||
expect(highlightStub.notCalled).toEqual(true);
|
||||
def2.resolve();
|
||||
def3.resolve();
|
||||
expect(highlightStub.calledOnce).toEqual(true);
|
||||
expect(highlightStub.getCall(0).args[0]).toEqual(['upload.txt', 'upload2.txt']);
|
||||
|
||||
highlightStub.restore();
|
||||
});
|
||||
it('queries storage stats', function() {
|
||||
var statStub = sinon.stub(fileList, 'updateStorageStatistics');
|
||||
addFile(createUpload('upload.txt', '/subdir'));
|
||||
expect(statStub.notCalled).toEqual(true);
|
||||
$uploader.triggerHandler(new $.Event('fileuploadstop'));
|
||||
expect(statStub.calledOnce).toEqual(true);
|
||||
statStub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Handling errors', function () {
|
||||
var deferredList;
|
||||
|
|
|
@ -72,7 +72,8 @@ OCA.Sharing.PublicApp = {
|
|||
folderDropOptions: folderDropOptions,
|
||||
fileActions: fileActions,
|
||||
detailsViewEnabled: false,
|
||||
filesClient: filesClient
|
||||
filesClient: filesClient,
|
||||
enableUpload: true
|
||||
}
|
||||
);
|
||||
this.files = OCA.Files.Files;
|
||||
|
@ -170,6 +171,30 @@ OCA.Sharing.PublicApp = {
|
|||
return OC.generateUrl('/s/' + token + '/download') + '?' + OC.buildQueryString(params);
|
||||
};
|
||||
|
||||
this.fileList.getUploadUrl = function(fileName, dir) {
|
||||
if (_.isUndefined(dir)) {
|
||||
dir = this.getCurrentDirectory();
|
||||
}
|
||||
|
||||
var pathSections = dir.split('/');
|
||||
if (!_.isUndefined(fileName)) {
|
||||
pathSections.push(fileName);
|
||||
}
|
||||
var encodedPath = '';
|
||||
_.each(pathSections, function(section) {
|
||||
if (section !== '') {
|
||||
encodedPath += '/' + encodeURIComponent(section);
|
||||
}
|
||||
});
|
||||
var base = '';
|
||||
|
||||
if (!this._uploader.isXHRUpload()) {
|
||||
// also add auth in URL due to POST workaround
|
||||
base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : '');
|
||||
}
|
||||
return base + OC.getRootPath() + '/public.php/webdav' + encodedPath;
|
||||
};
|
||||
|
||||
this.fileList.getAjaxUrl = function (action, params) {
|
||||
params = params || {};
|
||||
params.t = token;
|
||||
|
@ -203,20 +228,12 @@ OCA.Sharing.PublicApp = {
|
|||
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
|
||||
};
|
||||
|
||||
var file_upload_start = $('#file_upload_start');
|
||||
file_upload_start.on('fileuploadadd', function (e, data) {
|
||||
var fileDirectory = '';
|
||||
if (typeof data.files[0].relativePath !== 'undefined') {
|
||||
fileDirectory = data.files[0].relativePath;
|
||||
this.fileList._uploader.on('fileuploadadd', function(e, data) {
|
||||
if (!data.headers) {
|
||||
data.headers = {};
|
||||
}
|
||||
|
||||
// Add custom data to the upload handler
|
||||
data.formData = {
|
||||
requesttoken: $('#publicUploadRequestToken').val(),
|
||||
dirToken: $('#dirToken').val(),
|
||||
subdir: data.targetDir || self.fileList.getCurrentDirectory(),
|
||||
file_directory: fileDirectory
|
||||
};
|
||||
data.headers.Authorization = 'Basic ' + btoa(token + ':');
|
||||
});
|
||||
|
||||
// do not allow sharing from the public page
|
||||
|
|
|
@ -87,10 +87,18 @@ describe('OCA.Sharing.PublicApp tests', function() {
|
|||
});
|
||||
|
||||
it('Uses public webdav endpoint', function() {
|
||||
App._initialized = false;
|
||||
fakeServer.restore();
|
||||
window.fakeServer = sinon.fakeServer.create();
|
||||
|
||||
// uploader function messes up with fakeServer
|
||||
var uploaderDetectStub = sinon.stub(OC.Uploader.prototype, '_supportAjaxUploadWithProgress');
|
||||
App.initialize($('#preview'));
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
|
||||
expect(fakeServer.requests[0].url).toEqual('https://example.com:9876/owncloud/public.php/webdav/subdir');
|
||||
expect(fakeServer.requests[0].requestHeaders.Authorization).toEqual('Basic c2g0dG9rOm51bGw=');
|
||||
uploaderDetectStub.restore();
|
||||
});
|
||||
|
||||
describe('Download Url', function() {
|
||||
|
@ -118,5 +126,20 @@ describe('OCA.Sharing.PublicApp tests', function() {
|
|||
.toEqual(OC.webroot + '/index.php/apps/files_sharing/ajax/test.php?a=1&b=x%20y&t=sh4tok');
|
||||
});
|
||||
});
|
||||
describe('Upload Url', function() {
|
||||
var fileList;
|
||||
|
||||
beforeEach(function() {
|
||||
fileList = App.fileList;
|
||||
});
|
||||
it('returns correct upload URL', function() {
|
||||
expect(fileList.getUploadUrl('some file.txt'))
|
||||
.toEqual('/owncloud/public.php/webdav/subdir/some%20file.txt');
|
||||
});
|
||||
it('returns correct upload URL with specified dir', function() {
|
||||
expect(fileList.getUploadUrl('some file.txt', 'sub'))
|
||||
.toEqual('/owncloud/public.php/webdav/sub/some%20file.txt');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue