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;
|
||||
if ($req->getHeader('OC-Chunked')) {
|
||||
$info = \OC_FileChunking::decodeName($newName);
|
||||
$chunkHandler = new \OC_FileChunking($info);
|
||||
$chunkHandler = $this->getFileChunking($info);
|
||||
// subtract the already uploaded size to see whether
|
||||
// there is still enough space for the remaining chunks
|
||||
$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 (isset($chunkHandler)) {
|
||||
$chunkHandler->cleanup();
|
||||
|
@ -111,6 +113,11 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getFileChunking($info) {
|
||||
// FIXME: need a factory for better mocking support
|
||||
return new \OC_FileChunking($info);
|
||||
}
|
||||
|
||||
public function getLength() {
|
||||
$req = $this->server->httpRequest;
|
||||
$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
|
||||
*/
|
||||
public function getFreeSpace($parentUri) {
|
||||
public function getFreeSpace($uri) {
|
||||
try {
|
||||
$freeSpace = $this->view->free_space($parentUri);
|
||||
$freeSpace = $this->view->free_space(ltrim($uri, '/'));
|
||||
return $freeSpace;
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
|
|
|
@ -39,10 +39,13 @@ class QuotaPlugin extends \Test\TestCase {
|
|||
*/
|
||||
private $plugin;
|
||||
|
||||
private function init($quota) {
|
||||
$view = $this->buildFileViewMock($quota);
|
||||
private function init($quota, $checkedPath = '') {
|
||||
$view = $this->buildFileViewMock($quota, $checkedPath);
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -51,6 +54,8 @@ class QuotaPlugin extends \Test\TestCase {
|
|||
*/
|
||||
public function testLength($expected, $headers) {
|
||||
$this->init(0);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||
$length = $this->plugin->getLength();
|
||||
$this->assertEquals($expected, $length);
|
||||
|
@ -61,6 +66,8 @@ class QuotaPlugin extends \Test\TestCase {
|
|||
*/
|
||||
public function testCheckQuota($quota, $headers) {
|
||||
$this->init($quota);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||
$result = $this->plugin->checkQuota('');
|
||||
|
@ -73,11 +80,26 @@ class QuotaPlugin extends \Test\TestCase {
|
|||
*/
|
||||
public function testCheckExceededQuota($quota, $headers) {
|
||||
$this->init($quota);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers);
|
||||
$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() {
|
||||
return 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
|
||||
$view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false);
|
||||
$view->expects($this->any())
|
||||
->method('free_space')
|
||||
->with($this->identicalTo(''))
|
||||
->with($this->identicalTo($checkedPath))
|
||||
->will($this->returnValue($quota));
|
||||
|
||||
return $view;
|
||||
|
|
|
@ -136,8 +136,12 @@ $maxUploadFileSize = $storageStats['uploadMaxFilesize'];
|
|||
$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
|
||||
|
||||
$totalSize = 0;
|
||||
foreach ($files['size'] as $size) {
|
||||
$totalSize += $size;
|
||||
$isReceivedShare = \OC::$server->getRequest()->getParam('isReceivedShare', false) === 'true';
|
||||
// defer quota check for received shares
|
||||
if (!$isReceivedShare) {
|
||||
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'),
|
||||
|
|
|
@ -251,7 +251,26 @@ OC.Upload = {
|
|||
$('#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() {
|
||||
var self = this;
|
||||
if ( $('#file_upload_start').exists() ) {
|
||||
var file_upload_param = {
|
||||
dropZone: $('#content'), // restrict dropZone to content div
|
||||
|
@ -341,10 +360,15 @@ OC.Upload = {
|
|||
}
|
||||
}
|
||||
|
||||
// add size
|
||||
selection.totalBytes += file.size;
|
||||
// update size of biggest file
|
||||
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
|
||||
// only count if we're not overwriting an existing shared file
|
||||
if (self._isReceivedSharedFile(file)) {
|
||||
file.isReceivedShare = true;
|
||||
} else {
|
||||
// add size
|
||||
selection.totalBytes += file.size;
|
||||
// update size of biggest file
|
||||
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
|
||||
}
|
||||
|
||||
// check PHP upload limit against biggest file
|
||||
if (selection.biggestFileBytes > $('#upload_limit').val()) {
|
||||
|
@ -430,11 +454,16 @@ OC.Upload = {
|
|||
fileDirectory = data.files[0].relativePath;
|
||||
}
|
||||
|
||||
addFormData(data.formData, {
|
||||
var params = {
|
||||
requesttoken: oc_requesttoken,
|
||||
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) {
|
||||
OC.Upload.log('fail', e, data);
|
||||
|
|
|
@ -141,16 +141,33 @@ class Quota extends Wrapper {
|
|||
*/
|
||||
public function fopen($path, $mode) {
|
||||
$source = $this->storage->fopen($path, $mode);
|
||||
$free = $this->free_space('');
|
||||
if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
|
||||
// only apply quota for files, not metadata, trash or others
|
||||
if (strpos(ltrim($path, '/'), 'files/') === 0) {
|
||||
return \OC\Files\Stream\Quota::wrap($source, $free);
|
||||
|
||||
// don't apply quota for part files
|
||||
if (!$this->isPartFile($path)) {
|
||||
$free = $this->free_space('');
|
||||
if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
|
||||
// only apply quota for files, not metadata, trash or others
|
||||
if (strpos(ltrim($path, '/'), 'files/') === 0) {
|
||||
return \OC\Files\Stream\Quota::wrap($source, $free);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 string $sourceInternalPath
|
||||
|
|
Loading…
Reference in New Issue