diff --git a/apps/dav/lib/connector/sabre/quotaplugin.php b/apps/dav/lib/connector/sabre/quotaplugin.php
index a02827da499718592529a8e92f435b31e02eb72f..b1c3bbfbbb937561245f0959a80463bf6e0e2b88 100644
--- a/apps/dav/lib/connector/sabre/quotaplugin.php
+++ b/apps/dav/lib/connector/sabre/quotaplugin.php
@@ -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());
diff --git a/apps/dav/tests/unit/connector/sabre/quotaplugin.php b/apps/dav/tests/unit/connector/sabre/quotaplugin.php
index cc4339ecc1a71e6e1fd89121466cdcf397d82c28..b5a8bfef31c6c432ec40b718c332d799a9b829fe 100644
--- a/apps/dav/tests/unit/connector/sabre/quotaplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/quotaplugin.php
@@ -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;
diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
index 5bf69d3e304d8a70bdcb58f4c98da996ef865a06..36aaed5ad9ed0c4661479b705a2cda3f45533cd3 100644
--- a/apps/files/ajax/upload.php
+++ b/apps/files/ajax/upload.php
@@ -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'),
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index d419cb3a2c08699997621171446f311f0a80d419..8ba294e2a7f451781b1e4a419338f5c4992ad087 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -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);
diff --git a/lib/private/files/storage/wrapper/quota.php b/lib/private/files/storage/wrapper/quota.php
index 844505679df0f4bc3076929dc207d967da30ddbf..500677b092e6337417ae6f5706c0538706f703cf 100644
--- a/lib/private/files/storage/wrapper/quota.php
+++ b/lib/private/files/storage/wrapper/quota.php
@@ -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