Merge pull request #21610 from owncloud/quota-sharedfile
Fix quota check for single shared files
This commit is contained in:
commit
6c4cdddba8
|
@ -95,12 +95,14 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
||||||
$req = $this->server->httpRequest;
|
$req = $this->server->httpRequest;
|
||||||
if ($req->getHeader('OC-Chunked')) {
|
if ($req->getHeader('OC-Chunked')) {
|
||||||
$info = \OC_FileChunking::decodeName($newName);
|
$info = \OC_FileChunking::decodeName($newName);
|
||||||
$chunkHandler = new \OC_FileChunking($info);
|
$chunkHandler = $this->getFileChunking($info);
|
||||||
// subtract the already uploaded size to see whether
|
// subtract the already uploaded size to see whether
|
||||||
// there is still enough space for the remaining chunks
|
// there is still enough space for the remaining chunks
|
||||||
$length -= $chunkHandler->getCurrentSize();
|
$length -= $chunkHandler->getCurrentSize();
|
||||||
|
// use target file name for free space check in case of shared files
|
||||||
|
$uri = rtrim($parentUri, '/') . '/' . $info['name'];
|
||||||
}
|
}
|
||||||
$freeSpace = $this->getFreeSpace($parentUri);
|
$freeSpace = $this->getFreeSpace($uri);
|
||||||
if ($freeSpace !== \OCP\Files\FileInfo::SPACE_UNKNOWN && $length > $freeSpace) {
|
if ($freeSpace !== \OCP\Files\FileInfo::SPACE_UNKNOWN && $length > $freeSpace) {
|
||||||
if (isset($chunkHandler)) {
|
if (isset($chunkHandler)) {
|
||||||
$chunkHandler->cleanup();
|
$chunkHandler->cleanup();
|
||||||
|
@ -111,6 +113,11 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFileChunking($info) {
|
||||||
|
// FIXME: need a factory for better mocking support
|
||||||
|
return new \OC_FileChunking($info);
|
||||||
|
}
|
||||||
|
|
||||||
public function getLength() {
|
public function getLength() {
|
||||||
$req = $this->server->httpRequest;
|
$req = $this->server->httpRequest;
|
||||||
$length = $req->getHeader('X-Expected-Entity-Length');
|
$length = $req->getHeader('X-Expected-Entity-Length');
|
||||||
|
@ -127,12 +134,12 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $parentUri
|
* @param string $uri
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getFreeSpace($parentUri) {
|
public function getFreeSpace($uri) {
|
||||||
try {
|
try {
|
||||||
$freeSpace = $this->view->free_space($parentUri);
|
$freeSpace = $this->view->free_space(ltrim($uri, '/'));
|
||||||
return $freeSpace;
|
return $freeSpace;
|
||||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||||
|
|
|
@ -39,10 +39,13 @@ class QuotaPlugin extends \Test\TestCase {
|
||||||
*/
|
*/
|
||||||
private $plugin;
|
private $plugin;
|
||||||
|
|
||||||
private function init($quota) {
|
private function init($quota, $checkedPath = '') {
|
||||||
$view = $this->buildFileViewMock($quota);
|
$view = $this->buildFileViewMock($quota, $checkedPath);
|
||||||
$this->server = new \Sabre\DAV\Server();
|
$this->server = new \Sabre\DAV\Server();
|
||||||
$this->plugin = new \OCA\DAV\Connector\Sabre\QuotaPlugin($view);
|
$this->plugin = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\QuotaPlugin')
|
||||||
|
->setConstructorArgs([$view])
|
||||||
|
->setMethods(['getFileChunking'])
|
||||||
|
->getMock();
|
||||||
$this->plugin->initialize($this->server);
|
$this->plugin->initialize($this->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +54,8 @@ class QuotaPlugin extends \Test\TestCase {
|
||||||
*/
|
*/
|
||||||
public function testLength($expected, $headers) {
|
public function testLength($expected, $headers) {
|
||||||
$this->init(0);
|
$this->init(0);
|
||||||
|
$this->plugin->expects($this->never())
|
||||||
|
->method('getFileChunking');
|
||||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
$length = $this->plugin->getLength();
|
$length = $this->plugin->getLength();
|
||||||
$this->assertEquals($expected, $length);
|
$this->assertEquals($expected, $length);
|
||||||
|
@ -61,6 +66,8 @@ class QuotaPlugin extends \Test\TestCase {
|
||||||
*/
|
*/
|
||||||
public function testCheckQuota($quota, $headers) {
|
public function testCheckQuota($quota, $headers) {
|
||||||
$this->init($quota);
|
$this->init($quota);
|
||||||
|
$this->plugin->expects($this->never())
|
||||||
|
->method('getFileChunking');
|
||||||
|
|
||||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
$result = $this->plugin->checkQuota('');
|
$result = $this->plugin->checkQuota('');
|
||||||
|
@ -73,11 +80,26 @@ class QuotaPlugin extends \Test\TestCase {
|
||||||
*/
|
*/
|
||||||
public function testCheckExceededQuota($quota, $headers) {
|
public function testCheckExceededQuota($quota, $headers) {
|
||||||
$this->init($quota);
|
$this->init($quota);
|
||||||
|
$this->plugin->expects($this->never())
|
||||||
|
->method('getFileChunking');
|
||||||
|
|
||||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
$this->plugin->checkQuota('');
|
$this->plugin->checkQuota('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider quotaOkayProvider
|
||||||
|
*/
|
||||||
|
public function testCheckQuotaOnPath($quota, $headers) {
|
||||||
|
$this->init($quota, 'sub/test.txt');
|
||||||
|
$this->plugin->expects($this->never())
|
||||||
|
->method('getFileChunking');
|
||||||
|
|
||||||
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
|
$result = $this->plugin->checkQuota('/sub/test.txt');
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
public function quotaOkayProvider() {
|
public function quotaOkayProvider() {
|
||||||
return array(
|
return array(
|
||||||
array(1024, array()),
|
array(1024, array()),
|
||||||
|
@ -110,12 +132,89 @@ class QuotaPlugin extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildFileViewMock($quota) {
|
public function quotaChunkedOkProvider() {
|
||||||
|
return array(
|
||||||
|
array(1024, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')),
|
||||||
|
array(1024, 0, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(1024, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')),
|
||||||
|
// with existing chunks (allowed size = total length - chunk total size)
|
||||||
|
array(400, 128, array('X-EXPECTED-ENTITY-LENGTH' => '512')),
|
||||||
|
array(400, 128, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(400, 128, array('OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500')),
|
||||||
|
// \OCP\Files\FileInfo::SPACE-UNKNOWN = -2
|
||||||
|
array(-2, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')),
|
||||||
|
array(-2, 0, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(-2, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')),
|
||||||
|
array(-2, 128, array('X-EXPECTED-ENTITY-LENGTH' => '1024')),
|
||||||
|
array(-2, 128, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(-2, 128, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider quotaChunkedOkProvider
|
||||||
|
*/
|
||||||
|
public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers) {
|
||||||
|
$this->init($quota, 'sub/test.txt');
|
||||||
|
|
||||||
|
$mockChunking = $this->getMockBuilder('\OC_FileChunking')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$mockChunking->expects($this->once())
|
||||||
|
->method('getCurrentSize')
|
||||||
|
->will($this->returnValue($chunkTotalSize));
|
||||||
|
|
||||||
|
$this->plugin->expects($this->once())
|
||||||
|
->method('getFileChunking')
|
||||||
|
->will($this->returnValue($mockChunking));
|
||||||
|
|
||||||
|
$headers['OC-CHUNKED'] = 1;
|
||||||
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
|
$result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1');
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function quotaChunkedFailProvider() {
|
||||||
|
return array(
|
||||||
|
array(400, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')),
|
||||||
|
array(400, 0, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(400, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')),
|
||||||
|
// with existing chunks (allowed size = total length - chunk total size)
|
||||||
|
array(380, 128, array('X-EXPECTED-ENTITY-LENGTH' => '512')),
|
||||||
|
array(380, 128, array('CONTENT-LENGTH' => '512')),
|
||||||
|
array(380, 128, array('OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider quotaChunkedFailProvider
|
||||||
|
* @expectedException \Sabre\DAV\Exception\InsufficientStorage
|
||||||
|
*/
|
||||||
|
public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers) {
|
||||||
|
$this->init($quota, 'sub/test.txt');
|
||||||
|
|
||||||
|
$mockChunking = $this->getMockBuilder('\OC_FileChunking')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$mockChunking->expects($this->once())
|
||||||
|
->method('getCurrentSize')
|
||||||
|
->will($this->returnValue($chunkTotalSize));
|
||||||
|
|
||||||
|
$this->plugin->expects($this->once())
|
||||||
|
->method('getFileChunking')
|
||||||
|
->will($this->returnValue($mockChunking));
|
||||||
|
|
||||||
|
$headers['OC-CHUNKED'] = 1;
|
||||||
|
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||||
|
$this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFileViewMock($quota, $checkedPath) {
|
||||||
// mock filesysten
|
// mock filesysten
|
||||||
$view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false);
|
$view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false);
|
||||||
$view->expects($this->any())
|
$view->expects($this->any())
|
||||||
->method('free_space')
|
->method('free_space')
|
||||||
->with($this->identicalTo(''))
|
->with($this->identicalTo($checkedPath))
|
||||||
->will($this->returnValue($quota));
|
->will($this->returnValue($quota));
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
|
|
|
@ -136,8 +136,12 @@ $maxUploadFileSize = $storageStats['uploadMaxFilesize'];
|
||||||
$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
|
$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
|
||||||
|
|
||||||
$totalSize = 0;
|
$totalSize = 0;
|
||||||
foreach ($files['size'] as $size) {
|
$isReceivedShare = \OC::$server->getRequest()->getParam('isReceivedShare', false) === 'true';
|
||||||
|
// defer quota check for received shares
|
||||||
|
if (!$isReceivedShare) {
|
||||||
|
foreach ($files['size'] as $size) {
|
||||||
$totalSize += $size;
|
$totalSize += $size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($maxUploadFileSize >= 0 and $totalSize > $maxUploadFileSize) {
|
if ($maxUploadFileSize >= 0 and $totalSize > $maxUploadFileSize) {
|
||||||
OCP\JSON::error(array('data' => array('message' => $l->t('Not enough storage available'),
|
OCP\JSON::error(array('data' => array('message' => $l->t('Not enough storage available'),
|
||||||
|
|
|
@ -251,7 +251,26 @@ OC.Upload = {
|
||||||
$('#file_upload_start').trigger(new $.Event('resized'));
|
$('#file_upload_start').trigger(new $.Event('resized'));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given file is known to be a received shared file
|
||||||
|
*
|
||||||
|
* @param {Object} file file
|
||||||
|
* @return {bool} true if the file is a shared file
|
||||||
|
*/
|
||||||
|
_isReceivedSharedFile: function(file) {
|
||||||
|
if (!window.FileList) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var $tr = window.FileList.findFileEl(file.name);
|
||||||
|
if (!$tr.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($tr.attr('data-mounttype') === 'shared-root' && $tr.attr('data-mime') !== 'httpd/unix-directory');
|
||||||
|
},
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
|
var self = this;
|
||||||
if ( $('#file_upload_start').exists() ) {
|
if ( $('#file_upload_start').exists() ) {
|
||||||
var file_upload_param = {
|
var file_upload_param = {
|
||||||
dropZone: $('#content'), // restrict dropZone to content div
|
dropZone: $('#content'), // restrict dropZone to content div
|
||||||
|
@ -341,10 +360,15 @@ OC.Upload = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only count if we're not overwriting an existing shared file
|
||||||
|
if (self._isReceivedSharedFile(file)) {
|
||||||
|
file.isReceivedShare = true;
|
||||||
|
} else {
|
||||||
// add size
|
// add size
|
||||||
selection.totalBytes += file.size;
|
selection.totalBytes += file.size;
|
||||||
// update size of biggest file
|
// update size of biggest file
|
||||||
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
|
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
|
||||||
|
}
|
||||||
|
|
||||||
// check PHP upload limit against biggest file
|
// check PHP upload limit against biggest file
|
||||||
if (selection.biggestFileBytes > $('#upload_limit').val()) {
|
if (selection.biggestFileBytes > $('#upload_limit').val()) {
|
||||||
|
@ -430,11 +454,16 @@ OC.Upload = {
|
||||||
fileDirectory = data.files[0].relativePath;
|
fileDirectory = data.files[0].relativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
addFormData(data.formData, {
|
var params = {
|
||||||
requesttoken: oc_requesttoken,
|
requesttoken: oc_requesttoken,
|
||||||
dir: data.targetDir || FileList.getCurrentDirectory(),
|
dir: data.targetDir || FileList.getCurrentDirectory(),
|
||||||
file_directory: fileDirectory
|
file_directory: fileDirectory,
|
||||||
});
|
};
|
||||||
|
if (data.files[0].isReceivedShare) {
|
||||||
|
params.isReceivedShare = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFormData(data.formData, params);
|
||||||
},
|
},
|
||||||
fail: function(e, data) {
|
fail: function(e, data) {
|
||||||
OC.Upload.log('fail', e, data);
|
OC.Upload.log('fail', e, data);
|
||||||
|
|
|
@ -141,6 +141,9 @@ class Quota extends Wrapper {
|
||||||
*/
|
*/
|
||||||
public function fopen($path, $mode) {
|
public function fopen($path, $mode) {
|
||||||
$source = $this->storage->fopen($path, $mode);
|
$source = $this->storage->fopen($path, $mode);
|
||||||
|
|
||||||
|
// don't apply quota for part files
|
||||||
|
if (!$this->isPartFile($path)) {
|
||||||
$free = $this->free_space('');
|
$free = $this->free_space('');
|
||||||
if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
|
if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
|
||||||
// only apply quota for files, not metadata, trash or others
|
// only apply quota for files, not metadata, trash or others
|
||||||
|
@ -148,9 +151,23 @@ class Quota extends Wrapper {
|
||||||
return \OC\Files\Stream\Quota::wrap($source, $free);
|
return \OC\Files\Stream\Quota::wrap($source, $free);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return $source;
|
return $source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given path is a part file
|
||||||
|
*
|
||||||
|
* @param string $path Path that may identify a .part file
|
||||||
|
* @return string File path without .part extension
|
||||||
|
* @note this is needed for reusing keys
|
||||||
|
*/
|
||||||
|
private function isPartFile($path) {
|
||||||
|
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
return ($extension === 'part');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \OCP\Files\Storage $sourceStorage
|
* @param \OCP\Files\Storage $sourceStorage
|
||||||
* @param string $sourceInternalPath
|
* @param string $sourceInternalPath
|
||||||
|
|
Loading…
Reference in New Issue