diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php
index d68397dcaa3226f7aa3e7cc55c1a2abbf56cb414..e85a67a87593c7ceed79677f8522d22344a2c39b 100644
--- a/apps/dav/lib/connector/sabre/filesplugin.php
+++ b/apps/dav/lib/connector/sabre/filesplugin.php
@@ -116,6 +116,7 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
 		$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
 		$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
 		$this->server->on('afterMethod:GET', [$this,'httpGet']);
+		$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
 		$this->server->on('afterResponse', function($request, ResponseInterface $response) {
 			$body = $response->getBody();
 			if (is_resource($body)) {
@@ -148,6 +149,32 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
 		}
 	}
 
+	/**
+	 * This sets a cookie to be able to recognize the start of the download
+	 * the content must not be longer than 32 characters and must only contain
+	 * alphanumeric characters
+	 *
+	 * @param RequestInterface $request
+	 * @param ResponseInterface $response
+	 */
+	function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
+		$queryParams = $request->getQueryParameters();
+
+		/**
+		 * this sets a cookie to be able to recognize the start of the download
+		 * the content must not be longer than 32 characters and must only contain
+		 * alphanumeric characters
+		 */
+		if (isset($queryParams['downloadStartSecret'])) {
+			$token = $queryParams['downloadStartSecret'];
+			if (!isset($token[32])
+				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
+				// FIXME: use $response->setHeader() instead
+				setcookie('ocDownloadStarted', $token, time() + 20, '/');
+			}
+		}
+	}
+
 	/**
 	 * Plugin that adds a 'Content-Disposition: attachment' header to all files
 	 * delivered by SabreDAV.
diff --git a/apps/files/ajax/delete.php b/apps/files/ajax/delete.php
deleted file mode 100644
index 2d02869df14f135c3d6fdffa0116af638a77dbef..0000000000000000000000000000000000000000
--- a/apps/files/ajax/delete.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * @author Arthur Schiwon <blizzz@owncloud.com>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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/>
- *
- */
-OCP\JSON::checkLoggedIn();
-OCP\JSON::callCheck();
-\OC::$server->getSession()->close();
-
-
-// Get data
-$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
-$allFiles = isset($_POST["allfiles"]) ? (string)$_POST["allfiles"] : false;
-
-// delete all files in dir ?
-if ($allFiles === 'true') {
-	$files = array();
-	$fileList = \OC\Files\Filesystem::getDirectoryContent($dir);
-	foreach ($fileList as $fileInfo) {
-		$files[] = $fileInfo['name'];
-	}
-} else {
-	$files = isset($_POST["file"]) ? (string)$_POST["file"] : (string)$_POST["files"];
-	$files = json_decode($files);
-}
-$filesWithError = '';
-
-$success = true;
-
-//Now delete
-foreach ($files as $file) {
-	try {
-		if (\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
-			!(\OC\Files\Filesystem::isDeletable($dir . '/' . $file) &&
-				\OC\Files\Filesystem::unlink($dir . '/' . $file))
-		) {
-			$filesWithError .= $file . "\n";
-			$success = false;
-		}
-	} catch (\Exception $e) {
-		$filesWithError .= $file . "\n";
-		$success = false;
-	}
-}
-
-// get array with updated storage stats (e.g. max file size) after upload
-try {
-	$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
-} catch(\OCP\Files\NotFoundException $e) {
-	OCP\JSON::error(['data' => ['message' => 'File not found']]);
-	return;
-}
-
-if ($success) {
-	OCP\JSON::success(array("data" => array_merge(array("dir" => $dir, "files" => $files), $storageStats)));
-} else {
-	OCP\JSON::error(array("data" => array_merge(array("message" => "Could not delete:\n" . $filesWithError), $storageStats)));
-}
diff --git a/apps/files/ajax/move.php b/apps/files/ajax/move.php
deleted file mode 100644
index 0961636a116bde8096b8e94574c62b210c74bc27..0000000000000000000000000000000000000000
--- a/apps/files/ajax/move.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * @author Björn Schießle <schiessle@owncloud.com>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Georg Ehrke <georg@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Robin Appelman <icewind@owncloud.com>
- * @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/>
- *
- */
-OCP\JSON::checkLoggedIn();
-OCP\JSON::callCheck();
-\OC::$server->getSession()->close();
-
-// Get data
-$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
-$file = isset($_POST['file']) ? (string)$_POST['file'] : '';
-$target = isset($_POST['target']) ? rawurldecode((string)$_POST['target']) : '';
-
-$l = \OC::$server->getL10N('files');
-
-if(\OC\Files\Filesystem::file_exists($target . '/' . $file)) {
-	OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s - File with this name already exists", array($file)) )));
-	exit;
-}
-
-if ($target != '' || strtolower($file) != 'shared') {
-	$targetFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file);
-	$sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file);
-	try {
-		if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
-			OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
-		} else {
-			OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
-		}
-	} catch (\OCP\Files\NotPermittedException $e) {
-		OCP\JSON::error(array("data" => array( "message" => $l->t("Permission denied") )));
-	} catch (\Exception $e) {
-		OCP\JSON::error(array("data" => array( "message" => $e->getMessage())));
-	}
-}else{
-	OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
-}
diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php
deleted file mode 100644
index be09b288d4b479b71205e8f4e756679ebaf91579..0000000000000000000000000000000000000000
--- a/apps/files/ajax/newfile.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-/**
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Georg Ehrke <georg@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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/>
- *
- */
-// Init owncloud
-global $eventSource;
-
-\OCP\JSON::checkLoggedIn();
-\OCP\JSON::callCheck();
-
-\OC::$server->getSession()->close();
-
-// Get the params
-$dir = isset( $_REQUEST['dir'] ) ? '/'.trim((string)$_REQUEST['dir'], '/\\') : '';
-$fileName = isset( $_REQUEST['filename'] ) ? trim((string)$_REQUEST['filename'], '/\\') : '';
-
-$l10n = \OC::$server->getL10N('files');
-
-$result = array(
-	'success' 	=> false,
-	'data'		=> NULL
-);
-
-try {
-	\OC\Files\Filesystem::getView()->verifyPath($dir, $fileName);
-} catch (\OCP\Files\InvalidPathException $ex) {
-	$result['data'] = [
-		'message' => $ex->getMessage()];
-	OCP\JSON::error($result);
-	return;
-}
-
-if (!\OC\Files\Filesystem::file_exists($dir . '/')) {
-	$result['data'] = array('message' => (string)$l10n->t(
-			'The target folder has been moved or deleted.'),
-			'code' => 'targetnotfound'
-		);
-	OCP\JSON::error($result);
-	exit();
-}
-
-$target = $dir.'/'.$fileName;
-
-if (\OC\Files\Filesystem::file_exists($target)) {
-	$result['data'] = array('message' => (string)$l10n->t(
-			'The name %s is already used in the folder %s. Please choose a different name.',
-			array($fileName, $dir))
-		);
-	OCP\JSON::error($result);
-	exit();
-}
-
-$success = false;
-$templateManager = OC_Helper::getFileTemplateManager();
-$mimeType = OC_Helper::getMimetypeDetector()->detectPath($target);
-$content = $templateManager->getTemplate($mimeType);
-
-try {
-	if($content) {
-		$success = \OC\Files\Filesystem::file_put_contents($target, $content);
-	} else {
-		$success = \OC\Files\Filesystem::touch($target);
-	}
-} catch (\Exception $e) {
-	$result = [
-		'success' => false,
-		'data' => [
-			'message' => $e->getMessage()
-		]
-	];
-	OCP\JSON::error($result);
-	exit();
-}
-
-if($success) {
-	$meta = \OC\Files\Filesystem::getFileInfo($target);
-	OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
-	return;
-}
-
-OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the file') )));
diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php
deleted file mode 100644
index a2897dd437ac798539937186f210f6d4ea4deabc..0000000000000000000000000000000000000000
--- a/apps/files/ajax/newfolder.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-/**
- * @author Arthur Schiwon <blizzz@owncloud.com>
- * @author Björn Schießle <schiessle@owncloud.com>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Georg Ehrke <georg@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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/>
- *
- */
-// Init owncloud
-
-
-OCP\JSON::checkLoggedIn();
-OCP\JSON::callCheck();
-\OC::$server->getSession()->close();
-
-// Get the params
-$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
-$folderName = isset($_POST['foldername']) ?(string) $_POST['foldername'] : '';
-
-$l10n = \OC::$server->getL10N('files');
-
-$result = array(
-	'success' 	=> false,
-	'data'		=> NULL
-	);
-
-try {
-	\OC\Files\Filesystem::getView()->verifyPath($dir, $folderName);
-} catch (\OCP\Files\InvalidPathException $ex) {
-	$result['data'] = [
-		'message' => $ex->getMessage()];
-	OCP\JSON::error($result);
-	return;
-}
-
-if (!\OC\Files\Filesystem::file_exists($dir . '/')) {
-	$result['data'] = array('message' => (string)$l10n->t(
-			'The target folder has been moved or deleted.'),
-			'code' => 'targetnotfound'
-		);
-	OCP\JSON::error($result);
-	exit();
-}
-
-$target = $dir . '/' . $folderName;
-		
-if (\OC\Files\Filesystem::file_exists($target)) {
-	$result['data'] = array('message' => $l10n->t(
-			'The name %s is already used in the folder %s. Please choose a different name.',
-			array($folderName, $dir))
-		);
-	OCP\JSON::error($result);
-	exit();
-}
-
-try {
-	if(\OC\Files\Filesystem::mkdir($target)) {
-		if ( $dir !== '/') {
-			$path = $dir.'/'.$folderName;
-		} else {
-			$path = '/'.$folderName;
-		}
-		$meta = \OC\Files\Filesystem::getFileInfo($path);
-		$meta['type'] = 'dir'; // missing ?!
-		OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
-		exit();
-	}
-} catch (\Exception $e) {
-	$result = [
-		'success' => false,
-		'data' => [
-			'message' => $e->getMessage()
-		]
-	];
-	OCP\JSON::error($result);
-	exit();
-}
-
-OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the folder') )));
diff --git a/apps/files/ajax/rename.php b/apps/files/ajax/rename.php
deleted file mode 100644
index a24a57b10464cc47e553ead57a6da09536c6f2ad..0000000000000000000000000000000000000000
--- a/apps/files/ajax/rename.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-/**
- * @author Christopher Schäpers <kondou@ts.unde.re>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <icewind@owncloud.com>
- * @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/>
- *
- */
-
-OCP\JSON::checkLoggedIn();
-OCP\JSON::callCheck();
-\OC::$server->getSession()->close();
-
-$l10n = \OC::$server->getL10N('files');
-
-$files = new \OCA\Files\App(
-	\OC\Files\Filesystem::getView(),
-	\OC::$server->getL10N('files')
-);
-try {
-	$result = $files->rename(
-		isset($_GET['dir']) ? (string)$_GET['dir'] : '',
-		isset($_GET['file']) ? (string)$_GET['file'] : '',
-		isset($_GET['newname']) ? (string)$_GET['newname'] : ''
-	);
-} catch (\Exception $e) {
-	$result = [
-		'success' => false,
-		'data' => [
-			'message' => $e->getMessage()
-		]
-	];
-}
-
-if($result['success'] === true){
-	OCP\JSON::success(['data' => $result['data']]);
-} else {
-	OCP\JSON::error(['data' => $result['data']]);
-}
diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
index a784642728fa47834f864cbbfa9b5e1918d3ec3d..18e9cfe6117b7d591f02c8ac1743f30d76d51d3c 100644
--- a/apps/files/ajax/upload.php
+++ b/apps/files/ajax/upload.php
@@ -41,7 +41,6 @@ OCP\JSON::setContentTypeHeader('text/plain');
 // If not, check the login.
 // If no token is sent along, rely on login only
 
-$allowedPermissions = \OCP\Constants::PERMISSION_ALL;
 $errorCode = null;
 
 $l = \OC::$server->getL10N('files');
@@ -60,8 +59,6 @@ if (empty($_POST['dirToken'])) {
 
 	\OC_User::setIncognitoMode(true);
 
-	// return only read permissions for public upload
-	$allowedPermissions = \OCP\Constants::PERMISSION_READ;
 	$publicDirectory = !empty($_POST['subdir']) ? (string)$_POST['subdir'] : '/';
 
 	$linkItem = OCP\Share::getShareByToken((string)$_POST['dirToken']);
@@ -207,7 +204,7 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) {
 						$data['originalname'] = $files['name'][$i];
 						$data['uploadMaxFilesize'] = $maxUploadFileSize;
 						$data['maxHumanFilesize'] = $maxHumanFileSize;
-						$data['permissions'] = $meta['permissions'] & $allowedPermissions;
+						$data['permissions'] = $meta['permissions'];
 						$data['directory'] = $returnedDir;
 						$result[] = $data;
 					}
@@ -234,7 +231,7 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) {
 				$data['originalname'] = $files['name'][$i];
 				$data['uploadMaxFilesize'] = $maxUploadFileSize;
 				$data['maxHumanFilesize'] = $maxHumanFileSize;
-				$data['permissions'] = $meta['permissions'] & $allowedPermissions;
+				$data['permissions'] = $meta['permissions'];
 				$data['directory'] = $returnedDir;
 				$result[] = $data;
 			}
diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index ba8bb62494e585608572702b0b36470dc4ae292b..4ab226f396851981cd0320b0267017611602eca4 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -8,7 +8,7 @@
 	<shipped>true</shipped>
 	<standalone/>
 	<default_enable/>
-	<version>1.3.0</version>
+	<version>1.4.0</version>
 	<types>
 		<filesystem/>
 	</types>
diff --git a/apps/files/controller/viewcontroller.php b/apps/files/controller/viewcontroller.php
index c274680e525ce276a8a98d5fd71c3d6bf483a35e..1d1a9111d192e1fd2e9c9e7b63cade440fb8cb5a 100644
--- a/apps/files/controller/viewcontroller.php
+++ b/apps/files/controller/viewcontroller.php
@@ -119,6 +119,8 @@ class ViewController extends Controller {
 	 * @throws \OCP\Files\NotFoundException
 	 */
 	public function index($dir = '', $view = '') {
+		$nav = new \OCP\Template('files', 'appnavigation', '');
+
 		// Load the files we need
 		\OCP\Util::addStyle('files', 'files');
 		\OCP\Util::addStyle('files', 'upload');
@@ -169,8 +171,6 @@ class ViewController extends Controller {
 		// FIXME: Make non static
 		$storageInfo = $this->getStorageInfo();
 
-		$nav = new \OCP\Template('files', 'appnavigation', '');
-
 		\OCA\Files\App::getNavigationManager()->add(
 			[
 				'id' => 'favorites',
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index f31770466fed598a00fbd6dd288e0f6ef3956140..ff505d417f1ecca396a25c1f5d9c8d525d3aee03 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -71,7 +71,8 @@
 					folderDropOptions: folderDropOptions,
 					fileActions: fileActions,
 					allowLegacyActions: true,
-					scrollTo: urlParams.scrollto
+					scrollTo: urlParams.scrollto,
+					filesClient: OC.Files.getClient()
 				}
 			);
 			this.files.initialize();
diff --git a/apps/files/js/favoritesplugin.js b/apps/files/js/favoritesplugin.js
index 417a32ef804b3a6cedd6b0bd62b35720108713e7..454a505c7bd68e60c79b708fed9190325c6e3d68 100644
--- a/apps/files/js/favoritesplugin.js
+++ b/apps/files/js/favoritesplugin.js
@@ -92,7 +92,7 @@
 			// folder in the files app instead of opening it directly
 			fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
 				OCA.Files.App.setActiveView('files', {silent: true});
-				OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
+				OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
 			});
 			fileActions.setDefault('dir', 'Open');
 			return fileActions;
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 6a767d48a289130376977c0fb224a0c70c4ff3c1..871a2149c883241f64292fd24bae4767f6eacf7e 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -575,7 +575,8 @@
 				},
 				actionHandler: function (filename, context) {
 					var dir = context.dir || context.fileList.getCurrentDirectory();
-					var url = context.fileList.getDownloadUrl(filename, dir);
+					var isDir = context.$file.attr('data-type') === 'dir';
+					var url = context.fileList.getDownloadUrl(filename, dir, isDir);
 
 					var downloadFileaction = $(context.$file).find('.fileactions .action-download');
 
@@ -611,10 +612,7 @@
 
 			this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
 				var dir = context.$file.attr('data-path') || context.fileList.getCurrentDirectory();
-				if (dir !== '/') {
-					dir = dir + '/';
-				}
-				context.fileList.changeDirectory(dir + filename);
+				context.fileList.changeDirectory(OC.joinPaths(dir, filename));
 			});
 
 			this.registerAction({
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index d1f68d98eab041951d1a368252afaa0372ea489f..672c39a8bb16f440982d758d64cb362ed151a6e3 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -22,11 +22,12 @@
 	 *
 	 * @param $el container element with existing markup for the #controls
 	 * and a table
-	 * @param [options] map of options, see other parameters
-	 * @param [options.scrollContainer] scrollable container, defaults to $(window)
-	 * @param [options.dragOptions] drag options, disabled by default
-	 * @param [options.folderDropOptions] folder drop options, disabled by default
-	 * @param [options.detailsViewEnabled=true] whether to enable details view
+	 * @param {Object} [options] map of options, see other parameters
+	 * @param {Object} [options.scrollContainer] scrollable container, defaults to $(window)
+	 * @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 {OC.Files.Client} [options.filesClient] files client to use
 	 */
 	var FileList = function($el, options) {
 		this.initialize($el, options);
@@ -73,6 +74,13 @@
 		 */
 		_detailsView: null,
 
+		/**
+		 * Files client instance
+		 *
+		 * @type OC.Files.Client
+		 */
+		filesClient: null,
+
 		/**
 		 * Whether the file list was initialized already.
 		 * @type boolean
@@ -92,10 +100,17 @@
 		 * Array of files in the current folder.
 		 * The entries are of file data.
 		 *
-		 * @type Array.<Object>
+		 * @type Array.<OC.Files.FileInfo>
 		 */
 		files: [],
 
+		/**
+		 * Current directory entry
+		 *
+		 * @type OC.Files.FileInfo
+		 */
+		dirInfo: null,
+
 		/**
 		 * File actions handler, defaults to OCA.Files.FileActions
 		 * @type OCA.Files.FileActions
@@ -149,7 +164,7 @@
 		 * When false, clicking on a table header will call reload().
 		 * When true, clicking on a table header will simply resort the list.
 		 */
-		_clientSideSort: false,
+		_clientSideSort: true,
 
 		/**
 		 * Current directory
@@ -170,6 +185,7 @@
 		 * @param options.dragOptions drag options, disabled by default
 		 * @param options.folderDropOptions folder drop options, disabled by default
 		 * @param options.scrollTo name of file to scroll to after the first load
+		 * @param {OC.Files.Client} [options.filesClient] files API client
 		 * @private
 		 */
 		initialize: function($el, options) {
@@ -185,6 +201,12 @@
 			if (options.folderDropOptions) {
 				this._folderDropOptions = options.folderDropOptions;
 			}
+			if (options.filesClient) {
+				this.filesClient = options.filesClient;
+			} else {
+				// default client if not specified
+				this.filesClient = OC.Files.getClient();
+			}
 
 			this.$el = $el;
 			if (options.id) {
@@ -209,6 +231,8 @@
 			this.files = [];
 			this._selectedFiles = {};
 			this._selectionSummary = new OCA.Files.FileSummary();
+			// dummy root dir info
+			this.dirInfo = new OC.Files.FileInfo({});
 
 			this.fileSummary = this._createSummary();
 
@@ -359,7 +383,7 @@
 				var highlightState = $tr.hasClass('highlighted');
 				$tr = self.updateRow(
 					$tr,
-					_.extend({isPreviewAvailable: true}, model.toJSON()),
+					model.toJSON(),
 					{updateSummary: true, silent: false, animate: true}
 				);
 				$tr.toggleClass('highlighted', highlightState);
@@ -618,7 +642,7 @@
 			};
 
 			OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
-			OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir), disableLoadingState);
+			OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir, true), disableLoadingState);
 			return false;
 		},
 
@@ -894,16 +918,39 @@
 				self.$el.closest('#app-content').trigger(jQuery.Event('apprendered'));
 			});
 		},
+
+		/**
+		 * Returns the icon URL matching the given file info
+		 *
+		 * @param {OC.Files.FileInfo} fileInfo file info
+		 *
+		 * @return {string} icon URL
+		 */
+		_getIconUrl: function(fileInfo) {
+			var mimeType = fileInfo.mimetype || 'application/octet-stream';
+			if (mimeType === 'httpd/unix-directory') {
+				// use default folder icon
+				if (fileInfo.mountType === 'shared' || fileInfo.mountType === 'shared-root') {
+					return OC.MimeType.getIconUrl('dir-shared');
+				} else if (fileInfo.mountType === 'external-root') {
+					return OC.MimeType.getIconUrl('dir-external');
+				}
+				return OC.MimeType.getIconUrl('dir');
+			}
+			return OC.MimeType.getIconUrl(mimeType);
+		},
+
 		/**
 		 * Creates a new table row element using the given file data.
-		 * @param {OCA.Files.FileInfo} fileData file info attributes
+		 * @param {OC.Files.FileInfo} fileData file info attributes
 		 * @param options map of attributes
 		 * @return new tr element (not appended to the table)
 		 */
 		_createRow: function(fileData, options) {
 			var td, simpleSize, basename, extension, sizeColor,
-				icon = OC.MimeType.getIconUrl(fileData.mimetype),
+				icon = fileData.icon || this._getIconUrl(fileData),
 				name = fileData.name,
+				// TODO: get rid of type, only use mime type
 				type = fileData.type || 'file',
 				mtime = parseInt(fileData.mtime, 10),
 				mime = fileData.mimetype,
@@ -943,6 +990,14 @@
 			}
 
 			if (fileData.mountType) {
+				// FIXME: HACK: detect shared-root
+				if (fileData.mountType === 'shared' && this.dirInfo.mountType !== 'shared') {
+					// if parent folder isn't share, assume the displayed folder is a share root
+					fileData.mountType = 'shared-root';
+				} else if (fileData.mountType === 'external' && this.dirInfo.mountType !== 'external') {
+					// if parent folder isn't external, assume the displayed folder is the external storage root
+					fileData.mountType = 'external-root';
+				}
 				tr.attr('data-mounttype', fileData.mountType);
 			}
 
@@ -953,24 +1008,16 @@
 				path = this.getCurrentDirectory();
 			}
 
-			if (type === 'dir') {
-				// use default folder icon
-				icon = icon || OC.imagePath('core', 'filetypes/folder');
-			}
-			else {
-				icon = icon || OC.imagePath('core', 'filetypes/file');
-			}
-
 			// filename td
 			td = $('<td class="filename"></td>');
 
 
 			// linkUrl
-			if (type === 'dir') {
+			if (mime === 'httpd/unix-directory') {
 				linkUrl = this.linkTo(path + '/' + name);
 			}
 			else {
-				linkUrl = this.getDownloadUrl(name, path);
+				linkUrl = this.getDownloadUrl(name, path, type === 'dir');
 			}
 			if (this._allowSelection) {
 				td.append(
@@ -996,7 +1043,7 @@
 				basename = '';
 				extension = name;
 			// split extension from filename for non dirs
-			} else if (type !== 'dir' && name.indexOf('.') !== -1) {
+			} else if (mime !== 'httpd/unix-directory' && name.indexOf('.') !== -1) {
 				basename = name.substr(0, name.lastIndexOf('.'));
 				extension = name.substr(name.lastIndexOf('.'));
 			} else {
@@ -1018,7 +1065,7 @@
 				nameSpan.tooltip({placement: 'right'});
 			}
 			// dirs can show the number of uploaded files
-			if (type === 'dir') {
+			if (mime !== 'httpd/unix-directory') {
 				linkElem.append($('<span></span>').attr({
 					'class': 'uploadtext',
 					'currentUploads': 0
@@ -1074,7 +1121,7 @@
 		 * Adds an entry to the files array and also into the DOM
 		 * in a sorted manner.
 		 *
-		 * @param {OCA.Files.FileInfo} fileData map of file attributes
+		 * @param {OC.Files.FileInfo} fileData map of file attributes
 		 * @param {Object} [options] map of attributes
 		 * @param {boolean} [options.updateSummary] true to update the summary
 		 * after adding (default), false otherwise. Defaults to true.
@@ -1147,7 +1194,7 @@
 		 * Creates a new row element based on the given attributes
 		 * and returns it.
 		 *
-		 * @param {OCA.Files.FileInfo} fileData map of file attributes
+		 * @param {OC.Files.FileInfo} fileData map of file attributes
 		 * @param {Object} [options] map of attributes
 		 * @param {int} [options.index] index at which to insert the element
 		 * @param {boolean} [options.updateSummary] true to update the summary
@@ -1182,7 +1229,7 @@
 				filenameTd.draggable(this._dragOptions);
 			}
 			// allow dropping on folders
-			if (this._folderDropOptions && fileData.type === 'dir') {
+			if (this._folderDropOptions && mime === 'httpd/unix-directory') {
 				filenameTd.droppable(this._folderDropOptions);
 			}
 
@@ -1193,7 +1240,7 @@
 			// display actions
 			this.fileActions.display(filenameTd, !options.silent, this);
 
-			if (fileData.isPreviewAvailable && mime !== 'httpd/unix-directory') {
+			if (mime !== 'httpd/unix-directory') {
 				var iconDiv = filenameTd.find('.thumbnail');
 				// lazy load / newly inserted td ?
 				// the typeof check ensures that the default value of animate is true
@@ -1329,6 +1376,13 @@
 			}
 		},
 
+		/**
+		 * Returns list of webdav properties to request
+		 */
+		_getWebdavProperties: function() {
+			return this.filesClient.getPropfindProperties();
+		},
+
 		/**
 		 * Reloads the file list using ajax call
 		 *
@@ -1343,17 +1397,12 @@
 			this._currentFileModel = null;
 			this.$el.find('.select-all').prop('checked', false);
 			this.showMask();
-			if (this._reloadCall) {
-				this._reloadCall.abort();
-			}
-			this._reloadCall = $.ajax({
-				url: this.getAjaxUrl('list'),
-				data: {
-					dir : this.getCurrentDirectory(),
-					sort: this._sort,
-					sortdirection: this._sortDirection
+			this._reloadCall = this.filesClient.getFolderContents(
+				this.getCurrentDirectory(), {
+					includeParent: true,
+					properties: this._getWebdavProperties()
 				}
-			});
+			);
 			if (this._detailsView) {
 				// close sidebar
 				this._updateDetailsView(null);
@@ -1361,24 +1410,19 @@
 			var callBack = this.reloadCallback.bind(this);
 			return this._reloadCall.then(callBack, callBack);
 		},
-		reloadCallback: function(result) {
+		reloadCallback: function(status, result) {
 			delete this._reloadCall;
 			this.hideMask();
 
-			if (!result || result.status === 'error') {
-				// if the error is not related to folder we're trying to load, reload the page to handle logout etc
-				if (result.data.error === 'authentication_error' ||
-					result.data.error === 'token_expired' ||
-					result.data.error === 'application_not_enabled'
-				) {
-					OC.redirect(OC.generateUrl('apps/files'));
-				}
-				OC.Notification.showTemporary(result.data.message);
+			if (status === 401) {
+				// TODO: append current URL to be able to get back after logging in again
+				OC.redirect(OC.generateUrl('apps/files'));
+				OC.Notification.show(result);
 				return false;
 			}
 
 			// Firewall Blocked request?
-			if (result.status === 403) {
+			if (status === 403) {
 				// Go home
 				this.changeDirectory('/');
 				OC.Notification.showTemporary(t('files', 'This operation is forbidden'));
@@ -1386,32 +1430,49 @@
 			}
 
 			// Did share service die or something else fail?
-			if (result.status === 500) {
+			if (status === 500) {
 				// Go home
 				this.changeDirectory('/');
-				OC.Notification.showTemporary(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
+				OC.Notification.showTemporary(
+					t('files', 'This directory is unavailable, please check the logs or contact the administrator')
+				);
+				return false;
+			}
+
+			if (status === 503) {
+				// Go home
+				if (this.getCurrentDirectory() !== '/') {
+					this.changeDirectory('/');
+					// TODO: read error message from exception
+					OC.Notification.showTemporary(
+						t('files', 'Storage not available')
+					);
+				}
 				return false;
 			}
 
-			if (result.status === 404) {
+			if (status === 404) {
 				// go back home
 				this.changeDirectory('/');
 				return false;
 			}
 			// aborted ?
-			if (result.status === 0){
+			if (status === 0){
 				return true;
 			}
 
-			// TODO: should rather return upload file size through
-			// the files list ajax call
+			// TODO: parse remaining quota from PROPFIND response
 			this.updateStorageStatistics(true);
 
-			if (result.data.permissions) {
-				this.setDirectoryPermissions(result.data.permissions);
+			// first entry is the root
+			this.dirInfo = result.shift();
+
+			if (this.dirInfo.permissions) {
+				this.setDirectoryPermissions(this.dirInfo.permissions);
 			}
 
-			this.setFiles(result.data.files);
+			result.sort(this._sortComparator);
+			this.setFiles(result);
 			return true;
 		},
 
@@ -1419,12 +1480,15 @@
 			OCA.Files.Files.updateStorageStatistics(this.getCurrentDirectory(), force);
 		},
 
+		/**
+		 * @deprecated do not use nor override
+		 */
 		getAjaxUrl: function(action, params) {
 			return OCA.Files.Files.getAjaxUrl(action, params);
 		},
 
-		getDownloadUrl: function(files, dir) {
-			return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory());
+		getDownloadUrl: function(files, dir, isDir) {
+			return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory(), isDir);
 		},
 
 		/**
@@ -1489,8 +1553,6 @@
 			if (etag){
 				// use etag as cache buster
 				urlSpec.c = etag;
-			} else {
-				console.warn('OCA.Files.FileList.lazyLoadPreview(): missing etag argument');
 			}
 
 			previewURL = self.generatePreviewUrl(urlSpec);
@@ -1514,6 +1576,9 @@
 			img.src = previewURL;
 		},
 
+		/**
+		 * @deprecated
+		 */
 		setDirectoryPermissions: function(permissions) {
 			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
 			this.$el.find('#permissions').val(permissions);
@@ -1610,7 +1675,7 @@
 		 * fileData should be inserted, considering the current
 		 * sorting
 		 *
-		 * @param {OCA.Files.FileInfo} fileData file info
+		 * @param {OC.Files.FileInfo} fileData file info
 		 */
 		_findInsertionIndex: function(fileData) {
 			var index = 0;
@@ -1628,6 +1693,9 @@
 		move: function(fileNames, targetPath) {
 			var self = this;
 			var dir = this.getCurrentDirectory();
+			if (dir.charAt(dir.length - 1) !== '/') {
+				dir += '/';
+			}
 			var target = OC.basename(targetPath);
 			if (!_.isArray(fileNames)) {
 				fileNames = [fileNames];
@@ -1635,46 +1703,42 @@
 			_.each(fileNames, function(fileName) {
 				var $tr = self.findFileEl(fileName);
 				self.showFileBusyState($tr, true);
-				// TODO: improve performance by sending all file names in a single call
-				$.post(
-					OC.filePath('files', 'ajax', 'move.php'),
-					{
-						dir: dir,
-						file: fileName,
-						target: targetPath
-					},
-					function(result) {
-						if (result) {
-							if (result.status === 'success') {
-								// if still viewing the same directory
-								if (self.getCurrentDirectory() === dir) {
-									// recalculate folder size
-									var oldFile = self.findFileEl(target);
-									var newFile = self.findFileEl(fileName);
-									var oldSize = oldFile.data('size');
-									var newSize = oldSize + newFile.data('size');
-									oldFile.data('size', newSize);
-									oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
-
-									// TODO: also update entry in FileList.files
-
-									self.remove(fileName);
-								}
-							} else {
-								OC.Notification.hide();
-								if (result.status === 'error' && result.data.message) {
-									OC.Notification.showTemporary(result.data.message);
-								}
-								else {
-									OC.Notification.showTemporary(t('files', 'Error moving file.'));
-								}
-							}
+				if (targetPath.charAt(targetPath.length - 1) !== '/') {
+					// make sure we move the files into the target dir,
+					// not overwrite it
+					targetPath = targetPath + '/';
+				}
+				self.filesClient.move(dir + fileName, targetPath + fileName)
+					.done(function() {
+						// if still viewing the same directory
+						if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
+							// recalculate folder size
+							var oldFile = self.findFileEl(target);
+							var newFile = self.findFileEl(fileName);
+							var oldSize = oldFile.data('size');
+							var newSize = oldSize + newFile.data('size');
+							oldFile.data('size', newSize);
+							oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
+
+							// TODO: also update entry in FileList.files
+							self.remove(fileName);
+						}
+					})
+					.fail(function(status) {
+						if (status === 412) {
+							// TODO: some day here we should invoke the conflict dialog
+							OC.Notification.showTemporary(
+								t('files', 'Could not move "{file}", target exists', {file: fileName})
+							);
 						} else {
-							OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
+							OC.Notification.showTemporary(
+								t('files', 'Could not move "{file}"', {file: fileName})
+							);
 						}
+					})
+					.always(function() {
 						self.showFileBusyState($tr, false);
-					}
-				);
+					});
 			});
 
 		},
@@ -1700,16 +1764,16 @@
 		 * Triggers file rename input field for the given file name.
 		 * If the user enters a new name, the file will be renamed.
 		 *
-		 * @param oldname file name of the file to rename
+		 * @param oldName file name of the file to rename
 		 */
-		rename: function(oldname) {
+		rename: function(oldName) {
 			var self = this;
 			var tr, td, input, form;
-			tr = this.findFileEl(oldname);
+			tr = this.findFileEl(oldName);
 			var oldFileInfo = this.files[tr.index()];
 			tr.data('renaming',true);
 			td = tr.children('td.filename');
-			input = $('<input type="text" class="filename"/>').val(oldname);
+			input = $('<input type="text" class="filename"/>').val(oldName);
 			form = $('<form></form>');
 			form.append(input);
 			td.children('a.name').hide();
@@ -1724,11 +1788,11 @@
 			input.selectRange(0, len);
 			var checkInput = function () {
 				var filename = input.val();
-				if (filename !== oldname) {
+				if (filename !== oldName) {
 					// Files.isFileNameValid(filename) throws an exception itself
 					OCA.Files.Files.isFileNameValid(filename);
 					if (self.inList(filename)) {
-						throw t('files', '{new_name} already exists', {new_name: filename});
+						throw t('files', '{newName} already exists', {newName: filename});
 					}
 				}
 				return true;
@@ -1741,6 +1805,12 @@
 				td.children('a.name').show();
 			}
 
+			function updateInList(fileInfo) {
+				self.updateRow(tr, fileInfo);
+				self._updateDetailsView(fileInfo.name, false);
+			}
+
+			// TODO: too many nested blocks, move parts into functions
 			form.submit(function(event) {
 				event.stopPropagation();
 				event.preventDefault();
@@ -1753,7 +1823,7 @@
 					input.tooltip('hide');
 					form.remove();
 
-					if (newName !== oldname) {
+					if (newName !== oldName) {
 						checkInput();
 						// mark as loading (temp element)
 						self.showFileBusyState(tr, true);
@@ -1765,34 +1835,45 @@
 						td.find('a.name span.nametext').text(basename);
 						td.children('a.name').show();
 
-						$.ajax({
-							url: OC.filePath('files','ajax','rename.php'),
-							data: {
-								dir : tr.attr('data-path') || self.getCurrentDirectory(),
-								newname: newName,
-								file: oldname
-							},
-							success: function(result) {
-								var fileInfo;
-								if (!result || result.status === 'error') {
-									OC.dialogs.alert(result.data.message, t('files', 'Could not rename file'));
-									fileInfo = oldFileInfo;
-									if (result.data.code === 'sourcenotfound') {
-										self.remove(result.data.newname, {updateSummary: true});
-										return;
-									}
-								}
-								else {
-									fileInfo = result.data;
+						var path = tr.attr('data-path') || self.getCurrentDirectory();
+						self.filesClient.move(OC.joinPaths(path, oldName), OC.joinPaths(path, newName))
+							.done(function() {
+								oldFileInfo.name = newName;
+								updateInList(oldFileInfo);
+							})
+							.fail(function(status) {
+								// TODO: 409 means current folder does not exist, redirect ?
+								if (status === 404) {
+									// source not found, so remove it from the list
+									OC.Notification.showTemporary(
+										t(
+											'files',
+											'Could not rename "{fileName}", it does not exist any more',
+											{fileName: oldName}
+										)
+									);
+									self.remove(newName, {updateSummary: true});
+									return;
+								} else if (status === 412) {
+									// target exists
+									OC.Notification.showTemporary(
+										t(
+											'files',
+											'The name "{targetName}" is already used in the folder "{dir}". Please choose a different name.',
+											{
+												targetName: newName,
+												dir: self.getCurrentDirectory()
+											}
+										)
+									);
+								} else {
+									// restore the item to its previous state
+									OC.Notification.showTemporary(
+										t('files', 'Could not rename "{fileName}"', {fileName: oldName})
+									);
 								}
-								// reinsert row
-								self.files.splice(tr.index(), 1);
-								tr.remove();
-								tr = self.add(fileInfo, {updateSummary: false, silent: true});
-								self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)}));
-								self._updateDetailsView(fileInfo.name, false);
-							}
-						});
+								updateInList(oldFileInfo);
+							});
 					} else {
 						// add back the old file info when cancelled
 						self.files.splice(tr.index(), 1);
@@ -1849,32 +1930,48 @@
 			var promise = deferred.promise();
 
 			OCA.Files.Files.isFileNameValid(name);
-			name = this.getUniqueName(name);
 
 			if (this.lastAction) {
 				this.lastAction();
 			}
 
-			$.post(
-				OC.generateUrl('/apps/files/ajax/newfile.php'),
-				{
-					dir: this.getCurrentDirectory(),
-					filename: name
-				},
-				function(result) {
-					if (result.status === 'success') {
-						self.add(result.data, {animate: true, scrollTo: true});
-						deferred.resolve(result.status, result.data);
+			name = this.getUniqueName(name);
+			var targetPath = this.getCurrentDirectory() + '/' + name;
+
+			self.filesClient.putFileContents(
+					targetPath,
+					'',
+					{
+						contentType: 'text/plain',
+						overwrite: true
+					}
+				)
+				.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);
+						});
+				})
+				.fail(function(status) {
+					if (status === 412) {
+						OC.Notification.showTemporary(
+							t('files', 'Could not create file "{file}" because it already exists', {file: name})
+						);
 					} else {
-						if (result.data && result.data.message) {
-							OC.Notification.showTemporary(result.data.message);
-						} else {
-							OC.Notification.showTemporary(t('core', 'Could not create file'));
-						}
-						deferred.reject(result.status, result.data);
+						OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
 					}
-				}
-			);
+					deferred.reject(status);
+				});
 
 			return promise;
 		},
@@ -1895,32 +1992,58 @@
 			var promise = deferred.promise();
 
 			OCA.Files.Files.isFileNameValid(name);
-			name = this.getUniqueName(name);
 
 			if (this.lastAction) {
 				this.lastAction();
 			}
 
-			$.post(
-				OC.generateUrl('/apps/files/ajax/newfolder.php'),
-				{
-					dir: this.getCurrentDirectory(),
-					foldername: name
-				},
-				function(result) {
-					if (result.status === 'success') {
-						self.add(result.data, {animate: true, scrollTo: true});
-						deferred.resolve(result.status, result.data);
+			name = this.getUniqueName(name);
+			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);
+						});
+				})
+				.fail(function(createStatus) {
+					// method not allowed, folder might exist already
+					if (createStatus === 405) {
+						self.filesClient.getFileInfo(
+								targetPath, {
+									properties: self._getWebdavProperties()
+								}
+							)
+							.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})
+								);
+								// still consider a failure
+								deferred.reject(createStatus, data);
+							})
+							.fail(function() {
+								OC.Notification.showTemporary(
+									t('files', 'Could not create folder "{dir}"', {dir: name})
+								);
+								deferred.reject(status);
+							});
 					} else {
-						if (result.data && result.data.message) {
-							OC.Notification.showTemporary(result.data.message);
-						} else {
-							OC.Notification.showTemporary(t('core', 'Could not create folder'));
-						}
-						deferred.reject(result.status);
+						OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
+						deferred.reject(createStatus);
 					}
-				}
-			);
+				});
 
 			return promise;
 		},
@@ -1981,76 +2104,59 @@
 		 */
 		do_delete:function(files, dir) {
 			var self = this;
-			var params;
 			if (files && files.substr) {
 				files=[files];
 			}
+			if (!files) {
+				// delete all files in directory
+				files = _.pluck(this.files, 'name');
+			}
 			if (files) {
 				this.showFileBusyState(files, true);
-				for (var i=0; i<files.length; i++) {
-				}
 			}
 			// Finish any existing actions
 			if (this.lastAction) {
 				this.lastAction();
 			}
 
-			params = {
-				dir: dir || this.getCurrentDirectory()
-			};
-			if (files) {
-				params.files = JSON.stringify(files);
+			dir = dir || this.getCurrentDirectory();
+
+			function removeFromList(file) {
+				var fileEl = self.remove(file, {updateSummary: false});
+				// FIXME: not sure why we need this after the
+				// element isn't even in the DOM any more
+				fileEl.find('.selectCheckBox').prop('checked', false);
+				fileEl.removeClass('selected');
+				self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
+				// TODO: this info should be returned by the ajax call!
+				self.updateEmptyContent();
+				self.fileSummary.update();
+				self.updateSelectionSummary();
+				// FIXME: don't repeat this, do it once all files are done
+				self.updateStorageStatistics();
 			}
-			else {
-				// no files passed, delete all in current dir
-				params.allfiles = true;
-				// show spinner for all files
-				this.showFileBusyState(this.$fileList.find('tr'), true);
-			}
-
-			$.post(OC.filePath('files', 'ajax', 'delete.php'),
-					params,
-					function(result) {
-						if (result.status === 'success') {
-							if (params.allfiles) {
-								self.setFiles([]);
-							}
-							else {
-								$.each(files,function(index,file) {
-									var fileEl = self.remove(file, {updateSummary: false});
-									// FIXME: not sure why we need this after the
-									// element isn't even in the DOM any more
-									fileEl.find('.selectCheckBox').prop('checked', false);
-									fileEl.removeClass('selected');
-									self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
-								});
-							}
-							// TODO: this info should be returned by the ajax call!
-							self.updateEmptyContent();
-							self.fileSummary.update();
-							self.updateSelectionSummary();
-							self.updateStorageStatistics();
-							// in case there was a "storage full" permanent notification
-							OC.Notification.hide();
+
+			_.each(files, function(file) {
+				self.filesClient.remove(dir + '/' + file)
+					.done(function() {
+						removeFromList(file);
+					})
+					.fail(function(status) {
+						if (status === 404) {
+							// the file already did not exist, remove it from the list
+							removeFromList(file);
 						} else {
-							if (result.status === 'error' && result.data.message) {
-								OC.Notification.showTemporary(result.data.message);
-							}
-							else {
-								OC.Notification.showTemporary(t('files', 'Error deleting file.'));
-							}
-							if (params.allfiles) {
-								// reload the page as we don't know what files were deleted
-								// and which ones remain
-								self.reload();
-							}
-							else {
-								$.each(files,function(index,file) {
-									self.showFileBusyState(file, false);
-								});
-							}
+							// only reset the spinner for that one file
+							OC.Notification.showTemporary(
+									t('files', 'Error deleting file "{fileName}".', {fileName: file}),
+									{timeout: 10}
+							);
+							var deleteAction = self.findFileEl(file).find('.action.delete');
+							deleteAction.removeClass('icon-loading-small').addClass('icon-delete');
+							self.showFileBusyState(files, false);
 						}
 					});
+			});
 		},
 		/**
 		 * Creates the file summary section
@@ -2659,8 +2765,8 @@
 		 * Compares two file infos by name, making directories appear
 		 * first.
 		 *
-		 * @param {OCA.Files.FileInfo} fileInfo1 file info
-		 * @param {OCA.Files.FileInfo} fileInfo2 file info
+		 * @param {OC.Files.FileInfo} fileInfo1 file info
+		 * @param {OC.Files.FileInfo} fileInfo2 file info
 		 * @return {int} -1 if the first file must appear before the second one,
 		 * 0 if they are identify, 1 otherwise.
 		 */
@@ -2676,8 +2782,8 @@
 		/**
 		 * Compares two file infos by size.
 		 *
-		 * @param {OCA.Files.FileInfo} fileInfo1 file info
-		 * @param {OCA.Files.FileInfo} fileInfo2 file info
+		 * @param {OC.Files.FileInfo} fileInfo1 file info
+		 * @param {OC.Files.FileInfo} fileInfo2 file info
 		 * @return {int} -1 if the first file must appear before the second one,
 		 * 0 if they are identify, 1 otherwise.
 		 */
@@ -2687,8 +2793,8 @@
 		/**
 		 * Compares two file infos by timestamp.
 		 *
-		 * @param {OCA.Files.FileInfo} fileInfo1 file info
-		 * @param {OCA.Files.FileInfo} fileInfo2 file info
+		 * @param {OC.Files.FileInfo} fileInfo1 file info
+		 * @param {OC.Files.FileInfo} fileInfo2 file info
 		 * @return {int} -1 if the first file must appear before the second one,
 		 * 0 if they are identify, 1 otherwise.
 		 */
@@ -2700,23 +2806,14 @@
 	/**
 	 * File info attributes.
 	 *
-	 * @todo make this a real class in the future
-	 * @typedef {Object} OCA.Files.FileInfo
+	 * @typedef {Object} OC.Files.FileInfo
+	 *
+	 * @lends OC.Files.FileInfo
+	 *
+	 * @deprecated use OC.Files.FileInfo instead
 	 *
-	 * @property {int} id file id
-	 * @property {String} name file name
-	 * @property {String} [path] file path, defaults to the list's current path
-	 * @property {String} mimetype mime type
-	 * @property {String} type "file" for files or "dir" for directories
-	 * @property {int} permissions file permissions
-	 * @property {int} mtime modification time in milliseconds
-	 * @property {boolean} [isShareMountPoint] whether the file is a share mount
-	 * point
-	 * @property {boolean} [isPreviewAvailable] whether a preview is available
-	 * for the given file type
-	 * @property {String} [icon] path to the mime type icon
-	 * @property {String} etag etag of the file
 	 */
+	OCA.Files.FileInfo = OC.Files.FileInfo;
 
 	OCA.Files.FileList = FileList;
 })();
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index ae38511ec05789cc060e91e3f0dbdb9dac881118..e33b835443708bf70cd4021e12fe8da42b1a52b5 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -136,13 +136,27 @@
 
 		/**
 		 * Returns the download URL of the given file(s)
-		 * @param filename string or array of file names to download
-		 * @param dir optional directory in which the file name is, defaults to the current directory
+		 * @param {string} filename string or array of file names to download
+		 * @param {string} [dir] optional directory in which the file name is, defaults to the current directory
+		 * @param {bool} [isDir=false] whether the given filename is a directory and might need a special URL
 		 */
-		getDownloadUrl: function(filename, dir) {
-			if ($.isArray(filename)) {
+		getDownloadUrl: function(filename, dir, isDir) {
+			if (!_.isArray(filename) && !isDir) {
+				var pathSections = dir.split('/');
+				pathSections.push(filename);
+				var encodedPath = '';
+				_.each(pathSections, function(section) {
+					if (section !== '') {
+						encodedPath += '/' + encodeURIComponent(section);
+					}
+				});
+				return OC.linkToRemoteBase('webdav') + encodedPath;
+			}
+
+			if (_.isArray(filename)) {
 				filename = JSON.stringify(filename);
 			}
+
 			var params = {
 				dir: dir,
 				files: filename
@@ -193,7 +207,7 @@
 		 */
 		lazyLoadPreview : function(path, mime, ready, width, height, etag) {
 			console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
-			return OCA.Files.App.fileList.lazyLoadPreview({
+			return FileList.lazyLoadPreview({
 				path: path,
 				mime: mime,
 				callback: ready,
@@ -356,8 +370,10 @@ scanFiles.scanning=false;
 
 // TODO: move to FileList
 var createDragShadow = function(event) {
+	// FIXME: inject file list instance somehow
+	/* global FileList, Files */
+
 	//select dragged file
-	var FileList = OCA.Files.App.fileList;
 	var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
 	if (!isDragSelected) {
 		//select dragged file
@@ -394,7 +410,7 @@ var createDragShadow = function(event) {
 				.css('background-image', 'url(' + OC.imagePath('core', 'filetypes/folder.png') + ')');
 		} else {
 			var path = dir + '/' + elem.name;
-			OCA.Files.App.files.lazyLoadPreview(path, elem.mime, function(previewpath) {
+			Files.lazyLoadPreview(path, elem.mimetype, function(previewpath) {
 				newtr.find('td.filename')
 					.css('background-image', 'url(' + previewpath + ')');
 			}, null, null, elem.etag);
@@ -441,7 +457,7 @@ var folderDropOptions = {
 	hoverClass: "canDrop",
 	drop: function( event, ui ) {
 		// don't allow moving a file into a selected folder
-		var FileList = OCA.Files.App.fileList;
+		/* global FileList */
 		if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
 			return false;
 		}
diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js
index 23945d52603b9d624edb85610b76345fc6b517c0..81b22e34cc25724afe036a1741bff02c796ce0a6 100644
--- a/apps/files/js/tagsplugin.js
+++ b/apps/files/js/tagsplugin.js
@@ -161,6 +161,38 @@
 				fileInfo.tags = tags;
 				return fileInfo;
 			};
+
+			var NS_OC = 'http://owncloud.org/ns';
+
+			var oldGetWebdavProperties = fileList._getWebdavProperties;
+			fileList._getWebdavProperties = function() {
+				var props = oldGetWebdavProperties.apply(this, arguments);
+				props.push('{' + NS_OC + '}tags');
+				props.push('{' + NS_OC + '}favorite');
+				return props;
+			};
+
+			fileList.filesClient.addFileInfoParser(function(response) {
+				var data = {};
+				var props = response.propStat[0].properties;
+				var tags = props['{' + NS_OC + '}tags'];
+				var favorite = props['{' + NS_OC + '}favorite'];
+				if (tags && tags.length) {
+					tags = _.chain(tags).filter(function(xmlvalue) {
+						return (xmlvalue.namespaceURI === NS_OC && xmlvalue.nodeName.split(':')[1] === 'tag');
+					}).map(function(xmlvalue) {
+						return xmlvalue.textContent || xmlvalue.text;
+					}).value();
+				}
+				if (tags) {
+					data.tags = tags;
+				}
+				if (favorite && parseInt(favorite, 10) !== 0) {
+					data.tags = data.tags || [];
+					data.tags.push(OC.TAG_FAVORITE);
+				}
+				return data;
+			});
 		},
 
 		attach: function(fileList) {
diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php
index 5bd8c1275861856eaecbfed0b683232ff641fa9d..18813e224de1ca43b0fa526eae8cfba74da7ade4 100644
--- a/apps/files/lib/app.php
+++ b/apps/files/lib/app.php
@@ -28,108 +28,22 @@
 namespace OCA\Files;
 
 class App {
-	/**
-	 * @var \OC_L10N
-	 */
-	private $l10n;
-
 	/**
 	 * @var \OCP\INavigationManager
 	 */
 	private static $navigationManager;
 
-	/**
-	 * @var \OC\Files\View
-	 */
-	private $view;
-
-	public function __construct($view, $l10n) {
-		$this->view = $view;
-		$this->l10n = $l10n;
-	}
-
 	/**
 	 * Returns the app's navigation manager
 	 *
 	 * @return \OCP\INavigationManager
 	 */
 	public static function getNavigationManager() {
+		// TODO: move this into a service in the Application class
 		if (self::$navigationManager === null) {
 			self::$navigationManager = new \OC\NavigationManager();
 		}
 		return self::$navigationManager;
 	}
 
-	/**
-	 * rename a file
-	 *
-	 * @param string $dir
-	 * @param string $oldname
-	 * @param string $newname
-	 * @return array
-	 */
-	public function rename($dir, $oldname, $newname) {
-		$result = array(
-			'success' 	=> false,
-			'data'		=> NULL
-		);
-
-		try {
-			// check if the new name is conform to file name restrictions
-			$this->view->verifyPath($dir, $newname);
-		} catch (\OCP\Files\InvalidPathException $ex) {
-			$result['data'] = array(
-				'message'	=> $this->l10n->t($ex->getMessage()),
-				'code' => 'invalidname',
-			);
-			return $result;
-		}
-
-		$normalizedOldPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $oldname);
-		$normalizedNewPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname);
-
-		// rename to non-existing folder is denied
-		if (!$this->view->file_exists($normalizedOldPath)) {
-			$result['data'] = array(
-				'message'	=> $this->l10n->t('%s could not be renamed as it has been deleted', array($oldname)),
-				'code' => 'sourcenotfound',
-				'oldname' => $oldname,
-				'newname' => $newname,
-			);
-		}else if (!$this->view->file_exists($dir)) {
-			$result['data'] = array('message' => (string)$this->l10n->t(
-					'The target folder has been moved or deleted.',
-					array($dir)),
-					'code' => 'targetnotfound'
-				);
-		// rename to existing file is denied
-		} else if ($this->view->file_exists($normalizedNewPath)) {
-
-			$result['data'] = array(
-				'message'	=> $this->l10n->t(
-						"The name %s is already used in the folder %s. Please choose a different name.",
-						array($newname, $dir))
-			);
-		} else if (
-			// rename to "." is denied
-			$newname !== '.' and
-			// THEN try to rename
-			$this->view->rename($normalizedOldPath, $normalizedNewPath)
-		) {
-			// successful rename
-			$meta = $this->view->getFileInfo($normalizedNewPath);
-			$meta = \OCA\Files\Helper::populateTags(array($meta));
-			$fileInfo = \OCA\Files\Helper::formatFileInfo(current($meta));
-			$fileInfo['path'] = dirname($normalizedNewPath);
-			$result['success'] = true;
-			$result['data'] = $fileInfo;
-		} else {
-			// rename failed
-			$result['data'] = array(
-				'message'	=> $this->l10n->t('%s could not be renamed', array($oldname))
-			);
-		}
-		return $result;
-	}
-
 }
diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php
index fb14cea731f3adebe08d5ab6ef0c8dc3e4602ace..9a4e8d59786ad1b68c64eb77131a92548e728aa6 100644
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/helper.php
@@ -139,9 +139,6 @@ class Helper {
 		$entry['parentId'] = $i['parent'];
 		$entry['mtime'] = $i['mtime'] * 1000;
 		// only pick out the needed attributes
-		if (\OC::$server->getPreviewManager()->isAvailable($i)) {
-			$entry['isPreviewAvailable'] = true;
-		}
 		$entry['name'] = $i->getName();
 		$entry['permissions'] = $i['permissions'];
 		$entry['mimetype'] = $i['mimetype'];
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index 7ebf80ee8b2f1aa44d0e6d490b691a40fa5d9688..04550f945b6e9a2382f6d68b3a343575dbf90cd7 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -1,16 +1,5 @@
 <div id="controls">
 		<div class="actions creatable hidden">
-		<?php /*
-			Only show upload button for public page
-		*/ ?>
-		<?php if(isset($_['dirToken'])):?>
-			<div id="upload" class="button upload"
-				 title="<?php isset($_['uploadMaxHumanFilesize']) ? p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) : '' ?>">
-					<label for="file_upload_start" class="svg icon-upload">
-						<span class="hidden-visually"><?php p($l->t('Upload'))?></span>
-					</label>
-			</div>
-		<?php endif; ?>
 			<div id="uploadprogresswrapper">
 				<div id="uploadprogressbar"></div>
 				<button class="stop icon-close" style="display:none">
diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php
deleted file mode 100644
index 859c7042b89cf265762e335e5042b2112077e6ed..0000000000000000000000000000000000000000
--- a/apps/files/tests/ajax_rename.php
+++ /dev/null
@@ -1,232 +0,0 @@
-<?php
-/**
- * @author Björn Schießle <schiessle@owncloud.com>
- * @author Christopher Schäpers <kondou@ts.unde.re>
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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/>
- *
- */
-
-class Test_OC_Files_App_Rename extends \Test\TestCase {
-	private static $user;
-
-	/**
-	 * @var PHPUnit_Framework_MockObject_MockObject
-	 */
-	private $viewMock;
-
-	/**
-	 * @var \OCA\Files\App
-	 */
-	private $files;
-
-	protected function setUp() {
-		parent::setUp();
-
-		// mock OC_L10n
-		if (!self::$user) {
-			self::$user = uniqid();
-		}
-		\OC_User::createUser(self::$user, 'password');
-		$this->loginAsUser(self::$user);
-
-		$l10nMock = $this->getMock('\OC_L10N', array('t'), array(), '', false);
-		$l10nMock->expects($this->any())
-			->method('t')
-			->will($this->returnArgument(0));
-		$viewMock = $this->getMock('\OC\Files\View', array('rename', 'normalizePath', 'getFileInfo', 'file_exists'), array(), '', false);
-		$viewMock->expects($this->any())
-			->method('normalizePath')
-			->will($this->returnArgument(0));
-		$viewMock->expects($this->any())
-			->method('rename')
-			->will($this->returnValue(true));
-		$this->viewMock = $viewMock;
-		$this->files = new \OCA\Files\App($viewMock, $l10nMock);
-	}
-
-	protected function tearDown() {
-		$result = \OC_User::deleteUser(self::$user);
-		$this->assertTrue($result);
-
-		$this->logout();
-		parent::tearDown();
-	}
-
-	/**
-	 * test rename of file/folder
-	 */
-	function testRenameFolder() {
-		$dir = '/';
-		$oldname = 'oldname';
-		$newname = 'newname';
-
-		$this->viewMock->expects($this->any())
-			->method('file_exists')
-			->with($this->anything())
-			->will($this->returnValueMap(array(
-				array('/', true),
-				array('/oldname', true)
-				)));
-
-
-		$this->viewMock->expects($this->any())
-			->method('getFileInfo')
-			->will($this->returnValue(new \OC\Files\FileInfo(
-				'/new_name',
-				new \OC\Files\Storage\Local(array('datadir' => '/')),
-				'/',
-				array(
-				'fileid' => 123,
-				'type' => 'dir',
-				'mimetype' => 'httpd/unix-directory',
-				'mtime' => 0,
-				'permissions' => 31,
-				'size' => 18,
-				'etag' => 'abcdef',
-				'directory' => '/',
-				'name' => 'new_name',
-			), null)));
-
-		$result = $this->files->rename($dir, $oldname, $newname);
-
-		$this->assertTrue($result['success']);
-		$this->assertEquals(123, $result['data']['id']);
-		$this->assertEquals('new_name', $result['data']['name']);
-		$this->assertEquals(18, $result['data']['size']);
-		$this->assertEquals('httpd/unix-directory', $result['data']['mimetype']);
-		$this->assertEquals('abcdef', $result['data']['etag']);
-		$this->assertFalse(isset($result['data']['tags']));
-		$this->assertEquals('/', $result['data']['path']);
-	}
-
-	/**
-	 * test rename of file with tag
-	 */
-	function testRenameFileWithTag() {
-		$taggerMock = $this->getMock('\OCP\ITags');
-		$taggerMock->expects($this->any())
-			->method('getTagsForObjects')
-			->with(array(123))
-			->will($this->returnValue(array(123 => array('tag1', 'tag2'))));
-		$tagManagerMock = $this->getMock('\OCP\ITagManager');
-		$tagManagerMock->expects($this->any())
-			->method('load')
-			->with('files')
-			->will($this->returnValue($taggerMock));
-		$oldTagManager = \OC::$server->query('TagManager');
-		\OC::$server->registerService('TagManager', function ($c) use ($tagManagerMock) {
-			return $tagManagerMock;
-		});
-
-		$dir = '/';
-		$oldname = 'oldname.txt';
-		$newname = 'newname.txt';
-
-		$this->viewMock->expects($this->any())
-			->method('file_exists')
-			->with($this->anything())
-			->will($this->returnValueMap(array(
-				array('/', true),
-				array('/oldname.txt', true)
-				)));
-
-
-		$this->viewMock->expects($this->any())
-			->method('getFileInfo')
-			->will($this->returnValue(new \OC\Files\FileInfo(
-				'/new_name.txt',
-				new \OC\Files\Storage\Local(array('datadir' => '/')),
-				'/',
-				array(
-				'fileid' => 123,
-				'type' => 'file',
-				'mimetype' => 'text/plain',
-				'mtime' => 0,
-				'permissions' => 31,
-				'size' => 18,
-				'etag' => 'abcdef',
-				'directory' => '/',
-				'name' => 'new_name.txt',
-			), null)));
-
-		$result = $this->files->rename($dir, $oldname, $newname);
-
-		$this->assertTrue($result['success']);
-		$this->assertEquals(123, $result['data']['id']);
-		$this->assertEquals('new_name.txt', $result['data']['name']);
-		$this->assertEquals(18, $result['data']['size']);
-		$this->assertEquals('text/plain', $result['data']['mimetype']);
-		$this->assertEquals('abcdef', $result['data']['etag']);
-		$this->assertEquals(array('tag1', 'tag2'), $result['data']['tags']);
-		$this->assertEquals('/', $result['data']['path']);
-
-		\OC::$server->registerService('TagManager', function ($c) use ($oldTagManager) {
-			return $oldTagManager;
-		});
-	}
-
-	/**
-	 * Test rename inside a folder that doesn't exist any more
-	 */
-	function testRenameInNonExistingFolder() {
-		$dir = '/unexist';
-		$oldname = 'oldname';
-		$newname = 'newname';
-
-		$this->viewMock->expects($this->at(0))
-			->method('file_exists')
-			->with('/unexist/oldname')
-			->will($this->returnValue(false));
-
-		$this->viewMock->expects($this->any())
-			->method('getFileInfo')
-			->will($this->returnValue(array(
-				'fileid' => 123,
-				'type' => 'dir',
-				'mimetype' => 'httpd/unix-directory',
-				'size' => 18,
-				'etag' => 'abcdef',
-				'directory' => '/unexist',
-				'name' => 'new_name',
-			)));
-
-		$result = $this->files->rename($dir, $oldname, $newname);
-
-		$this->assertFalse($result['success']);
-		$this->assertEquals('sourcenotfound', $result['data']['code']);
-	}
-
-	/**
-	 * Test move to invalid name
-	 */
-	function testRenameToInvalidName() {
-		$dir = '/';
-		$oldname = 'oldname';
-		$newname = 'abc\\';
-
-		$result = $this->files->rename($dir, $oldname, $newname);
-
-		$this->assertFalse($result['success']);
-		$this->assertEquals('File name contains at least one invalid character', $result['data']['message']);
-		$this->assertEquals('invalidname', $result['data']['code']);
-	}
-}
diff --git a/apps/files/tests/js/favoritesfilelistspec.js b/apps/files/tests/js/favoritesfilelistspec.js
index 608ddaca18be94fcc96a20fa988a8b06d218180f..1c833d334e21bd4949146e679625c46fd6b2e996 100644
--- a/apps/files/tests/js/favoritesfilelistspec.js
+++ b/apps/files/tests/js/favoritesfilelistspec.js
@@ -100,8 +100,7 @@ describe('OCA.Files.FavoritesFileList tests', function() {
 			expect($tr.attr('data-mtime')).toEqual('11111000');
 			expect($tr.find('a.name').attr('href')).toEqual(
 				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Fsomedir&files=test.txt'
+				'/remote.php/webdav/somedir/test.txt'
 			);
 			expect($tr.find('.nametext').text().trim()).toEqual('test.txt');
 		});
diff --git a/apps/files/tests/js/fileUploadSpec.js b/apps/files/tests/js/fileUploadSpec.js
index a49a5d4e2e07446d2a7cc5def4b8584dd3383d41..8a0d6b019523f2e6bfa79b1da5f0964371af68e4 100644
--- a/apps/files/tests/js/fileUploadSpec.js
+++ b/apps/files/tests/js/fileUploadSpec.js
@@ -19,6 +19,8 @@
 *
 */
 
+/* global FileList */
+
 describe('OC.Upload tests', function() {
 	var $dummyUploader;
 	var testFile;
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index d29164c51366e584cc68ba0f98ac510724b478b1..a905a4d969d8cb6b8b5e037771628253598bcf45 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -584,7 +584,7 @@ describe('OCA.Files.FileActions tests', function() {
 				expect(busyStub.calledWith('testName.txt', true)).toEqual(true);
 				expect(handleDownloadStub.calledOnce).toEqual(true);
 				expect(handleDownloadStub.getCall(0).args[0]).toEqual(
-					OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'
+					OC.webroot + '/remote.php/webdav/subdir/testName.txt'
 				);
 				busyStub.reset();
 				handleDownloadStub.yield();
diff --git a/apps/files/tests/js/fileactionsmenuSpec.js b/apps/files/tests/js/fileactionsmenuSpec.js
index dee542458b625df72ed907da65952ca3a25d0056..747a746a602522d5aa915d9baee0f489bc32a93e 100644
--- a/apps/files/tests/js/fileactionsmenuSpec.js
+++ b/apps/files/tests/js/fileactionsmenuSpec.js
@@ -237,8 +237,8 @@ describe('OCA.Files.FileActionsMenu tests', function() {
 			expect(redirectStub.calledOnce).toEqual(true);
 			expect(redirectStub.getCall(0).args[0]).toContain(
 				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Fsubdir&files=testName.txt');
+				'/remote.php/webdav/subdir/testName.txt'
+			);
 			redirectStub.restore();
 		});
 		it('takes the file\'s path into account when clicking download', function() {
@@ -269,8 +269,7 @@ describe('OCA.Files.FileActionsMenu tests', function() {
 
 			expect(redirectStub.calledOnce).toEqual(true);
 			expect(redirectStub.getCall(0).args[0]).toContain(
-				OC.webroot + '/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Fanotherpath%2Fthere&files=testName.txt'
+				OC.webroot + '/remote.php/webdav/anotherpath/there/testName.txt'
 			);
 			redirectStub.restore();
 		});
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 05e6fcc6122b5e0ecc508dbe9204bc561ac6eaaf..9f7ad50bc608716b00c421b5608051826b9a636a 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -20,8 +20,11 @@
 */
 
 describe('OCA.Files.FileList tests', function() {
-	var testFiles, alertStub, notificationStub, fileList, pageSizeStub;
+	var FileInfo = OC.Files.FileInfo;
+	var testFiles, testRoot, notificationStub, fileList, pageSizeStub;
 	var bcResizeStub;
+	var filesClient;
+	var redirectStub;
 
 	/**
 	 * Generate test file data
@@ -38,21 +41,29 @@ describe('OCA.Files.FileList tests', function() {
 				name += '0';
 			}
 			name += i + '.txt';
-			files.push({
+			files.push(new FileInfo({
 				id: i,
 				type: 'file',
 				name: name,
 				mimetype: 'text/plain',
 				size: i * 2,
 				etag: 'abc'
-			});
+			}));
 		}
 		return files;
 	}
 
 	beforeEach(function() {
-		alertStub = sinon.stub(OC.dialogs, 'alert');
-		notificationStub = sinon.stub(OC.Notification, 'show');
+		filesClient = new OC.Files.Client({
+			host: 'localhost',
+			port: 80,
+			// FIXME: uncomment after fixing the test OC.webroot
+			//root: OC.webroot + '/remote.php/webdav',
+			root: '/remote.php/webdav',
+			useHTTPS: false
+		});
+		redirectStub = sinon.stub(OC, 'redirect');
+		notificationStub = sinon.stub(OC.Notification, 'showTemporary');
 		// prevent resize algo to mess up breadcrumb order while
 		// testing
 		bcResizeStub = sinon.stub(OCA.Files.BreadCrumb.prototype, '_resize');
@@ -93,7 +104,17 @@ describe('OCA.Files.FileList tests', function() {
 			'</div>'
 		);
 
-		testFiles = [{
+		testRoot = new FileInfo({
+			// root entry
+			id: 99,
+			type: 'dir',
+			name: '/subdir',
+			mimetype: 'httpd/unix-directory',
+			size: 1200000,
+			etag: 'a0b0c0d0',
+			permissions: OC.PERMISSION_ALL
+		});
+		testFiles = [new FileInfo({
 			id: 1,
 			type: 'file',
 			name: 'One.txt',
@@ -102,7 +123,7 @@ describe('OCA.Files.FileList tests', function() {
 			size: 12,
 			etag: 'abc',
 			permissions: OC.PERMISSION_ALL
-		}, {
+		}), new FileInfo({
 			id: 2,
 			type: 'file',
 			name: 'Two.jpg',
@@ -111,7 +132,7 @@ describe('OCA.Files.FileList tests', function() {
 			size: 12049,
 			etag: 'def',
 			permissions: OC.PERMISSION_ALL
-		}, {
+		}), new FileInfo({
 			id: 3,
 			type: 'file',
 			name: 'Three.pdf',
@@ -120,7 +141,7 @@ describe('OCA.Files.FileList tests', function() {
 			size: 58009,
 			etag: '123',
 			permissions: OC.PERMISSION_ALL
-		}, {
+		}), new FileInfo({
 			id: 4,
 			type: 'dir',
 			name: 'somedir',
@@ -129,9 +150,11 @@ describe('OCA.Files.FileList tests', function() {
 			size: 250,
 			etag: '456',
 			permissions: OC.PERMISSION_ALL
-		}];
+		})];
 		pageSizeStub = sinon.stub(OCA.Files.FileList.prototype, 'pageSize').returns(20);
-		fileList = new OCA.Files.FileList($('#app-content-files'));
+		fileList = new OCA.Files.FileList($('#app-content-files'), {
+			filesClient: filesClient
+		});
 	});
 	afterEach(function() {
 		testFiles = undefined;
@@ -141,9 +164,9 @@ describe('OCA.Files.FileList tests', function() {
 		fileList = undefined;
 
 		notificationStub.restore();
-		alertStub.restore();
 		bcResizeStub.restore();
 		pageSizeStub.restore();
+		redirectStub.restore();
 	});
 	describe('Getters', function() {
 		it('Returns the current directory', function() {
@@ -166,15 +189,14 @@ describe('OCA.Files.FileList tests', function() {
 			clock.restore();
 		});
 		it('generates file element with correct attributes when calling add() with file data', function() {
-			var fileData = {
+			var fileData = new FileInfo({
 				id: 18,
-				type: 'file',
 				name: 'testName.txt',
 				mimetype: 'text/plain',
-				size: '1234',
+				size: 1234,
 				etag: 'a01234c',
-				mtime: '123456'
-			};
+				mtime: 123456
+			});
 			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
@@ -188,7 +210,7 @@ describe('OCA.Files.FileList tests', function() {
 			expect($tr.attr('data-mime')).toEqual('text/plain');
 			expect($tr.attr('data-mtime')).toEqual('123456');
 			expect($tr.find('a.name').attr('href'))
-				.toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+				.toEqual(OC.webroot + '/remote.php/webdav/subdir/testName.txt');
 			expect($tr.find('.nametext').text().trim()).toEqual('testName.txt');
 
 			expect($tr.find('.filesize').text()).toEqual('1 kB');
@@ -196,15 +218,14 @@ describe('OCA.Files.FileList tests', function() {
 			expect(fileList.findFileEl('testName.txt')[0]).toEqual($tr[0]);
 		});
 		it('generates dir element with correct attributes when calling add() with dir data', function() {
-			var fileData = {
+			var fileData = new FileInfo({
 				id: 19,
-				type: 'dir',
 				name: 'testFolder',
 				mimetype: 'httpd/unix-directory',
-				size: '1234',
+				size: 1234,
 				etag: 'a01234c',
-				mtime: '123456'
-			};
+				mtime: 123456
+			});
 			var $tr = fileList.add(fileData);
 
 			expect($tr).toBeDefined();
@@ -297,7 +318,6 @@ describe('OCA.Files.FileList tests', function() {
 			expect($tr.index()).toEqual(4);
 		});
 		it('inserts files in a sorted manner when insert option is enabled', function() {
-			var $tr;
 			for (var i = 0; i < testFiles.length; i++) {
 				fileList.add(testFiles[i]);
 			}
@@ -423,28 +443,31 @@ describe('OCA.Files.FileList tests', function() {
 		});
 	});
 	describe('Deleting files', function() {
+		var deferredDelete;
+		var deleteStub;
+
+		beforeEach(function() {
+			deferredDelete = $.Deferred();
+			deleteStub = sinon.stub(filesClient, 'remove').returns(deferredDelete.promise());
+		});
+		afterEach(function() {
+			deleteStub.restore();
+		});
+
 		function doDelete() {
-			var request, query;
 			// note: normally called from FileActions
 			fileList.do_delete(['One.txt', 'Two.jpg']);
 
-			expect(fakeServer.requests.length).toEqual(1);
-			request = fakeServer.requests[0];
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
-
-			query = fakeServer.requests[0].requestBody;
-			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', files: '["One.txt","Two.jpg"]'});
+			expect(deleteStub.calledTwice).toEqual(true);
+			expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
+			expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
 		}
 		it('calls delete.php, removes the deleted entries and updates summary', function() {
 			var $summary;
 			fileList.setFiles(testFiles);
 			doDelete();
 
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({status: 'success'})
-			);
+			deferredDelete.resolve(200);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 			expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
@@ -482,11 +505,7 @@ describe('OCA.Files.FileList tests', function() {
 			fileList.setFiles([testFiles[0], testFiles[1]]);
 			doDelete();
 
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({status: 'success'})
-			);
+			deferredDelete.resolve(200);
 
 			expect(fileList.$fileList.find('tr').length).toEqual(0);
 
@@ -501,21 +520,41 @@ describe('OCA.Files.FileList tests', function() {
 			fileList.setFiles(testFiles);
 			doDelete();
 
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({status: 'error', data: {message: 'WOOT'}})
-			);
+			deferredDelete.reject(403);
 
 			// files are still in the list
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 			expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
 			expect(fileList.$fileList.find('tr').length).toEqual(4);
 
-			expect(notificationStub.calledOnce).toEqual(true);
+			expect(notificationStub.calledTwice).toEqual(true);
+		});
+		it('remove file from list if delete call returned 404 not found', function() {
+			fileList.setFiles(testFiles);
+			doDelete();
+
+			deferredDelete.reject(404);
+
+			// files are still in the list
+			expect(fileList.findFileEl('One.txt').length).toEqual(0);
+			expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
+			expect(fileList.$fileList.find('tr').length).toEqual(2);
+
+			expect(notificationStub.notCalled).toEqual(true);
 		});
 	});
 	describe('Renaming files', function() {
+		var deferredRename;
+		var renameStub;
+
+		beforeEach(function() {
+			deferredRename = $.Deferred();
+			renameStub = sinon.stub(filesClient, 'move').returns(deferredRename.promise());
+		});
+		afterEach(function() {
+			renameStub.restore();
+		});
+
 		function doCancelRename() {
 			var $input;
 			for (var i = 0; i < testFiles.length; i++) {
@@ -530,10 +569,10 @@ describe('OCA.Files.FileList tests', function() {
 			// trigger submit because triggering blur doesn't work in all browsers
 			$input.closest('form').trigger('submit');
 
-			expect(fakeServer.requests.length).toEqual(0);
+			expect(renameStub.notCalled).toEqual(true);
 		}
 		function doRename() {
-			var $input, request;
+			var $input;
 
 			for (var i = 0; i < testFiles.length; i++) {
 				var file = testFiles[i];
@@ -548,83 +587,61 @@ describe('OCA.Files.FileList tests', function() {
 			// trigger submit because triggering blur doesn't work in all browsers
 			$input.closest('form').trigger('submit');
 
-			expect(fakeServer.requests.length).toEqual(1);
-			request = fakeServer.requests[0];
-			expect(request.url.substr(0, request.url.indexOf('?'))).toEqual(OC.webroot + '/index.php/apps/files/ajax/rename.php');
-			expect(OC.parseQueryString(request.url)).toEqual({'dir': '/some/subdir', newname: 'Tu_after_three.txt', file: 'One.txt'});
+			expect(renameStub.calledOnce).toEqual(true);
+			expect(renameStub.getCall(0).args[0]).toEqual('/some/subdir/One.txt');
+			expect(renameStub.getCall(0).args[1]).toEqual('/some/subdir/Tu_after_three.txt');
 		}
 		it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() {
 			doRename();
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'Tu_after_three.txt',
-					type: 'file'
-				}
-			}));
+			deferredRename.resolve(201);
 
 			// element stays renamed
 			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 			expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(1);
-			expect(fileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt
+			expect(fileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.jpg
 
-			expect(alertStub.notCalled).toEqual(true);
+			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('Reverts file entry if rename ajax call failed', function() {
 			doRename();
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'error',
-				data: {
-					message: 'Something went wrong'
-				}
-			}));
+			deferredRename.reject(403);
 
 			// element was reverted
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 			expect(fileList.findFileEl('One.txt').index()).toEqual(1); // after somedir
 			expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(0);
 
-			expect(alertStub.calledOnce).toEqual(true);
+			expect(notificationStub.calledOnce).toEqual(true);
 		});
 		it('Correctly updates file link after rename', function() {
 			var $tr;
 			doRename();
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'Tu_after_three.txt'
-				}
-			}));
+			deferredRename.resolve(201);
 
 			$tr = fileList.findFileEl('Tu_after_three.txt');
-			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=Tu_after_three.txt');
+			expect($tr.find('a.name').attr('href'))
+				.toEqual(OC.webroot + '/remote.php/webdav/some/subdir/Tu_after_three.txt');
 		});
 		it('Triggers "fileActionsReady" event after rename', function() {
 			var handler = sinon.stub();
 			fileList.$fileList.on('fileActionsReady', handler);
 			doRename();
 			expect(handler.notCalled).toEqual(true);
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'Tu_after_three.txt'
-				}
-			}));
+
+			deferredRename.resolve(201);
+
 			expect(handler.calledOnce).toEqual(true);
 			expect(fileList.$fileList.find('.test').length).toEqual(0);
 		});
 		it('Leaves the summary alone when reinserting renamed element', function() {
 			var $summary = $('#filestable .summary');
 			doRename();
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'Tu_after_three.txt'
-				}
-			}));
+
+			deferredRename.resolve(201);
+
 			expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
 		});
 		it('Leaves the summary alone when cancel renaming', function() {
@@ -668,7 +685,7 @@ describe('OCA.Files.FileList tests', function() {
 
 			// trigger submit does not send server request
 			$input.closest('form').trigger('submit');
-			expect(fakeServer.requests.length).toEqual(0);
+			expect(renameStub.notCalled).toEqual(true);
 
 			// simulate escape key
 			$input.trigger(new $.Event('keyup', {keyCode: 27}));
@@ -694,12 +711,7 @@ describe('OCA.Files.FileList tests', function() {
 			expect(OC.TestUtil.getImageUrl(fileList.findFileEl('Tu_after_three.txt').find('.thumbnail')))
 				.toEqual(OC.imagePath('core', 'loading.gif'));
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'error',
-				data: {
-					message: 'Something went wrong'
-				}
-			}));
+			deferredRename.reject(409);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 			expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
@@ -707,25 +719,27 @@ describe('OCA.Files.FileList tests', function() {
 		});
 	});
 	describe('Moving files', function() {
+		var deferredMove;
+		var moveStub;
+
 		beforeEach(function() {
+			deferredMove = $.Deferred();
+			moveStub = sinon.stub(filesClient, 'move').returns(deferredMove.promise());
+
 			fileList.setFiles(testFiles);
 		});
+		afterEach(function() {
+			moveStub.restore();
+		});
+
 		it('Moves single file to target folder', function() {
-			var request;
 			fileList.move('One.txt', '/somedir');
 
-			expect(fakeServer.requests.length).toEqual(1);
-			request = fakeServer.requests[0];
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+			expect(moveStub.calledOnce).toEqual(true);
+			expect(moveStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
+			expect(moveStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'One.txt',
-					type: 'file'
-				}
-			}));
+			deferredMove.resolve(201);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
@@ -736,39 +750,28 @@ describe('OCA.Files.FileList tests', function() {
 			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('Moves list of files to target folder', function() {
-			var request;
+			var deferredMove1 = $.Deferred();
+			var deferredMove2 = $.Deferred();
+			moveStub.onCall(0).returns(deferredMove1.promise());
+			moveStub.onCall(1).returns(deferredMove2.promise());
+
 			fileList.move(['One.txt', 'Two.jpg'], '/somedir');
 
-			expect(fakeServer.requests.length).toEqual(2);
-			request = fakeServer.requests[0];
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+			expect(moveStub.calledTwice).toEqual(true);
+			expect(moveStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
+			expect(moveStub.getCall(0).args[1]).toEqual('/somedir/One.txt');
+			expect(moveStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
+			expect(moveStub.getCall(1).args[1]).toEqual('/somedir/Two.jpg');
 
-			request = fakeServer.requests[1];
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'Two.jpg', target: '/somedir'});
-
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'One.txt',
-					type: 'file'
-				}
-			}));
+			deferredMove1.resolve(201);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(0);
 
-			// folder size has increased
+			// folder size has increased during move
 			expect(fileList.findFileEl('somedir').data('size')).toEqual(262);
 			expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B');
 
-			fakeServer.requests[1].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'success',
-				data: {
-					name: 'Two.jpg',
-					type: 'file'
-				}
-			}));
+			deferredMove2.resolve(201);
 
 			expect(fileList.findFileEl('Two.jpg').length).toEqual(0);
 
@@ -779,47 +782,31 @@ describe('OCA.Files.FileList tests', function() {
 			expect(notificationStub.notCalled).toEqual(true);
 		});
 		it('Shows notification if a file could not be moved', function() {
-			var request;
 			fileList.move('One.txt', '/somedir');
 
-			expect(fakeServer.requests.length).toEqual(1);
-			request = fakeServer.requests[0];
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			expect(OC.parseQueryString(request.requestBody)).toEqual({dir: '/subdir', file: 'One.txt', target: '/somedir'});
+			expect(moveStub.calledOnce).toEqual(true);
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'error',
-				data: {
-					message: 'Error while moving file'
-				}
-			}));
+			deferredMove.reject(409);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 
 			expect(notificationStub.calledOnce).toEqual(true);
-			expect(notificationStub.getCall(0).args[0]).toEqual('Error while moving file');
+			expect(notificationStub.getCall(0).args[0]).toEqual('Could not move "One.txt"');
 		});
 		it('Restores thumbnail if a file could not be moved', function() {
-			var request;
 			fileList.move('One.txt', '/somedir');
 
 			expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
 				.toEqual(OC.imagePath('core', 'loading.gif'));
 
-			expect(fakeServer.requests.length).toEqual(1);
-			request = fakeServer.requests[0];
+			expect(moveStub.calledOnce).toEqual(true);
 
-			fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
-				status: 'error',
-				data: {
-					message: 'Error while moving file'
-				}
-			}));
+			deferredMove.reject(409);
 
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 
 			expect(notificationStub.calledOnce).toEqual(true);
-			expect(notificationStub.getCall(0).args[0]).toEqual('Error while moving file');
+			expect(notificationStub.getCall(0).args[0]).toEqual('Could not move "One.txt"');
 
 			expect(OC.TestUtil.getImageUrl(fileList.findFileEl('One.txt').find('.thumbnail')))
 				.toEqual(OC.imagePath('core', 'filetypes/text.svg'));
@@ -878,7 +865,7 @@ describe('OCA.Files.FileList tests', function() {
 				name: 'testFile.txt',
 				directory: '/current dir'
 			};
-			var $tr = fileList.add(fileData);
+			fileList.add(fileData);
 			expect(fileList.findFileEl('testFile.txt').length).toEqual(1);
 		});
 		it('triggers "fileActionsReady" event after update', function() {
@@ -1143,69 +1130,85 @@ describe('OCA.Files.FileList tests', function() {
 		afterEach(function() {
 			previewLoadStub.restore();
 		});
-		it('renders default icon for file when none provided and no preview is available', function() {
+		it('renders default file icon when none provided and no mime type is set', function() {
 			var fileData = {
-				type: 'file',
 				name: 'testFile.txt'
 			};
 			var $tr = fileList.add(fileData);
 			var $imgDiv = $tr.find('td.filename .thumbnail');
 			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
-			expect(previewLoadStub.notCalled).toEqual(true);
+			// tries to load preview
+			expect(previewLoadStub.calledOnce).toEqual(true);
 		});
-		it('renders default icon for dir when none provided and no preview is available', function() {
+		it('renders default icon for folder when none provided', function() {
 			var fileData = {
-				type: 'dir',
-				name: 'test dir'
+				name: 'test dir',
+				mimetype: 'httpd/unix-directory'
 			};
+
 			var $tr = fileList.add(fileData);
 			var $imgDiv = $tr.find('td.filename .thumbnail');
 			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
+			// no preview since it's a directory
 			expect(previewLoadStub.notCalled).toEqual(true);
 		});
 		it('renders provided icon for file when provided', function() {
-			var fileData = {
+			var fileData = new FileInfo({
 				type: 'file',
 				name: 'test file',
 				icon: OC.webroot + '/core/img/filetypes/application-pdf.svg',
 				mimetype: 'application/pdf'
-			};
+			});
 			var $tr = fileList.add(fileData);
 			var $imgDiv = $tr.find('td.filename .thumbnail');
 			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
+			// try loading preview
+			expect(previewLoadStub.calledOnce).toEqual(true);
+		});
+		it('renders provided icon for file when provided', function() {
+			var fileData = new FileInfo({
+				name: 'somefile.pdf',
+				icon: OC.webroot + '/core/img/filetypes/application-pdf.svg'
+			});
+
+			var $tr = fileList.add(fileData);
+			var $imgDiv = $tr.find('td.filename .thumbnail');
+			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
+			// try loading preview
+			expect(previewLoadStub.calledOnce).toEqual(true);
+		});
+		it('renders provided icon for folder when provided', function() {
+			var fileData = new FileInfo({
+				name: 'some folder',
+				mimetype: 'httpd/unix-directory',
+				icon: OC.webroot + '/core/img/filetypes/folder-alt.svg'
+			});
+
+			var $tr = fileList.add(fileData);
+			var $imgDiv = $tr.find('td.filename .thumbnail');
+			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/folder-alt.svg');
+			// do not load preview for folders
 			expect(previewLoadStub.notCalled).toEqual(true);
 		});
-		it('renders preview when no icon was provided and preview is available', function() {
+		it('renders preview when no icon was provided', function() {
 			var fileData = {
 				type: 'file',
-				name: 'test file',
-				isPreviewAvailable: true
+				name: 'test file'
 			};
 			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
-			expect(OC.TestUtil.getImageUrl($td.find('.thumbnail'))).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
+			expect(OC.TestUtil.getImageUrl($td.find('.thumbnail')))
+				.toEqual(OC.webroot + '/core/img/filetypes/file.svg');
 			expect(previewLoadStub.calledOnce).toEqual(true);
 			// third argument is callback
 			previewLoadStub.getCall(0).args[0].callback(OC.webroot + '/somepath.png');
 			expect(OC.TestUtil.getImageUrl($td.find('.thumbnail'))).toEqual(OC.webroot + '/somepath.png');
 		});
-		it('renders default file type icon when no icon was provided and no preview is available', function() {
-			var fileData = {
-				type: 'file',
-				name: 'test file',
-				isPreviewAvailable: false
-			};
-			var $tr = fileList.add(fileData);
-			var $imgDiv = $tr.find('td.filename .thumbnail');
-			expect(OC.TestUtil.getImageUrl($imgDiv)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
-			expect(previewLoadStub.notCalled).toEqual(true);
-		});
 		it('does not render preview for directories', function() {
 			var fileData = {
 				type: 'dir',
 				mimetype: 'httpd/unix-directory',
-				name: 'test dir',
-				isPreviewAvailable: true
+				name: 'test dir'
 			};
 			var $tr = fileList.add(fileData);
 			var $td = $tr.find('td.filename');
@@ -1217,7 +1220,6 @@ describe('OCA.Files.FileList tests', function() {
 				type: 'dir',
 				mimetype: 'httpd/unix-directory',
 				name: 'test dir',
-				isPreviewAvailable: true,
 				mountType: 'external-root'
 			};
 			var $tr = fileList.add(fileData);
@@ -1230,7 +1232,6 @@ describe('OCA.Files.FileList tests', function() {
 				type: 'dir',
 				mimetype: 'httpd/unix-directory',
 				name: 'test dir',
-				isPreviewAvailable: true,
 				mountType: 'external'
 			};
 			var $tr = fileList.add(fileData);
@@ -1278,75 +1279,47 @@ describe('OCA.Files.FileList tests', function() {
 		});
 	});
 	describe('loading file list', function() {
+		var deferredList;
+		var getFolderContentsStub;
+
 		beforeEach(function() {
-			var data = {
-				status: 'success',
-				data: {
-					files: testFiles,
-					permissions: 31
-				}
-			};
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2F(subdir|anothersubdir)/, [
-					200, {
-						"Content-Type": "application/json"
-					},
-					JSON.stringify(data)
-				]);
+			deferredList = $.Deferred();
+			getFolderContentsStub = sinon.stub(filesClient, 'getFolderContents').returns(deferredList.promise());
+		});
+		afterEach(function() {
+			getFolderContentsStub.restore();
 		});
 		it('fetches file list from server and renders it when reload() is called', function() {
 			fileList.reload();
-			expect(fakeServer.requests.length).toEqual(1);
-			var url = fakeServer.requests[0].url;
-			var query = url.substr(url.indexOf('?') + 1);
-			expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', sort: 'name', sortdirection: 'asc'});
-			fakeServer.respond();
+			expect(getFolderContentsStub.calledOnce).toEqual(true);
+			expect(getFolderContentsStub.calledWith('/subdir')).toEqual(true);
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			expect($('#fileList tr').length).toEqual(4);
 			expect(fileList.findFileEl('One.txt').length).toEqual(1);
 		});
 		it('switches dir and fetches file list when calling changeDirectory()', function() {
 			fileList.changeDirectory('/anothersubdir');
 			expect(fileList.getCurrentDirectory()).toEqual('/anothersubdir');
-			expect(fakeServer.requests.length).toEqual(1);
-			var url = fakeServer.requests[0].url;
-			var query = url.substr(url.indexOf('?') + 1);
-			expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir', sort: 'name', sortdirection: 'asc'});
-			fakeServer.respond();
+			expect(getFolderContentsStub.calledOnce).toEqual(true);
+			expect(getFolderContentsStub.calledWith('/anothersubdir')).toEqual(true);
 		});
 		it('converts backslashes to slashes when calling changeDirectory()', function() {
 			fileList.changeDirectory('/another\\subdir');
 			expect(fileList.getCurrentDirectory()).toEqual('/another/subdir');
 		});
 		it('switches to root dir when current directory does not exist', function() {
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2funexist/, [
-					404, {
-						"Content-Type": "application/json"
-					},
-					''
-			]);
 			fileList.changeDirectory('/unexist');
-			fakeServer.respond();
+			deferredList.reject(404);
 			expect(fileList.getCurrentDirectory()).toEqual('/');
 		});
 		it('switches to root dir when current directory is forbidden', function() {
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2funexist/, [
-				403, {
-					"Content-Type": "application/json"
-				},
-				''
-			]);
 			fileList.changeDirectory('/unexist');
-			fakeServer.respond();
+			deferredList.reject(403);
 			expect(fileList.getCurrentDirectory()).toEqual('/');
 		});
 		it('switches to root dir when current directory is unavailable', function() {
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2funexist/, [
-				500, {
-					"Content-Type": "application/json"
-				},
-				''
-			]);
 			fileList.changeDirectory('/unexist');
-			fakeServer.respond();
+			deferredList.reject(500);
 			expect(fileList.getCurrentDirectory()).toEqual('/');
 		});
 		it('shows mask before loading file list then hides it at the end', function() {
@@ -1355,7 +1328,7 @@ describe('OCA.Files.FileList tests', function() {
 			fileList.changeDirectory('/anothersubdir');
 			expect(showMaskStub.calledOnce).toEqual(true);
 			expect(hideMaskStub.calledOnce).toEqual(false);
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			expect(showMaskStub.calledOnce).toEqual(true);
 			expect(hideMaskStub.calledOnce).toEqual(true);
 			showMaskStub.restore();
@@ -1365,6 +1338,7 @@ describe('OCA.Files.FileList tests', function() {
 			var handler = sinon.stub();
 			$('#app-content-files').on('changeDirectory', handler);
 			fileList.changeDirectory('/somedir');
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			expect(handler.calledOnce).toEqual(true);
 			expect(handler.getCall(0).args[0].dir).toEqual('/somedir');
 		});
@@ -1375,31 +1349,27 @@ describe('OCA.Files.FileList tests', function() {
 		it('refreshes breadcrumb after update', function() {
 			var setDirSpy = sinon.spy(fileList.breadcrumb, 'setDirectory');
 			fileList.changeDirectory('/anothersubdir');
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			expect(fileList.breadcrumb.setDirectory.calledOnce).toEqual(true);
 			expect(fileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true);
 			setDirSpy.restore();
+			getFolderContentsStub.restore();
 		});
 	});
 	describe('breadcrumb events', function() {
+		var deferredList;
+		var getFolderContentsStub;
+
 		beforeEach(function() {
-			var data = {
-				status: 'success',
-				data: {
-					files: testFiles,
-					permissions: 31
-				}
-			};
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2Fsubdir/, [
-					200, {
-						"Content-Type": "application/json"
-					},
-					JSON.stringify(data)
-			]);
+			deferredList = $.Deferred();
+			getFolderContentsStub = sinon.stub(filesClient, 'getFolderContents').returns(deferredList.promise());
+		});
+		afterEach(function() {
+			getFolderContentsStub.restore();
 		});
 		it('clicking on root breadcrumb changes directory to root', function() {
 			fileList.changeDirectory('/subdir/two/three with space/four/five');
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			var changeDirStub = sinon.stub(fileList, 'changeDirectory');
 			fileList.breadcrumb.$el.find('.crumb:eq(0)').trigger({type: 'click', which: 1});
 
@@ -1409,7 +1379,7 @@ describe('OCA.Files.FileList tests', function() {
 		});
 		it('clicking on breadcrumb changes directory', function() {
 			fileList.changeDirectory('/subdir/two/three with space/four/five');
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			var changeDirStub = sinon.stub(fileList, 'changeDirectory');
 			fileList.breadcrumb.$el.find('.crumb:eq(3)').trigger({type: 'click', which: 1});
 
@@ -1418,9 +1388,10 @@ describe('OCA.Files.FileList tests', function() {
 			changeDirStub.restore();
 		});
 		it('dropping files on breadcrumb calls move operation', function() {
-			var request, query, testDir = '/subdir/two/three with space/four/five';
+			var testDir = '/subdir/two/three with space/four/five';
+			var moveStub = sinon.stub(filesClient, 'move').returns($.Deferred().promise());
 			fileList.changeDirectory(testDir);
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			var $crumb = fileList.breadcrumb.$el.find('.crumb:eq(3)');
 			// no idea what this is but is required by the handler
 			var ui = {
@@ -1436,33 +1407,18 @@ describe('OCA.Files.FileList tests', function() {
 			// simulate drop event
 			fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
 
-			// will trigger two calls to move.php (first one was previous list.php)
-			expect(fakeServer.requests.length).toEqual(3);
-
-			request = fakeServer.requests[1];
-			expect(request.method).toEqual('POST');
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			query = OC.parseQueryString(request.requestBody);
-			expect(query).toEqual({
-				target: '/subdir/two/three with space',
-				dir: testDir,
-				file: 'One.txt'
-			});
-
-			request = fakeServer.requests[2];
-			expect(request.method).toEqual('POST');
-			expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php');
-			query = OC.parseQueryString(request.requestBody);
-			expect(query).toEqual({
-				target: '/subdir/two/three with space',
-				dir: testDir,
-				file: 'Two.jpg'
-			});
+			expect(moveStub.callCount).toEqual(2);
+			expect(moveStub.getCall(0).args[0]).toEqual(testDir + '/One.txt');
+			expect(moveStub.getCall(0).args[1]).toEqual('/subdir/two/three with space/One.txt');
+			expect(moveStub.getCall(1).args[0]).toEqual(testDir + '/Two.jpg');
+			expect(moveStub.getCall(1).args[1]).toEqual('/subdir/two/three with space/Two.jpg');
+			moveStub.restore();
 		});
 		it('dropping files on same dir breadcrumb does nothing', function() {
 			var testDir = '/subdir/two/three with space/four/five';
+			var moveStub = sinon.stub(filesClient, 'move').returns($.Deferred().promise());
 			fileList.changeDirectory(testDir);
-			fakeServer.respond();
+			deferredList.resolve(200, [testRoot].concat(testFiles));
 			var $crumb = fileList.breadcrumb.$el.find('.crumb:last');
 			// no idea what this is but is required by the handler
 			var ui = {
@@ -1479,21 +1435,26 @@ describe('OCA.Files.FileList tests', function() {
 			fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui);
 
 			// no extra server request
-			expect(fakeServer.requests.length).toEqual(1);
+			expect(moveStub.notCalled).toEqual(true);
 		});
 	});
 	describe('Download Url', function() {
 		it('returns correct download URL for single files', function() {
-			expect(fileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt');
-			expect(fileList.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt'))
+				.toEqual(OC.webroot + '/remote.php/webdav/subdir/some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt', '/anotherpath/abc'))
+				.toEqual(OC.webroot + '/remote.php/webdav/anotherpath/abc/some%20file.txt');
 			$('#dir').val('/');
-			expect(fileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt');
+			expect(fileList.getDownloadUrl('some file.txt'))
+				.toEqual(OC.webroot + '/remote.php/webdav/some%20file.txt');
 		});
 		it('returns correct download URL for multiple files', function() {
-			expect(fileList.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
+			expect(fileList.getDownloadUrl(['a b c.txt', 'd e f.txt']))
+				.toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D');
 		});
 		it('returns the correct ajax URL', function() {
-			expect(fileList.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
+			expect(fileList.getAjaxUrl('test', {a:1, b:'x y'}))
+				.toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y');
 		});
 	});
 	describe('File selection', function() {
@@ -1672,24 +1633,17 @@ describe('OCA.Files.FileList tests', function() {
 		});
 		it('Selection is cleared when switching dirs', function() {
 			$('.select-all').click();
-			var data = {
-				status: 'success',
-				data: {
-					files: testFiles,
-					permissions: 31
-				}
-			};
-			fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php/, [
-					200, {
-						"Content-Type": "application/json"
-					},
-					JSON.stringify(data)
-				]
-			);
+			var deferredList = $.Deferred();
+			var getFolderContentsStub = sinon.stub(filesClient, 'getFolderContents').returns(deferredList.promise());
+
 			fileList.changeDirectory('/');
-			fakeServer.respond();
+
+			deferredList.resolve(200, [testRoot].concat(testFiles));
+
 			expect($('.select-all').prop('checked')).toEqual(false);
 			expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual([]);
+
+			getFolderContentsStub.restore();
 		});
 		it('getSelectedFiles returns the selected files even when they are on the next page', function() {
 			var selectedFiles;
@@ -1796,6 +1750,12 @@ describe('OCA.Files.FileList tests', function() {
 					etag: '456',
 					permissions: OC.PERMISSION_ALL
 				});
+				expect(files[0].id).toEqual(1);
+				expect(files[0].name).toEqual('One.txt');
+				expect(files[1].id).toEqual(3);
+				expect(files[1].name).toEqual('Three.pdf');
+				expect(files[2].id).toEqual(4);
+				expect(files[2].name).toEqual('somedir');
 			});
 			it('Removing a file removes it from the selection', function() {
 				fileList.remove('Three.pdf');
@@ -1824,7 +1784,6 @@ describe('OCA.Files.FileList tests', function() {
 			});
 			describe('Download', function() {
 				it('Opens download URL when clicking "Download"', function() {
-					var redirectStub = sinon.stub(OC, 'redirect');
 					$('.selectedActions .download').click();
 					expect(redirectStub.calledOnce).toEqual(true);
 					expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22One.txt%22%2C%22Three.pdf%22%2C%22somedir%22%5D');
@@ -1833,54 +1792,53 @@ describe('OCA.Files.FileList tests', function() {
 				it('Downloads root folder when all selected in root folder', function() {
 					$('#dir').val('/');
 					$('.select-all').click();
-					var redirectStub = sinon.stub(OC, 'redirect');
 					$('.selectedActions .download').click();
 					expect(redirectStub.calledOnce).toEqual(true);
 					expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=');
-					redirectStub.restore();
 				});
 				it('Downloads parent folder when all selected in subfolder', function() {
 					$('.select-all').click();
-					var redirectStub = sinon.stub(OC, 'redirect');
 					$('.selectedActions .download').click();
 					expect(redirectStub.calledOnce).toEqual(true);
 					expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=subdir');
-					redirectStub.restore();
 				});
 			});
 			describe('Delete', function() {
+				var deleteStub, deferredDelete;
+				beforeEach(function() {
+					deferredDelete = $.Deferred();
+					deleteStub = sinon.stub(filesClient, 'remove').returns(deferredDelete.promise());
+				});
+				afterEach(function() {
+					deleteStub.restore();
+				});
 				it('Deletes selected files when "Delete" clicked', function() {
-					var request;
 					$('.selectedActions .delete-selected').click();
-					expect(fakeServer.requests.length).toEqual(1);
-					request = fakeServer.requests[0];
-					expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
-					expect(OC.parseQueryString(request.requestBody))
-						.toEqual({'dir': '/subdir', files: '["One.txt","Three.pdf","somedir"]'});
-					fakeServer.requests[0].respond(
-						200,
-						{ 'Content-Type': 'application/json' },
-						JSON.stringify({status: 'success'})
-					);
+
+					expect(deleteStub.callCount).toEqual(3);
+					expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
+					expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Three.pdf');
+					expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/somedir');
+
+					deferredDelete.resolve(204);
+
 					expect(fileList.findFileEl('One.txt').length).toEqual(0);
 					expect(fileList.findFileEl('Three.pdf').length).toEqual(0);
 					expect(fileList.findFileEl('somedir').length).toEqual(0);
 					expect(fileList.findFileEl('Two.jpg').length).toEqual(1);
 				});
 				it('Deletes all files when all selected when "Delete" clicked', function() {
-					var request;
 					$('.select-all').click();
 					$('.selectedActions .delete-selected').click();
-					expect(fakeServer.requests.length).toEqual(1);
-					request = fakeServer.requests[0];
-					expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php');
-					expect(OC.parseQueryString(request.requestBody))
-						.toEqual({'dir': '/subdir', allfiles: 'true'});
-					fakeServer.requests[0].respond(
-						200,
-						{ 'Content-Type': 'application/json' },
-						JSON.stringify({status: 'success'})
-					);
+
+					expect(deleteStub.callCount).toEqual(4);
+					expect(deleteStub.getCall(0).args[0]).toEqual('/subdir/One.txt');
+					expect(deleteStub.getCall(1).args[0]).toEqual('/subdir/Two.jpg');
+					expect(deleteStub.getCall(2).args[0]).toEqual('/subdir/Three.pdf');
+					expect(deleteStub.getCall(3).args[0]).toEqual('/subdir/somedir');
+
+					deferredDelete.resolve(204);
+
 					expect(fileList.isEmpty).toEqual(true);
 				});
 			});
@@ -2118,30 +2076,6 @@ describe('OCA.Files.FileList tests', function() {
 		});
 	});
 	describe('Sorting files', function() {
-		it('Sorts by name by default', function() {
-			fileList.reload();
-			expect(fakeServer.requests.length).toEqual(1);
-			var url = fakeServer.requests[0].url;
-			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
-			expect(query.sort).toEqual('name');
-			expect(query.sortdirection).toEqual('asc');
-		});
-		it('Reloads file list with a different sort when clicking on column header of unsorted column', function() {
-			fileList.$el.find('.column-size .columntitle').click();
-			expect(fakeServer.requests.length).toEqual(1);
-			var url = fakeServer.requests[0].url;
-			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
-			expect(query.sort).toEqual('size');
-			expect(query.sortdirection).toEqual('desc');
-		});
-		it('Toggles sort direction when clicking on already sorted column', function() {
-			fileList.$el.find('.column-name .columntitle').click();
-			expect(fakeServer.requests.length).toEqual(1);
-			var url = fakeServer.requests[0].url;
-			var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
-			expect(query.sort).toEqual('name');
-			expect(query.sortdirection).toEqual('desc');
-		});
 		it('Toggles the sort indicator when clicking on a column header', function() {
 			var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS;
 			var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS;
@@ -2191,28 +2125,15 @@ describe('OCA.Files.FileList tests', function() {
 		it('Uses correct sort comparator when inserting files', function() {
 			testFiles.sort(OCA.Files.FileList.Comparators.size);
 			testFiles.reverse();	//default is descending
-			// this will make it reload the testFiles with the correct sorting
+			fileList.setFiles(testFiles);
 			fileList.$el.find('.column-size .columntitle').click();
-			expect(fakeServer.requests.length).toEqual(1);
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'success',
-					data: {
-						files: testFiles,
-						permissions: 31
-					}
-				})
-			);
-			var newFileData = {
+			var newFileData = new FileInfo({
 				id: 999,
-				type: 'file',
 				name: 'new file.txt',
 				mimetype: 'text/plain',
 				size: 40001,
 				etag: '999'
-			};
+			});
 			fileList.add(newFileData);
 			expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
 			expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
@@ -2224,41 +2145,18 @@ describe('OCA.Files.FileList tests', function() {
 		});
 		it('Uses correct reversed sort comparator when inserting files', function() {
 			testFiles.sort(OCA.Files.FileList.Comparators.size);
-			// this will make it reload the testFiles with the correct sorting
+			fileList.setFiles(testFiles);
 			fileList.$el.find('.column-size .columntitle').click();
-			expect(fakeServer.requests.length).toEqual(1);
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'success',
-					data: {
-						files: testFiles,
-						permissions: 31
-					}
-				})
-			);
+
 			// reverse sort
 			fileList.$el.find('.column-size .columntitle').click();
-			fakeServer.requests[1].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'success',
-					data: {
-						files: testFiles,
-						permissions: 31
-					}
-				})
-			);
-			var newFileData = {
+			var newFileData = new FileInfo({
 				id: 999,
-				type: 'file',
 				name: 'new file.txt',
 				mimetype: 'text/plain',
 				size: 40001,
 				etag: '999'
-			};
+			});
 			fileList.add(newFileData);
 			expect(fileList.findFileEl('One.txt').index()).toEqual(0);
 			expect(fileList.findFileEl('somedir').index()).toEqual(1);
@@ -2290,87 +2188,96 @@ describe('OCA.Files.FileList tests', function() {
 	});
 	describe('create file', function() {
 		var deferredCreate;
+		var deferredInfo;
+		var createStub;
+		var getFileInfoStub;
 
 		beforeEach(function() {
 			deferredCreate = $.Deferred();
+			deferredInfo = $.Deferred();
+			createStub = sinon.stub(filesClient, 'putFileContents')
+				.returns(deferredCreate.promise());
+			getFileInfoStub = sinon.stub(filesClient, 'getFileInfo')
+				.returns(deferredInfo.promise());
+		});
+		afterEach(function() {
+			createStub.restore();
+			getFileInfoStub.restore();
 		});
 
 		it('creates file with given name and adds it to the list', function() {
-			var deferred = fileList.createFile('test file.txt');
-			var successStub = sinon.stub();
-			var failureStub = sinon.stub();
+			fileList.createFile('test.txt');
 
-			deferred.done(successStub);
-			deferred.fail(failureStub);
+			expect(createStub.calledOnce).toEqual(true);
+			expect(createStub.getCall(0).args[0]).toEqual('/subdir/test.txt');
+			expect(createStub.getCall(0).args[2]).toEqual({
+				contentType: 'text/plain',
+				overwrite: true
+			});
 
-			expect(fakeServer.requests.length).toEqual(1);
-			expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('/apps/files/ajax/newfile.php'));
+			deferredCreate.resolve(200);
 
-			var query = fakeServer.requests[0].requestBody;
-			expect(OC.parseQueryString(query)).toEqual({
-				dir: '/subdir',
-				filename: 'test file.txt'
-			});
+			expect(getFileInfoStub.calledOnce).toEqual(true);
+			expect(getFileInfoStub.getCall(0).args[0]).toEqual('/subdir/test.txt');
 
-			fakeServer.requests[0].respond(
+			deferredInfo.resolve(
 				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'success',
-					data: {
-						path: '/subdir',
-						name: 'test file.txt',
-						mimetype: 'text/plain'
-					}
+			   	new FileInfo({
+					path: '/subdir',
+					name: 'test.txt',
+					mimetype: 'text/plain'
 				})
 			);
 
-			var $tr = fileList.findFileEl('test file.txt');
+			var $tr = fileList.findFileEl('test.txt');
 			expect($tr.length).toEqual(1);
 			expect($tr.attr('data-mime')).toEqual('text/plain');
-
-			expect(successStub.calledOnce).toEqual(true);
-			expect(failureStub.notCalled).toEqual(true);
 		});
 		// TODO: error cases
 		// TODO: unique name cases
 	});
-	describe('create directory', function() {
-		it('creates directory with given name and adds it to the list', function() {
-			var deferred = fileList.createDirectory('test directory');
-			var successStub = sinon.stub();
-			var failureStub = sinon.stub();
-
-			deferred.done(successStub);
-			deferred.fail(failureStub);
-
-			expect(fakeServer.requests.length).toEqual(1);
-			expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('/apps/files/ajax/newfolder.php'));
-			var query = fakeServer.requests[0].requestBody;
-			expect(OC.parseQueryString(query)).toEqual({
-				dir: '/subdir',
-				foldername: 'test directory'
-			});
+	describe('create folder', function() {
+		var deferredCreate;
+		var deferredInfo;
+		var createStub;
+		var getFileInfoStub;
+
+		beforeEach(function() {
+			deferredCreate = $.Deferred();
+			deferredInfo = $.Deferred();
+			createStub = sinon.stub(filesClient, 'createDirectory')
+				.returns(deferredCreate.promise());
+			getFileInfoStub = sinon.stub(filesClient, 'getFileInfo')
+				.returns(deferredInfo.promise());
+		});
+		afterEach(function() {
+			createStub.restore();
+			getFileInfoStub.restore();
+		});
+
+		it('creates folder with given name and adds it to the list', function() {
+			fileList.createDirectory('sub dir');
+
+			expect(createStub.calledOnce).toEqual(true);
+			expect(createStub.getCall(0).args[0]).toEqual('/subdir/sub dir');
 
-			fakeServer.requests[0].respond(
+			deferredCreate.resolve(200);
+
+			expect(getFileInfoStub.calledOnce).toEqual(true);
+			expect(getFileInfoStub.getCall(0).args[0]).toEqual('/subdir/sub dir');
+
+			deferredInfo.resolve(
 				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'success',
-					data: {
-						path: '/subdir',
-						name: 'test directory',
-						mimetype: 'httpd/unix-directory'
-					}
+			   	new FileInfo({
+					path: '/subdir',
+					name: 'sub dir',
+					mimetype: 'httpd/unix-directory'
 				})
 			);
 
-			var $tr = fileList.findFileEl('test directory');
+			var $tr = fileList.findFileEl('sub dir');
 			expect($tr.length).toEqual(1);
 			expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
-
-			expect(successStub.calledOnce).toEqual(true);
-			expect(failureStub.notCalled).toEqual(true);
 		});
 		// TODO: error cases
 		// TODO: unique name cases
@@ -2481,14 +2388,14 @@ describe('OCA.Files.FileList tests', function() {
 				expect(ev.result).not.toEqual(false);
 			});
 			it('drop on a folder row inside the table triggers upload to target folder', function() {
-				var ev, formData;
+				var ev;
 				ev = dropOn(fileList.findFileEl('somedir').find('td:eq(2)'), uploadData);
 
 				expect(ev.result).not.toEqual(false);
 				expect(uploadData.targetDir).toEqual('/subdir/somedir');
 			});
 			it('drop on a breadcrumb inside the table triggers upload to target folder', function() {
-				var ev, formData;
+				var ev;
 				fileList.changeDirectory('a/b/c/d');
 				ev = dropOn(fileList.$el.find('.crumb:eq(2)'), uploadData);
 
@@ -2497,32 +2404,50 @@ describe('OCA.Files.FileList tests', function() {
 			});
 		});
 	});
-	describe('Handeling errors', function () {
-		var redirectStub;
+	describe('Handling errors', function () {
+		var deferredList;
+		var getFolderContentsStub;
 
-		beforeEach(function () {
-			redirectStub = sinon.stub(OC, 'redirect');
-
-			fileList = new OCA.Files.FileList($('#app-content-files'));
+		beforeEach(function() {
+			deferredList = $.Deferred();
+			getFolderContentsStub =
+				sinon.stub(filesClient, 'getFolderContents');
+			getFolderContentsStub.onCall(0).returns(deferredList.promise());
+			getFolderContentsStub.onCall(1).returns($.Deferred().promise());
+			fileList.reload();
 		});
-		afterEach(function () {
+		afterEach(function() {
+			getFolderContentsStub.restore();
 			fileList = undefined;
+		});
+		it('redirects to files app in case of auth error', function () {
+			deferredList.reject(401, 'Authentication error');
 
-			redirectStub.restore();
+			expect(redirectStub.calledOnce).toEqual(true);
+			expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files');
+			expect(getFolderContentsStub.calledOnce).toEqual(true);
 		});
-		it('reloads the page on authentication errors', function () {
-			fileList.reload();
-			fakeServer.requests[0].respond(
-				200,
-				{ 'Content-Type': 'application/json' },
-				JSON.stringify({
-					status: 'error',
-					data: {
-						'error': 'authentication_error'
-					}
-				})
-			);
-			expect(redirectStub.calledWith(OC.generateUrl('apps/files'))).toEqual(true);
+		it('redirects to root folder in case of forbidden access', function () {
+			deferredList.reject(403);
+
+			expect(fileList.getCurrentDirectory()).toEqual('/');
+			expect(getFolderContentsStub.calledTwice).toEqual(true);
+		});
+		it('redirects to root folder and shows notification in case of internal server error', function () {
+			expect(notificationStub.notCalled).toEqual(true);
+			deferredList.reject(500);
+
+			expect(fileList.getCurrentDirectory()).toEqual('/');
+			expect(getFolderContentsStub.calledTwice).toEqual(true);
+			expect(notificationStub.calledOnce).toEqual(true);
+		});
+		it('redirects to root folder and shows notification in case of storage not available', function () {
+			expect(notificationStub.notCalled).toEqual(true);
+			deferredList.reject(503, 'Storage not available');
+
+			expect(fileList.getCurrentDirectory()).toEqual('/');
+			expect(getFolderContentsStub.calledTwice).toEqual(true);
+			expect(notificationStub.calledOnce).toEqual(true);
 		});
 	});
 	describe('showFileBusyState', function() {
diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js
index 30e6675c1556c3906aff0219a4b60bfab17e0e8a..b7627d59fdf66945b32531bcaf333b4c097a6c3e 100644
--- a/apps/files/tests/js/filesSpec.js
+++ b/apps/files/tests/js/filesSpec.js
@@ -76,11 +76,11 @@ describe('OCA.Files.Files tests', function() {
 	describe('getDownloadUrl', function() {
 		it('returns the ajax download URL when filename and dir specified', function() {
 			var url = Files.getDownloadUrl('test file.txt', '/subdir');
-			expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
+			expect(url).toEqual(OC.webroot + '/remote.php/webdav/subdir/test%20file.txt');
 		});
-		it('returns the ajax download URL when filename and root dir specific', function() {
+		it('returns the webdav download URL when filename and root dir specified', function() {
 			var url = Files.getDownloadUrl('test file.txt', '/');
-			expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=test%20file.txt');
+			expect(url).toEqual(OC.webroot + '/remote.php/webdav/test%20file.txt');
 		});
 		it('returns the ajax download URL when multiple files specified', function() {
 			var url = Files.getDownloadUrl(['test file.txt', 'abc.txt'], '/subdir');
diff --git a/apps/files_external/js/app.js b/apps/files_external/js/app.js
index bf853f926dcf969bff35e634e7aa92df40acf79e..1bff3014bd663e9cd6e7052b5f55bf0ce9ec71e8 100644
--- a/apps/files_external/js/app.js
+++ b/apps/files_external/js/app.js
@@ -54,7 +54,7 @@ OCA.External.App = {
 		// folder in the files app instead of opening it directly
 		fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
 			OCA.Files.App.setActiveView('files', {silent: true});
-			OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
+			OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
 		});
 		fileActions.setDefault('dir', 'Open');
 		return fileActions;
diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php
deleted file mode 100644
index c7f0bde5d4ab29daa563b403885c7f2c4ca1fe64..0000000000000000000000000000000000000000
--- a/apps/files_sharing/ajax/list.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/**
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <rullzer@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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/>
- *
- */
-
-OCP\JSON::checkAppEnabled('files_sharing');
-
-if(!isset($_GET['t'])){
-	\OC_Response::setStatus(\OC_Response::STATUS_BAD_REQUEST);
-	\OCP\Util::writeLog('core-preview', 'No token parameter was passed', \OCP\Util::DEBUG);
-	exit;
-}
-
-$token = $_GET['t'];
-
-$password = null;
-if (isset($_POST['password'])) {
-	$password = $_POST['password'];
-}
-
-$relativePath = null;
-if (isset($_GET['dir'])) {
-	$relativePath = $_GET['dir'];
-}
-
-$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
-$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
-
-$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password);
-
-$linkItem = $data['linkItem'];
-// Load the files
-$dir = $data['realPath'];
-
-$dir = \OC\Files\Filesystem::normalizePath($dir);
-if (!\OC\Files\Filesystem::is_dir($dir . '/')) {
-	\OC_Response::setStatus(\OC_Response::STATUS_NOT_FOUND);
-	\OCP\JSON::error(array('success' => false));
-	exit();
-}
-
-$data = array();
-
-// make filelist
-$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
-
-$formattedFiles = array();
-foreach ($files as $file) {
-	$entry = \OCA\Files\Helper::formatFileInfo($file);
-	// for now
-	unset($entry['directory']);
-	// do not disclose share owner
-	unset($entry['shareOwner']);
-	// do not disclose if something is a remote shares
-	unset($entry['mountType']);
-	unset($entry['icon']);
-	$entry['permissions'] = \OCP\Constants::PERMISSION_READ;
-	$formattedFiles[] = $entry;
-}
-
-$data['directory'] = $relativePath;
-$data['files'] = $formattedFiles;
-$data['dirToken'] = $linkItem['token'];
-
-$permissions = $linkItem['permissions'];
-
-// if globally disabled
-if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') {
-	// only allow reading
-	$permissions = \OCP\Constants::PERMISSION_READ;
-}
-
-$data['permissions'] = $permissions;
-
-OCP\JSON::success(array('data' => $data));
diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js
index 3168e930829b0d3d24bccc64fd657fef3eb512ec..af198208de2b5584c12f617b6d2d4c0e1e8d9355 100644
--- a/apps/files_sharing/js/app.js
+++ b/apps/files_sharing/js/app.js
@@ -142,7 +142,7 @@ OCA.Sharing.App = {
 		// folder in the files app instead of opening it directly
 		fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
 			OCA.Files.App.setActiveView('files', {silent: true});
-			OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
+			OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
 		});
 		fileActions.setDefault('dir', 'Open');
 		return fileActions;
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 246b639f652c3647f93dca2e739a5a8363584662..8269112992612ac18995030f2af2400ee1654c5b 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -48,8 +48,20 @@ OCA.Sharing.PublicApp = {
 		this._initialized = true;
 		this.initialDir = $('#dir').val();
 
+		var token = $('#sharingToken').val();
+
 		// file list mode ?
 		if ($el.find('#filestable').length) {
+			var filesClient = new OC.Files.Client({
+				host: OC.getHost(),
+				port: OC.getPort(),
+				userName: token,
+				// note: password not be required, the endpoint
+				// will recognize previous validation from the session
+				root: OC.getRootPath() + '/public.php/webdav',
+				useHTTPS: OC.getProtocol() === 'https'
+			});
+
 			this.fileList = new OCA.Files.FileList(
 				$el,
 				{
@@ -58,7 +70,8 @@ OCA.Sharing.PublicApp = {
 					dragOptions: dragOptions,
 					folderDropOptions: folderDropOptions,
 					fileActions: fileActions,
-					detailsViewEnabled: false
+					detailsViewEnabled: false,
+					filesClient: filesClient
 				}
 			);
 			this.files = OCA.Files.Files;
@@ -88,7 +101,6 @@ OCA.Sharing.PublicApp = {
 
 
 		// dynamically load image previews
-		var token = $('#sharingToken').val();
 		var bottomMargin = 350;
 		var previewWidth = Math.ceil($(window).width() * window.devicePixelRatio);
 		var previewHeight = Math.ceil(($(window).height() - bottomMargin) * window.devicePixelRatio);
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 63225a0d8ec6c55253b8b5fe72c4a2e6758de4db..3d105f283d85bdb54ae1b1a8d5c66045000038c6 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -50,7 +50,7 @@
 				if (fileData.shareOwner) {
 					tr.attr('data-share-owner', fileData.shareOwner);
 					// user should always be able to rename a mount point
-					if (fileData.isShareMountPoint) {
+					if (fileData.mountType === 'shared-root') {
 						tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE);
 					}
 				}
@@ -68,6 +68,26 @@
 				return fileInfo;
 			};
 
+			var NS_OC = 'http://owncloud.org/ns';
+
+			var oldGetWebdavProperties = fileList._getWebdavProperties;
+			fileList._getWebdavProperties = function() {
+				var props = oldGetWebdavProperties.apply(this, arguments);
+				props.push('{' + NS_OC + '}owner-display-name');
+				return props;
+			};
+
+			fileList.filesClient.addFileInfoParser(function(response) {
+				var data = {};
+				var props = response.propStat[0].properties;
+				var permissionsProp = props['{' + NS_OC + '}permissions'];
+
+				if (permissionsProp && permissionsProp.indexOf('S') >= 0) {
+					data.shareOwner = props['{' + NS_OC + '}owner-display-name'];
+				}
+				return data;
+			});
+
 			// use delegate to catch the case with multiple file lists
 			fileList.$el.on('fileActionsReady', function(ev){
 				var fileList = ev.fileList;
diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js
index 68bfd63ec89ac4788eca875ed464944353f2fc45..a799d4a94c2a0b437fe4a88311e494cb1b3d32ae 100644
--- a/apps/files_sharing/js/sharedfilelist.js
+++ b/apps/files_sharing/js/sharedfilelist.js
@@ -231,6 +231,7 @@
 			files = _.chain(files)
 				// convert share data to file data
 				.map(function(share) {
+					// TODO: use OC.Files.FileInfo
 					var file = {
 						id: share.file_source,
 						icon: OC.MimeType.getIconUrl(share.mimetype),
@@ -242,9 +243,6 @@
 					}
 					else {
 						file.type = 'file';
-						if (share.isPreviewAvailable) {
-							file.isPreviewAvailable = true;
-						}
 					}
 					file.share = {
 						id: share.id,
diff --git a/apps/files_sharing/tests/js/publicAppSpec.js b/apps/files_sharing/tests/js/publicAppSpec.js
index d496b78acfac6e80a98a249db67e4e853144c6ea..1ea5f7ed1bc660dda72097746e7359a484a8d45c 100644
--- a/apps/files_sharing/tests/js/publicAppSpec.js
+++ b/apps/files_sharing/tests/js/publicAppSpec.js
@@ -21,11 +21,13 @@
 
 describe('OCA.Sharing.PublicApp tests', function() {
 	var App = OCA.Sharing.PublicApp;
+	var hostStub, protocolStub, webrootStub;
 	var $preview;
-	var fileListIn;
-	var fileListOut;
 
 	beforeEach(function() {
+		protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
+		hostStub = sinon.stub(OC, 'getHost').returns('example.com');
+		webrootStub = sinon.stub(OC, 'getRootPath').returns('/owncloud');
 		$preview = $('<div id="preview"></div>');
 		$('#testArea').append($preview);
 		$preview.append(
@@ -35,6 +37,12 @@ describe('OCA.Sharing.PublicApp tests', function() {
 		);
 	});
 
+	afterEach(function() {
+		protocolStub.restore();
+		hostStub.restore();
+		webrootStub.restore();
+	});
+
 	describe('File list', function() {
 		// TODO: this should be moved to a separate file once the PublicFileList is extracted from public.js
 		beforeEach(function() {
@@ -78,6 +86,12 @@ describe('OCA.Sharing.PublicApp tests', function() {
 			App._initialized = false;
 		});
 
+		it('Uses public webdav endpoint', function() {
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('PROPFIND');
+			expect(fakeServer.requests[0].url).toEqual('https://sh4tok@example.com/owncloud/public.php/webdav/subdir');
+		});
+
 		describe('Download Url', function() {
 			var fileList;
 
diff --git a/apps/files_sharing/tests/js/sharedfilelistSpec.js b/apps/files_sharing/tests/js/sharedfilelistSpec.js
index b4b6ac4954a9df01ba730ed8006a8da1023f2c8a..fdc9de49c174c5bdab1d3cb59d4dc6db62b00b25 100644
--- a/apps/files_sharing/tests/js/sharedfilelistSpec.js
+++ b/apps/files_sharing/tests/js/sharedfilelistSpec.js
@@ -166,8 +166,7 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-id')).toEqual('7');
 			expect($tr.find('a.name').attr('href')).toEqual(
 				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Flocal%20path&files=local%20name.txt'
+				'/remote.php/webdav/local%20path/local%20name.txt'
 			);
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 
@@ -185,8 +184,7 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-id')).toEqual('8');
 			expect($tr.find('a.name').attr('href')).toEqual(
 				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2F&files=b.txt'
+				'/remote.php/webdav/b.txt'
 			);
 			expect($tr.find('.nametext').text().trim()).toEqual('b.txt');
 		});
@@ -338,8 +336,7 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-id')).toEqual('7');
 			expect($tr.find('a.name').attr('href')).toEqual(
 				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Flocal%20path&files=local%20name.txt'
+				'/remote.php/webdav/local%20path/local%20name.txt'
 			);
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 		});
@@ -429,9 +426,8 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-owner')).not.toBeDefined();
 			expect($tr.attr('data-share-id')).toEqual('7');
 			expect($tr.find('a.name').attr('href')).toEqual(
-					OC.webroot +
-					'/index.php/apps/files/ajax/download.php' +
-					'?dir=%2Flocal%20path&files=local%20name.txt');
+				OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
+			);
 
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 		});
@@ -498,9 +494,7 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-owner')).not.toBeDefined();
 			expect($tr.attr('data-share-id')).toEqual('7,8,9');
 			expect($tr.find('a.name').attr('href')).toEqual(
-				OC.webroot +
-				'/index.php/apps/files/ajax/download.php' +
-				'?dir=%2Flocal%20path&files=local%20name.txt'
+				OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
 			);
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 		});
@@ -592,9 +586,8 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-owner')).not.toBeDefined();
 			expect($tr.attr('data-share-id')).toEqual('7');
 			expect($tr.find('a.name').attr('href')).toEqual(
-					OC.webroot +
-					'/index.php/apps/files/ajax/download.php' +
-					'?dir=%2Flocal%20path&files=local%20name.txt');
+				OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
+			);
 
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 		});
@@ -634,8 +627,7 @@ describe('OCA.Sharing.FileList tests', function() {
 			expect($tr.attr('data-share-id')).toEqual('7');
 			expect($tr.find('a.name').attr('href')).toEqual(
 					OC.webroot +
-					'/index.php/apps/files/ajax/download.php' +
-					'?dir=%2Flocal%20path&files=local%20name.txt');
+					'/remote.php/webdav/local%20path/local%20name.txt');
 
 			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
 		});
diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js
index 1f46f568bf2fadd60f7071139316b33935a3dd26..600a8ce2b03d03f01c753f371489cce126163d73 100644
--- a/apps/files_trashbin/js/app.js
+++ b/apps/files_trashbin/js/app.js
@@ -38,10 +38,7 @@ OCA.Trashbin.App = {
 		var fileActions = new OCA.Files.FileActions();
 		fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
 			var dir = context.fileList.getCurrentDirectory();
-			if (dir !== '/') {
-				dir = dir + '/';
-			}
-			context.fileList.changeDirectory(dir + filename);
+			context.fileList.changeDirectory(OC.joinPaths(dir, filename));
 		});
 
 		fileActions.setDefault('dir', 'Open');
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index 6b624e333a06315de2a9243fca8b26395be4b21c..5812aff82f7abdd34cc54f6678aeb8adcf4379d1 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/apps/files_trashbin/js/filelist.js
@@ -283,7 +283,77 @@
 
 		isSelectedDeletable: function() {
 			return true;
-		}
+		},
+
+		/**
+		 * Reloads the file list using ajax call
+		 *
+		 * @return ajax call object
+		 */
+		reload: function() {
+			this._selectedFiles = {};
+			this._selectionSummary.clear();
+			this.$el.find('.select-all').prop('checked', false);
+			this.showMask();
+			if (this._reloadCall) {
+				this._reloadCall.abort();
+			}
+			this._reloadCall = $.ajax({
+				url: this.getAjaxUrl('list'),
+				data: {
+					dir : this.getCurrentDirectory(),
+					sort: this._sort,
+					sortdirection: this._sortDirection
+				}
+			});
+			var callBack = this.reloadCallback.bind(this);
+			return this._reloadCall.then(callBack, callBack);
+		},
+		reloadCallback: function(result) {
+			delete this._reloadCall;
+			this.hideMask();
+
+			if (!result || result.status === 'error') {
+				// if the error is not related to folder we're trying to load, reload the page to handle logout etc
+				if (result.data.error === 'authentication_error' ||
+					result.data.error === 'token_expired' ||
+					result.data.error === 'application_not_enabled'
+				) {
+					OC.redirect(OC.generateUrl('apps/files'));
+				}
+				OC.Notification.show(result.data.message);
+				return false;
+			}
+
+			// Firewall Blocked request?
+			if (result.status === 403) {
+				// Go home
+				this.changeDirectory('/');
+				OC.Notification.show(t('files', 'This operation is forbidden'));
+				return false;
+			}
+
+			// Did share service die or something else fail?
+			if (result.status === 500) {
+				// Go home
+				this.changeDirectory('/');
+				OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
+				return false;
+			}
+
+			if (result.status === 404) {
+				// go back home
+				this.changeDirectory('/');
+				return false;
+			}
+			// aborted ?
+			if (result.status === 0){
+				return true;
+			}
+
+			this.setFiles(result.data.files);
+			return true;
+		},
 
 	});
 
diff --git a/bower.json b/bower.json
index e8bb9a35f321b9f9181afe77509d21fd0e7ae214..77d7d75e85c5ac4d3f25bd70624583e89440b6ab 100644
--- a/bower.json
+++ b/bower.json
@@ -27,6 +27,8 @@
     "strengthify": "0.4.2",
     "underscore": "~1.8.0",
     "bootstrap": "~3.3.5",
-    "backbone": "~1.2.1"
+    "backbone": "~1.2.1",
+    "davclient.js": "https://github.com/evert/davclient.js.git",
+    "es6-promise": "https://github.com/jakearchibald/es6-promise.git#~2.3.0"
   }
 }
diff --git a/buildjsdocs.sh b/buildjsdocs.sh
index 90562558f66ac8943148bc07108606ba479f8535..57eefb29fd046e11c82e472089dd87291f23fddd 100755
--- a/buildjsdocs.sh
+++ b/buildjsdocs.sh
@@ -11,7 +11,7 @@ NPM="$(which npm 2>/dev/null)"
 PREFIX="build"
 OUTPUT_DIR="build/jsdocs"
 
-JS_FILES="core/js/*.js apps/*/js/*.js"
+JS_FILES="core/js/*.js core/js/**/*.js apps/*/js/*.js"
 
 if test -z "$NPM"
 then
diff --git a/core/js/core.json b/core/js/core.json
index a80636e8463667846e8d4f0087ae608bf88db057..c7621a08d627f6c2ad8872d13c3a65461b1e6d5a 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -8,7 +8,9 @@
 		"handlebars/handlebars.js",
 		"blueimp-md5/js/md5.js",
 		"bootstrap/js/tooltip.js",
-		"backbone/backbone.js"
+		"backbone/backbone.js",
+		"es6-promise/dist/es6-promise.js",
+		"davclient.js/lib/client.js"
 	],
 	"libraries": [
 		"jquery-showpassword.js",
@@ -39,6 +41,8 @@
 		"setupchecks.js",
 		"../search/js/search.js",
 		"mimetype.js",
-		"mimetypelist.js"
+		"mimetypelist.js",
+		"files/fileinfo.js",
+		"files/client.js"
 	]
 }
diff --git a/core/js/files/client.js b/core/js/files/client.js
new file mode 100644
index 0000000000000000000000000000000000000000..82cf3ff51218062289e27e768ec8ed39c2f73210
--- /dev/null
+++ b/core/js/files/client.js
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global dav */
+
+(function(OC, FileInfo) {
+	/**
+	 * @class OC.Files.Client
+	 * @classdesc Client to access files on the server
+	 *
+	 * @param {Object} options
+	 * @param {String} options.host host name
+	 * @param {int} [options.port] port
+	 * @param {boolean} [options.useHTTPS] whether to use https
+	 * @param {String} [options.root] root path
+	 * @param {String} [options.userName] user name
+	 * @param {String} [options.password] password
+	 *
+	 * @since 8.2
+	 */
+	var Client = function(options) {
+		this._root = options.root;
+		if (this._root.charAt(this._root.length - 1) === '/') {
+			this._root = this._root.substr(0, this._root.length - 1);
+		}
+
+		var url = 'http://';
+		if (options.useHTTPS) {
+			url = 'https://';
+		}
+		var credentials = '';
+		if (options.userName) {
+			credentials += encodeURIComponent(options.userName);
+		}
+		if (options.password) {
+			credentials += ':' + encodeURIComponent(options.password);
+		}
+		if (credentials.length > 0) {
+			url += credentials + '@';
+		}
+
+		url += options.host + this._root;
+		this._defaultHeaders = options.defaultHeaders || {'X-Requested-With': 'XMLHttpRequest'};
+		this._baseUrl = url;
+		this._client = new dav.Client({
+			baseUrl: this._baseUrl,
+			xmlNamespaces: {
+				'DAV:': 'd',
+				'http://owncloud.org/ns': 'oc'
+			}
+		});
+		this._client.xhrProvider = _.bind(this._xhrProvider, this);
+	};
+
+	Client.NS_OWNCLOUD = 'http://owncloud.org/ns';
+	Client.NS_DAV = 'DAV:';
+	Client._PROPFIND_PROPERTIES = [
+		/**
+		 * Modified time
+		 */
+		[Client.NS_DAV, 'getlastmodified'],
+		/**
+		 * Etag
+		 */
+		[Client.NS_DAV, 'getetag'],
+		/**
+		 * Mime type
+		 */
+		[Client.NS_DAV, 'getcontenttype'],
+		/**
+		 * Resource type "collection" for folders, empty otherwise
+		 */
+		[Client.NS_DAV, 'resourcetype'],
+		/**
+		 * File id
+		 */
+		[Client.NS_OWNCLOUD, 'fileid'],
+		/**
+		 * Letter-coded permissions
+		 */
+		[Client.NS_OWNCLOUD, 'permissions'],
+		//[Client.NS_OWNCLOUD, 'downloadURL'],
+		/**
+		 * Folder sizes
+		 */
+		[Client.NS_OWNCLOUD, 'size'],
+		/**
+		 * File sizes
+		 */
+		[Client.NS_DAV, 'getcontentlength']
+	];
+
+	/**
+	 * @memberof OC.Files
+	 */
+	Client.prototype = {
+
+		/**
+		 * Root path of the Webdav endpoint
+		 *
+		 * @type string
+		 */
+		_root: null,
+
+		/**
+		 * Client from the library
+		 *
+		 * @type dav.Client
+		 */
+		_client: null,
+
+		/**
+		 * Array of file info parsing functions.
+		 *
+		 * @type Array<OC.Files.Client~parseFileInfo>
+		 */
+		_fileInfoParsers: [],
+
+		/**
+		 * Returns the configured XHR provider for davclient
+		 * @return {XMLHttpRequest}
+		 */
+		_xhrProvider: function() {
+			var headers = this._defaultHeaders;
+			var xhr = new XMLHttpRequest();
+			var oldOpen = xhr.open;
+			// override open() method to add headers
+			xhr.open = function() {
+				var result = oldOpen.apply(this, arguments);
+				_.each(headers, function(value, key) {
+					xhr.setRequestHeader(key, value);
+				});
+				return result;
+			};
+			return xhr;
+		},
+
+		/**
+		 * Prepends the base url to the given path sections
+		 *
+		 * @param {...String} path sections
+		 *
+		 * @return {String} base url + joined path, any leading or trailing slash
+		 * will be kept
+		 */
+		_buildUrl: function() {
+			var path = this._buildPath.apply(this, arguments);
+			if (path.charAt([path.length - 1]) === '/') {
+				path = path.substr(0, path.length - 1);
+			}
+			if (path.charAt(0) === '/') {
+				path = path.substr(1);
+			}
+			return this._baseUrl + '/' + path;
+		},
+
+		/**
+		 * Append the path to the root and also encode path
+		 * sections
+		 *
+		 * @param {...String} path sections
+		 *
+		 * @return {String} joined path, any leading or trailing slash
+		 * will be kept
+		 */
+		_buildPath: function() {
+			var path = OC.joinPaths.apply(this, arguments);
+			var sections = path.split('/');
+			var i;
+			for (i = 0; i < sections.length; i++) {
+				sections[i] = encodeURIComponent(sections[i]);
+			}
+			path = sections.join('/');
+			return path;
+		},
+
+		/**
+		 * Parse headers string into a map
+		 *
+		 * @param {string} headersString headers list as string
+		 *
+		 * @return {Object.<String,Array>} map of header name to header contents
+		 */
+		_parseHeaders: function(headersString) {
+			var headerRows = headersString.split('\n');
+			var headers = {};
+			for (var i = 0; i < headerRows.length; i++) {
+				var sepPos = headerRows[i].indexOf(':');
+				if (sepPos < 0) {
+					continue;
+				}
+
+				var headerName = headerRows[i].substr(0, sepPos);
+				var headerValue = headerRows[i].substr(sepPos + 2);
+
+				if (!headers[headerName]) {
+					// make it an array
+					headers[headerName] = [];
+				}
+
+				headers[headerName].push(headerValue);
+			}
+			return headers;
+		},
+
+		/**
+		 * Parses the etag response which is in double quotes.
+		 *
+		 * @param {string} etag etag value in double quotes
+		 *
+		 * @return {string} etag without double quotes
+		 */
+		_parseEtag: function(etag) {
+			if (etag.charAt(0) === '"') {
+				return etag.split('"')[1];
+			}
+			return etag;
+		},
+
+		/**
+		 * Parse Webdav result
+		 *
+		 * @param {Object} response XML object
+		 *
+		 * @return {Array.<FileInfo>} array of file info
+		 */
+		_parseFileInfo: function(response) {
+			var path = response.href;
+			if (path.substr(0, this._root.length) === this._root) {
+				path = path.substr(this._root.length);
+			}
+
+			if (path.charAt(path.length - 1) === '/') {
+				path = path.substr(0, path.length - 1);
+			}
+
+			path = '/' + decodeURIComponent(path);
+
+			if (response.propStat.length === 1 && response.propStat[0].status !== 200) {
+				return null;
+			}
+
+			var props = response.propStat[0].properties;
+
+			var data = {
+				id: props['{' + Client.NS_OWNCLOUD + '}fileid'],
+				path: OC.dirname(path) || '/',
+				name: OC.basename(path),
+				mtime: new Date(props['{' + Client.NS_DAV + '}getlastmodified'])
+			};
+
+			var etagProp = props['{' + Client.NS_DAV + '}getetag'];
+			if (!_.isUndefined(etagProp)) {
+				data.etag = this._parseEtag(etagProp);
+			}
+
+			var sizeProp = props['{' + Client.NS_DAV + '}getcontentlength'];
+			if (!_.isUndefined(sizeProp)) {
+				data.size = parseInt(sizeProp, 10);
+			}
+
+			sizeProp = props['{' + Client.NS_OWNCLOUD + '}size'];
+			if (!_.isUndefined(sizeProp)) {
+				data.size = parseInt(sizeProp, 10);
+			}
+
+			var contentType = props['{' + Client.NS_DAV + '}getcontenttype'];
+			if (!_.isUndefined(contentType)) {
+				data.mimetype = contentType;
+			}
+
+			var resType = props['{' + Client.NS_DAV + '}resourcetype'];
+			var isFile = true;
+			if (!data.mimetype && resType) {
+				var xmlvalue = resType[0];
+				if (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {
+					data.mimetype = 'httpd/unix-directory';
+					isFile = false;
+				}
+			}
+
+			data.permissions = OC.PERMISSION_READ;
+			var permissionProp = props['{' + Client.NS_OWNCLOUD + '}permissions'];
+			if (!_.isUndefined(permissionProp)) {
+				var permString = permissionProp || '';
+				data.mountType = null;
+				for (var i = 0; i < permString.length; i++) {
+					var c = permString.charAt(i);
+					switch (c) {
+						// FIXME: twisted permissions
+						case 'C':
+						case 'K':
+							data.permissions |= OC.PERMISSION_CREATE;
+							if (!isFile) {
+								data.permissions |= OC.PERMISSION_UPDATE;
+							}
+							break;
+						case 'W':
+							if (isFile) {
+								// also add create permissions
+								data.permissions |= OC.PERMISSION_CREATE;
+							}
+							data.permissions |= OC.PERMISSION_UPDATE;
+							break;
+						case 'D':
+							data.permissions |= OC.PERMISSION_DELETE;
+							break;
+						case 'R':
+							data.permissions |= OC.PERMISSION_SHARE;
+							break;
+						case 'M':
+							if (!data.mountType) {
+								// TODO: how to identify external-root ?
+								data.mountType = 'external';
+							}
+							break;
+						case 'S':
+							// TODO: how to identify shared-root ?
+							data.mountType = 'shared';
+							break;
+					}
+				}
+			}
+
+			// extend the parsed data using the custom parsers
+			_.each(this._fileInfoParsers, function(parserFunction) {
+				_.extend(data, parserFunction(response) || {});
+			});
+
+			return new FileInfo(data);
+		},
+
+		/**
+		 * Parse Webdav multistatus
+		 *
+		 * @param {Array} responses
+		 */
+		_parseResult: function(responses) {
+			var self = this;
+			return _.map(responses, function(response) {
+				return self._parseFileInfo(response);
+			});
+		},
+
+		/**
+		 * Returns whether the given status code means success
+		 *
+		 * @param {int} status status code
+		 *
+		 * @return true if status code is between 200 and 299 included
+		 */
+		_isSuccessStatus: function(status) {
+			return status >= 200 && status <= 299;
+		},
+
+		/**
+		 * Returns the default PROPFIND properties to use during a call.
+		 *
+		 * @return {Array.<Object>} array of properties
+		 */
+		getPropfindProperties: function() {
+			if (!this._propfindProperties) {
+				this._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {
+					return '{' + propDef[0] + '}' + propDef[1];
+				});
+			}
+			return this._propfindProperties;
+		},
+
+		/**
+		 * Lists the contents of a directory
+		 *
+		 * @param {String} path path to retrieve
+		 * @param {Object} [options] options
+		 * @param {boolean} [options.includeParent=false] set to true to keep
+		 * the parent folder in the result list
+		 * @param {Array} [options.properties] list of Webdav properties to retrieve
+		 *
+		 * @return {Promise} promise
+		 */
+		getFolderContents: function(path, options) {
+			if (!path) {
+				path = '';
+			}
+			options = options || {};
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+			var properties;
+			if (_.isUndefined(options.properties)) {
+				properties = this.getPropfindProperties();
+			} else {
+				properties = options.properties;
+			}
+
+			// TODO: headers
+			this._client.propFind(
+				this._buildUrl(path),
+				properties,
+				1
+			).then(function(result) {
+				if (self._isSuccessStatus(result.status)) {
+					var results = self._parseResult(result.body);
+					if (!options || !options.includeParent) {
+						// remove root dir, the first entry
+						results.shift();
+					}
+					deferred.resolve(result.status, results);
+				} else {
+					deferred.reject(result.status);
+				}
+			});
+			return promise;
+		},
+
+		/**
+		 * Returns the file info of a given path.
+		 *
+		 * @param {String} path path
+		 * @param {Array} [options.properties] list of Webdav properties to retrieve
+		 *
+		 * @return {Promise} promise
+		 */
+		getFileInfo: function(path, options) {
+			if (!path) {
+				path = '';
+			}
+			options = options || {};
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+			var properties;
+			if (_.isUndefined(options.properties)) {
+				properties = this.getPropfindProperties();
+			} else {
+				properties = options.properties;
+			}
+
+			// TODO: headers
+			this._client.propFind(
+				this._buildUrl(path),
+				properties,
+				0
+			).then(
+				function(result) {
+					if (self._isSuccessStatus(result.status)) {
+						deferred.resolve(result.status, self._parseResult([result.body])[0]);
+					} else {
+						deferred.reject(result.status);
+					}
+				}
+			);
+			return promise;
+		},
+
+		/**
+		 * Returns the contents of the given file.
+		 *
+		 * @param {String} path path to file
+		 *
+		 * @return {Promise}
+		 */
+		getFileContents: function(path) {
+			if (!path) {
+				throw 'Missing argument "path"';
+			}
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+
+			this._client.request(
+				'GET',
+				this._buildUrl(path),
+				this._defaultHeaders
+			).then(
+				function(result) {
+					if (self._isSuccessStatus(result.status)) {
+						deferred.resolve(result.status, result.body);
+					} else {
+						deferred.reject(result.status);
+					}
+				}
+			);
+			return promise;
+		},
+
+		/**
+		 * Puts the given data into the given file.
+		 *
+		 * @param {String} path path to file
+		 * @param {String} body file body
+		 * @param {Object} [options]
+		 * @param {String} [options.contentType='text/plain'] content type
+		 * @param {bool} [options.overwrite=true] whether to overwrite an existing file
+		 *
+		 * @return {Promise}
+		 */
+		putFileContents: function(path, body, options) {
+			if (!path) {
+				throw 'Missing argument "path"';
+			}
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+			options = options || {};
+			var headers = _.extend({}, this._defaultHeaders);
+			var contentType = 'text/plain';
+			if (options.contentType) {
+				contentType = options.contentType;
+			}
+
+			headers['Content-Type'] = contentType;
+
+			if (_.isUndefined(options.overwrite) || options.overwrite) {
+				// will trigger 412 precondition failed if a file already exists
+				headers['If-None-Match'] = '*';
+			}
+
+			this._client.request(
+				'PUT',
+				this._buildUrl(path),
+				headers,
+				body || ''
+			).then(
+				function(result) {
+					if (self._isSuccessStatus(result.status)) {
+						deferred.resolve(result.status);
+					} else {
+						deferred.reject(result.status);
+					}
+				}
+			);
+			return promise;
+		},
+
+		_simpleCall: function(method, path) {
+			if (!path) {
+				throw 'Missing argument "path"';
+			}
+
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+
+			this._client.request(
+				method,
+				this._buildUrl(path),
+				this._defaultHeaders
+			).then(
+				function(result) {
+					if (self._isSuccessStatus(result.status)) {
+						deferred.resolve(result.status);
+					} else {
+						deferred.reject(result.status);
+					}
+				}
+			);
+			return promise;
+		},
+
+		/**
+		 * Creates a directory
+		 *
+		 * @param {String} path path to create
+		 *
+		 * @return {Promise}
+		 */
+		createDirectory: function(path) {
+			return this._simpleCall('MKCOL', path);
+		},
+
+		/**
+		 * Deletes a file or directory
+		 *
+		 * @param {String} path path to delete
+		 *
+		 * @return {Promise}
+		 */
+		remove: function(path) {
+			return this._simpleCall('DELETE', path);
+		},
+
+		/**
+		 * Moves path to another path
+		 *
+		 * @param {String} path path to move
+		 * @param {String} destinationPath destination path
+		 * @param {boolean} [allowOverwrite=false] true to allow overwriting,
+		 * false otherwise
+		 *
+		 * @return {Promise} promise
+		 */
+		move: function(path, destinationPath, allowOverwrite) {
+			if (!path) {
+				throw 'Missing argument "path"';
+			}
+			if (!destinationPath) {
+				throw 'Missing argument "destinationPath"';
+			}
+
+			var self = this;
+			var deferred = $.Deferred();
+			var promise = deferred.promise();
+			var headers =
+				_.extend({
+					'Destination' : this._buildUrl(destinationPath)
+				}, this._defaultHeaders);
+
+			if (!allowOverwrite) {
+				headers['Overwrite'] = 'F';
+			}
+
+			this._client.request(
+				'MOVE',
+				this._buildUrl(path),
+				headers
+			).then(
+				function(response) {
+					if (self._isSuccessStatus(response.status)) {
+						deferred.resolve(response.status);
+					} else {
+						deferred.reject(response.status);
+					}
+				}
+			);
+			return promise;
+		},
+
+		/**
+		 * Add a file info parser function
+		 *
+		 * @param {OC.Files.Client~parseFileInfo>}
+		 */
+		addFileInfoParser: function(parserFunction) {
+			this._fileInfoParsers.push(parserFunction);
+		}
+
+	};
+
+	/**
+	 * File info parser function
+	 *
+	 * This function receives a list of Webdav properties as input and
+	 * should return a hash array of parsed properties, if applicable.
+	 *
+	 * @callback OC.Files.Client~parseFileInfo
+	 * @param {Object} XML Webdav properties
+     * @return {Array} array of parsed property values
+	 */
+
+	if (!OC.Files) {
+		/**
+		 * @namespace OC.Files
+		 *
+		 * @since 8.2
+		 */
+		OC.Files = {};
+	}
+
+	/**
+	 * Returns the default instance of the files client
+	 *
+	 * @return {OC.Files.Client} default client
+	 *
+	 * @since 8.2
+	 */
+	OC.Files.getClient = function() {
+		if (OC.Files._defaultClient) {
+			return OC.Files._defaultClient;
+		}
+
+		var client = new OC.Files.Client({
+			host: OC.getHost(),
+			port: OC.getPort(),
+			root: OC.linkToRemoteBase('webdav'),
+			useHTTPS: OC.getProtocol() === 'https'
+		});
+		OC.Files._defaultClient = client;
+		return client;
+	};
+
+	OC.Files.Client = Client;
+})(OC, OC.Files.FileInfo);
+
diff --git a/core/js/files/fileinfo.js b/core/js/files/fileinfo.js
new file mode 100644
index 0000000000000000000000000000000000000000..3bf68d88b15c2cdbae1a8e19cc6b4bc08dce9a67
--- /dev/null
+++ b/core/js/files/fileinfo.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function(OC) {
+
+	/**
+	 * @class OC.Files.FileInfo
+	 * @classdesc File information
+	 *
+	 * @param {Object} data file data, see attributes for details
+	 *
+	 * @since 8.2
+	 */
+	var FileInfo = function(data) {
+		var self = this;
+		_.each(data, function(value, key) {
+			if (!_.isFunction(value)) {
+				self[key] = value;
+			}
+		});
+
+		if (!_.isUndefined(this.id)) {
+			this.id = parseInt(data.id, 10);
+		}
+
+		// TODO: normalize path
+		this.path = data.path || '';
+
+		if (this.type === 'dir') {
+			this.mimetype = 'httpd/unix-directory';
+		} else {
+			this.mimetype = this.mimetype || 'application/octet-stream';
+		}
+
+		if (!this.type) {
+			if (this.mimetype === 'httpd/unix-directory') {
+				this.type = 'dir';
+			} else {
+				this.type = 'file';
+			}
+		}
+	};
+
+	/**
+	 * @memberof OC.Files
+	 */
+	FileInfo.prototype = {
+		/**
+		 * File id
+		 *
+		 * @type int
+		 */
+		id: null,
+
+		/**
+		 * File name
+		 *
+		 * @type String
+		 */
+		name: null,
+
+		/**
+		 * Path leading to the file, without the file name,
+		 * and with a leading slash.
+		 *
+		 * @type String
+		 */
+		path: null,
+
+		/**
+		 * Mime type
+		 *
+		 * @type String
+		 */
+		mimetype: null,
+
+		/**
+		 * Icon URL.
+		 *
+		 * Can be used to override the mime type icon.
+		 *
+		 * @type String
+		 */
+		icon: null,
+
+		/**
+		 * File type. 'file'  for files, 'dir' for directories.
+		 *
+		 * @type String
+		 * @deprecated rely on mimetype instead
+		 */
+		type: null,
+
+		/**
+		 * Permissions.
+		 *
+		 * @see OC#PERMISSION_ALL for permissions
+		 * @type int
+		 */
+		permissions: null,
+
+		/**
+		 * Modification time
+		 *
+		 * @type int
+		 */
+		mtime: null,
+
+		/**
+		 * Etag
+		 *
+		 * @type String
+		 */
+		etag: null,
+
+		/**
+		 * Mount type.
+		 *
+		 * One of null, "external-root", "shared" or "shared-root"
+		 *
+		 * @type string
+		 */
+		mountType: null
+	};
+
+	if (!OC.Files) {
+		OC.Files = {};
+	}
+	OC.Files.FileInfo = FileInfo;
+})(OC);
+
diff --git a/core/js/files/iedavclient.js b/core/js/files/iedavclient.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc6bce2f9aeb0c84277eac9366eaa139344656db
--- /dev/null
+++ b/core/js/files/iedavclient.js
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global dav */
+(function(dav) {
+
+	/**
+	 * Override davclient.js methods with IE-compatible logic
+	 */
+	dav.Client.prototype = _.extend({}, dav.Client.prototype, {
+
+		/**
+		 * Generates a propFind request.
+		 *
+		 * @param {string} url Url to do the propfind request on
+		 * @param {Array} properties List of properties to retrieve.
+		 * @return {Promise}
+		 */
+		propFind : function(url, properties, depth) {
+
+			if(typeof depth == "undefined") {
+				depth = 0;
+			}
+
+			var headers = {
+				Depth          : depth,
+				'Content-Type' : 'application/xml; charset=utf-8'
+			};
+
+			var body =
+				'<?xml version="1.0"?>\n' +
+				'<d:propfind ';
+
+			var namespace;
+			for (namespace in this.xmlNamespaces) {
+				body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
+			}
+			body += '>\n' +
+				'  <d:prop>\n';
+
+			for(var ii in properties) {
+				var propText = properties[ii];
+				if (typeof propText !== 'string') {
+					// can happen on IE8
+					continue;
+				}
+				var property = this.parseClarkNotation(properties[ii]);
+				if (this.xmlNamespaces[property.namespace]) {
+					body+='    <' + this.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
+				} else {
+					body+='    <x:' + property.name + ' xmlns:x="' + property.namespace + '" />\n';
+				}
+
+			}
+			body+='  </d:prop>\n';
+			body+='</d:propfind>';
+
+			return this.request('PROPFIND', url, headers, body).then(
+				function(result) {
+					var elements = this.parseMultiStatus(result.xhr.responseXML);
+					var response;
+					if (depth===0) {
+						response = {
+							status: result.status,
+							body: elements[0]
+						};
+					} else {
+						response = {
+							status: result.status,
+							body: elements
+						};
+					}
+					return response;
+
+				}.bind(this)
+			);
+
+		},
+
+
+		_getElementsByTagName: function(node, name, resolver) {
+			var parts = name.split(':');
+			var tagName = parts[1];
+			var namespace = resolver(parts[0]);
+			if (node.getElementsByTagNameNS) {
+				return node.getElementsByTagNameNS(namespace, tagName);
+			}
+			return node.getElementsByTagName(name);
+		},
+
+		/**
+		 * Parses a multi-status response body.
+		 *
+		 * @param {string} xmlBody
+		 * @param {Array}
+		 */
+		parseMultiStatus : function(doc) {
+
+			var result = [];
+			var resolver = function(foo) {
+				var ii;
+				for(ii in this.xmlNamespaces) {
+					if (this.xmlNamespaces[ii] === foo) {
+						return ii;
+					}
+				}
+			}.bind(this);
+
+			var responses = this._getElementsByTagName(doc, 'd:response', resolver);
+			var i;
+			for (i = 0; i < responses.length; i++) {
+				var responseNode = responses[i];
+				var response = {
+					href : null,
+					propStat : []
+				};
+
+				var hrefNode = this._getElementsByTagName(responseNode, 'd:href', resolver)[0];
+
+				response.href = hrefNode.textContent || hrefNode.text;
+
+				var propStatNodes = this._getElementsByTagName(responseNode, 'd:propstat', resolver);
+				var j = 0;
+
+				for (j = 0; j < propStatNodes.length; j++) {
+					var propStatNode = propStatNodes[j];
+					var statusNode = this._getElementsByTagName(propStatNode, 'd:status', resolver)[0];
+
+					var propStat = {
+						status : statusNode.textContent || statusNode.text,
+						properties : []
+					};
+
+					var propNode = this._getElementsByTagName(propStatNode, 'd:prop', resolver)[0];
+					if (!propNode) {
+						continue;
+					}
+					var k = 0;
+					for (k = 0; k < propNode.childNodes.length; k++) {
+						var prop = propNode.childNodes[k];
+						var value = prop.textContent || prop.text;
+						if (prop.childNodes && prop.childNodes.length > 0 && prop.childNodes[0].nodeType === 1) {
+							value = prop.childNodes;
+						}
+						propStat.properties['{' + prop.namespaceURI + '}' + (prop.localName || prop.baseName)] = value;
+
+					}
+					response.propStat.push(propStat);
+				}
+
+				result.push(response);
+			}
+
+			return result;
+
+		}
+
+
+	});
+
+})(dav);
+
diff --git a/core/js/js.js b/core/js/js.js
index 57c9871233b4efb02140625c79ace399617f9cba..ce552bb8ea2c1a76cbfc5bc8c931a2944fdf2ad2 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1428,7 +1428,6 @@ function initCore() {
 		$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
 
 	}
-
 }
 
 $(document).ready(initCore);
diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js
index 4448b8130215e65349780c576cc1773e1aa018a3..fe93d0ea65701d09c6e5579c72e1faabd00df027 100644
--- a/core/js/oc-dialogs.js
+++ b/core/js/oc-dialogs.js
@@ -759,7 +759,7 @@ var OCdialogs = {
 					filename: entry.name,
 					date: OC.Util.relativeModifiedDate(entry.mtime)
 				});
-				if (entry.isPreviewAvailable) {
+				if (entry.type === 'file') {
 					var urlSpec = {
 						file: dir + '/' + entry.name
 					};
diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js
index cd387d76ce8f81b6991e5a136bf82810cd6f2ef8..f09a7054c9f968d0c95e50c19fdf8b294df99679 100644
--- a/core/js/tests/specHelper.js
+++ b/core/js/tests/specHelper.js
@@ -86,6 +86,7 @@ window.firstDay = 0;
 // setup dummy webroots
 /* jshint camelcase: false */
 window.oc_debug = true;
+// FIXME: oc_webroot is supposed to be only the path!!!
 window.oc_webroot = location.href + '/';
 window.oc_appswebroots = {
 	"files": window.oc_webroot + '/apps/files/'
diff --git a/core/js/tests/specs/files/clientSpec.js b/core/js/tests/specs/files/clientSpec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a3181d842636e669e54af6668406ddba848db53
--- /dev/null
+++ b/core/js/tests/specs/files/clientSpec.js
@@ -0,0 +1,711 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 along with this library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OC.Files.Client tests', function() {
+	var Client = OC.Files.Client;
+	var baseUrl;
+	var client;
+
+	beforeEach(function() {
+		baseUrl = 'https://testhost/owncloud/remote.php/webdav/';
+
+		client = new Client({
+			host: 'testhost',
+			root: '/owncloud/remote.php/webdav',
+			useHTTPS: true
+		});
+	});
+	afterEach(function() {
+		client = null;
+	});
+
+	/**
+	 * Send an status response and check that the given
+	 * promise gets its success handler called with the error
+	 * status code
+	 *
+	 * @param {Promise} promise promise
+	 * @param {int} status status to test
+	 */
+	function respondAndCheckStatus(promise, status) {
+		var successHandler = sinon.stub();
+		var failHandler = sinon.stub();
+		promise.done(successHandler);
+		promise.fail(failHandler);
+
+		fakeServer.requests[0].respond(
+			status,
+			{'Content-Type': 'application/xml'},
+			''
+		);
+
+		promise.then(function() {
+			expect(successHandler.calledOnce).toEqual(true);
+			expect(successHandler.getCall(0).args[0]).toEqual(status);
+
+			expect(failHandler.notCalled).toEqual(true);
+		});
+
+		return promise;
+	}
+
+	/**
+	 * Send an error response and check that the given
+	 * promise gets its fail handler called with the error
+	 * status code
+	 *
+	 * @param {Promise} promise promise object
+	 * @param {int} status error status to test
+	 */
+	function respondAndCheckError(promise, status) {
+		var successHandler = sinon.stub();
+		var failHandler = sinon.stub();
+		promise.done(successHandler);
+		promise.fail(failHandler);
+
+		fakeServer.requests[0].respond(
+			status,
+			{'Content-Type': 'application/xml'},
+			''
+		);
+
+		promise.then(function() {
+			expect(failHandler.calledOnce).toEqual(true);
+			expect(failHandler.calledWith(status)).toEqual(true);
+
+			expect(successHandler.notCalled).toEqual(true);
+
+			fulfill();
+		});
+
+		return promise;
+	}
+
+	/**
+	 * Returns a list of request properties parsed from the given request body.
+	 *
+	 * @param {string} requestBody request XML
+	 *
+	 * @return {Array.<String>} array of request properties in the format
+	 * "{NS:}propname"
+	 */
+	function getRequestedProperties(requestBody) {
+		var doc = (new window.DOMParser()).parseFromString(
+				requestBody,
+				'application/xml'
+		);
+		var propRoots = doc.getElementsByTagNameNS('DAV:', 'prop');
+		var propsList = propRoots.item(0).childNodes;
+		return _.map(propsList, function(propNode) {
+			return '{' + propNode.namespaceURI + '}' + propNode.localName;
+		});
+	}
+
+	function makePropBlock(props) {
+		var s = '<d:prop>\n';
+
+		_.each(props, function(value, key) {
+			s += '<' + key + '>' + value + '</' + key + '>\n';
+		});
+
+		return s + '</d:prop>\n';
+	}
+
+	function makeResponseBlock(href, props, failedProps) {
+		var s = '<d:response>\n';
+		s += '<d:href>' + href + '</d:href>\n';
+		s += '<d:propstat>\n';
+		s += makePropBlock(props);
+		s += '<d:status>HTTP/1.1 200 OK</d:status>';
+		s += '</d:propstat>\n';
+		if (failedProps) {
+			s += '<d:propstat>\n';
+			_.each(failedProps, function(prop) {
+				s += '<' + prop + '/>\n';
+			});
+			s += '<d:status>HTTP/1.1 404 Not Found</d:status>\n';
+			s += '</d:propstat>\n';
+		}
+		return s + '</d:response>\n';
+	}
+
+	describe('file listing', function() {
+
+		var folderContentsXml =
+			'<?xml version="1.0" encoding="utf-8"?>' +
+			'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+			makeResponseBlock(
+			'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
+			{
+				'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+				'd:getetag': '"56cfcabd79abb"',
+				'd:resourcetype': '<d:collection/>',
+				'oc:id': '00000011oc2d13a6a068',
+				'oc:permissions': 'RDNVCK',
+				'oc:size': 120
+			},
+			[
+				'd:getcontenttype',
+				'd:getcontentlength'
+			]
+			) +
+			makeResponseBlock(
+			'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt',
+			{
+				'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
+				'd:getetag': '"559fcabd79a38"',
+				'd:getcontenttype': 'text/plain',
+				'd:getcontentlength': 250,
+				'd:resourcetype': '',
+				'oc:id': '00000051oc2d13a6a068',
+				'oc:permissions': 'RDNVW'
+			},
+			[
+				'oc:size',
+			]
+			) +
+			makeResponseBlock(
+			'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub',
+			{
+				'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT',
+				'd:getetag': '"66cfcabd79abb"',
+				'd:resourcetype': '<d:collection/>',
+				'oc:id': '00000015oc2d13a6a068',
+				'oc:permissions': 'RDNVCK',
+				'oc:size': 100
+			},
+			[
+				'd:getcontenttype',
+				'd:getcontentlength'
+			]
+			) +
+			'</d:multistatus>';
+
+		it('sends PROPFIND with explicit properties to get file list', function() {
+			client.getFolderContents('path/to space/文件夹');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('PROPFIND');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(1);
+
+			var props = getRequestedProperties(fakeServer.requests[0].requestBody);
+			expect(props).toContain('{DAV:}getlastmodified');
+			expect(props).toContain('{DAV:}getcontentlength');
+			expect(props).toContain('{DAV:}getcontenttype');
+			expect(props).toContain('{DAV:}getetag');
+			expect(props).toContain('{DAV:}resourcetype');
+			expect(props).toContain('{http://owncloud.org/ns}fileid');
+			expect(props).toContain('{http://owncloud.org/ns}size');
+			expect(props).toContain('{http://owncloud.org/ns}permissions');
+		});
+		it('sends PROPFIND to base url when empty path given', function() {
+			client.getFolderContents('');
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].url).toEqual(baseUrl);
+		});
+		it('sends PROPFIND to base url when root path given', function() {
+			client.getFolderContents('/');
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].url).toEqual(baseUrl);
+		});
+		it('parses the result list into a FileInfo array', function() {
+			var promise = client.getFolderContents('path/to space/文件夹');
+
+			expect(fakeServer.requests.length).toEqual(1);
+
+			fakeServer.requests[0].respond(
+				207,
+				{'Content-Type': 'application/xml'},
+				folderContentsXml
+			);
+
+			promise.then(function(status, response) {
+				expect(status).toEqual(207);
+				expect(_.isArray(response)).toEqual(true);
+
+				expect(response.length).toEqual(2);
+
+				// file entry
+				var info = response[0];
+				expect(info instanceof OC.Files.FileInfo).toEqual(true);
+				expect(info.id).toEqual(51);
+				expect(info.path).toEqual('/path/to space/文件夹');
+				expect(info.name).toEqual('One.txt');
+				expect(info.permissions).toEqual(31);
+				expect(info.size).toEqual(250);
+				expect(info.mtime.getTime()).toEqual(1436535485000);
+				expect(info.mimetype).toEqual('text/plain');
+				expect(info.etag).toEqual('559fcabd79a38');
+
+				// sub entry
+				info = response[1];
+				expect(info instanceof OC.Files.FileInfo).toEqual(true);
+				expect(info.id).toEqual(15);
+				expect(info.path).toEqual('/path/to space/文件夹');
+				expect(info.name).toEqual('sub');
+				expect(info.permissions).toEqual(31);
+				expect(info.size).toEqual(100);
+				expect(info.mtime.getTime()).toEqual(1436536800000);
+				expect(info.mimetype).toEqual('httpd/unix-directory');
+				expect(info.etag).toEqual('66cfcabd79abb');
+			});
+			return promise.promise();
+		});
+		it('returns parent node in result if specified', function() {
+			var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
+
+			expect(fakeServer.requests.length).toEqual(1);
+
+			fakeServer.requests[0].respond(
+				207,
+				{'Content-Type': 'application/xml'},
+				folderContentsXml
+			);
+
+			promise.then(function(status, response) {
+				expect(status).toEqual(207);
+				expect(_.isArray(response)).toEqual(true);
+
+				expect(response.length).toEqual(3);
+
+				// root entry
+				var info = response[0];
+				expect(info instanceof OC.Files.FileInfo).toEqual(true);
+				expect(info.id).toEqual(11);
+				expect(info.path).toEqual('/path/to space');
+				expect(info.name).toEqual('文件夹');
+				expect(info.permissions).toEqual(31);
+				expect(info.size).toEqual(120);
+				expect(info.mtime.getTime()).toEqual(1436522405000);
+				expect(info.mimetype).toEqual('httpd/unix-directory');
+				expect(info.etag).toEqual('56cfcabd79abb');
+
+				// the two other entries follow
+				expect(response[1].id).toEqual(51);
+				expect(response[2].id).toEqual(15);
+			});
+
+			return promise;
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
+			return respondAndCheckError(promise, 404);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('file info', function() {
+		var responseXml =
+			'<?xml version="1.0" encoding="utf-8"?>' +
+			'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+			makeResponseBlock(
+			'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
+			{
+				'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+				'd:getetag': '"56cfcabd79abb"',
+				'd:resourcetype': '<d:collection/>',
+				'oc:id': '00000011oc2d13a6a068',
+				'oc:permissions': 'RDNVCK',
+				'oc:size': 120
+			},
+			[
+				'd:getcontenttype',
+				'd:getcontentlength'
+			]
+			) +
+			'</d:multistatus>';
+
+		it('sends PROPFIND with zero depth to get single file info', function() {
+			client.getFileInfo('path/to space/文件夹');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('PROPFIND');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(0);
+
+			var props = getRequestedProperties(fakeServer.requests[0].requestBody);
+			expect(props).toContain('{DAV:}getlastmodified');
+			expect(props).toContain('{DAV:}getcontentlength');
+			expect(props).toContain('{DAV:}getcontenttype');
+			expect(props).toContain('{DAV:}getetag');
+			expect(props).toContain('{DAV:}resourcetype');
+			expect(props).toContain('{http://owncloud.org/ns}fileid');
+			expect(props).toContain('{http://owncloud.org/ns}size');
+			expect(props).toContain('{http://owncloud.org/ns}permissions');
+		});
+		it('parses the result into a FileInfo', function() {
+			var promise = client.getFileInfo('path/to space/文件夹');
+
+			expect(fakeServer.requests.length).toEqual(1);
+
+			fakeServer.requests[0].respond(
+				207,
+				{'Content-Type': 'application/xml'},
+				responseXml
+			);
+
+			promise.then(function(status, response) {
+				expect(status).toEqual(207);
+				expect(_.isArray(response)).toEqual(false);
+
+				var info = response;
+				expect(info instanceof OC.Files.FileInfo).toEqual(true);
+				expect(info.id).toEqual(11);
+				expect(info.path).toEqual('/path/to space');
+				expect(info.name).toEqual('文件夹');
+				expect(info.permissions).toEqual(31);
+				expect(info.size).toEqual(120);
+				expect(info.mtime.getTime()).toEqual(1436522405000);
+				expect(info.mimetype).toEqual('httpd/unix-directory');
+				expect(info.etag).toEqual('56cfcabd79abb');
+			});
+
+			return promise;
+		});
+		it('properly parses entry inside root', function() {
+			var responseXml =
+				'<?xml version="1.0" encoding="utf-8"?>' +
+				'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+				makeResponseBlock(
+				'/owncloud/remote.php/webdav/in%20root',
+				{
+					'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+					'd:getetag': '"56cfcabd79abb"',
+					'd:resourcetype': '<d:collection/>',
+					'oc:id': '00000011oc2d13a6a068',
+					'oc:permissions': 'RDNVCK',
+					'oc:size': 120
+				},
+				[
+					'd:getcontenttype',
+					'd:getcontentlength'
+				]
+				) +
+				'</d:multistatus>';
+
+			var promise = client.getFileInfo('in root');
+
+			expect(fakeServer.requests.length).toEqual(1);
+
+			fakeServer.requests[0].respond(
+				207,
+				{'Content-Type': 'application/xml'},
+				responseXml
+			);
+
+			promise.then(function(status, response) {
+				expect(status).toEqual(207);
+				expect(_.isArray(response)).toEqual(false);
+
+				var info = response;
+				expect(info instanceof OC.Files.FileInfo).toEqual(true);
+				expect(info.id).toEqual(11);
+				expect(info.path).toEqual('/');
+				expect(info.name).toEqual('in root');
+				expect(info.permissions).toEqual(31);
+				expect(info.size).toEqual(120);
+				expect(info.mtime.getTime()).toEqual(1436522405000);
+				expect(info.mimetype).toEqual('httpd/unix-directory');
+				expect(info.etag).toEqual('56cfcabd79abb');
+			});
+
+			return promise;
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.getFileInfo('path/to space/文件夹');
+			return respondAndCheckError(promise, 404);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('permissions', function() {
+
+		function getFileInfoWithPermission(webdavPerm, isFile) {
+			var props = {
+				'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
+				'd:getetag': '"559fcabd79a38"',
+				'd:getcontentlength': 250,
+				'oc:id': '00000051oc2d13a6a068',
+				'oc:permissions': webdavPerm,
+			};
+
+			if (isFile) {
+				props['d:getcontenttype'] = 'text/plain';
+			} else {
+				props['d:resourcetype'] = '<d:collection/>';
+			}
+
+			var responseXml =
+				'<?xml version="1.0" encoding="utf-8"?>' +
+				'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+				makeResponseBlock(
+					'/owncloud/remote.php/webdav/file.txt',
+					props
+				) +
+				'</d:multistatus>';
+			var promise = client.getFileInfo('file.txt');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			fakeServer.requests[0].respond(
+				207,
+				{'Content-Type': 'application/xml'},
+				responseXml
+			);
+
+			fakeServer.restore();
+			fakeServer = sinon.fakeServer.create();
+
+			return promise;
+		}
+
+		function testPermission(permission, isFile, expectedPermissions) {
+			var promise = getFileInfoWithPermission(permission, isFile);
+			promise.then(function(result) {
+				expect(result.permissions).toEqual(expectedPermissions);
+			});
+			return promise;
+		}
+
+		function testMountType(permission, isFile, expectedMountType) {
+			var promise = getFileInfoWithPermission(permission, isFile);
+			promise.then(function(result) {
+				expect(result.mountType).toEqual(expectedMountType);
+			});
+			return promise;
+		}
+
+		it('properly parses file permissions', function() {
+			// permission, isFile, expectedPermissions
+			var testCases = [
+				['', true, OC.PERMISSION_READ],
+				['C', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
+				['K', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
+				['W', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+				['D', true, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
+				['R', true, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
+				['CKWDR', true, OC.PERMISSION_ALL]
+			];
+			return Promise.all(
+				_.map(testCases, function(testCase) {
+					return testPermission.apply(testCase);
+				})
+			);
+		});
+		it('properly parses folder permissions', function() {
+			var testCases = [
+				['', false, OC.PERMISSION_READ],
+				['C', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+				['K', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+				['W', false, OC.PERMISSION_READ | OC.PERMISSION_UPDATE],
+				['D', false, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
+				['R', false, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
+				['CKWDR', false, OC.PERMISSION_ALL]
+			];
+
+			return Promise.all(
+				_.map(testCases, function(testCase) {
+					return testPermission.apply(testCase);
+				})
+			);
+		});
+		it('properly parses mount types', function() {
+			var testCases = [
+				['CKWDR', false, null],
+				['M', false, 'external'],
+				['S', false, 'shared'],
+				['SM', false, 'shared']
+			];
+
+			return Promise.all(
+				_.map(testCases, function(testCase) {
+					return testMountType.apply(testCase);
+				})
+			);
+		});
+	});
+
+	describe('get file contents', function() {
+		it('returns file contents', function() {
+			var promise = client.getFileContents('path/to space/文件夹/One.txt');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('GET');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+
+			fakeServer.requests[0].respond(
+				200,
+				{'Content-Type': 'text/plain'},
+				'some contents'
+			);
+
+			promise.then(function(status, response) {
+				expect(status).toEqual(200);
+				expect(response).toEqual('some contents');
+			});
+
+			return promise;
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.getFileContents('path/to space/文件夹/One.txt');
+			return respondAndCheckError(promise, 409);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('put file contents', function() {
+		it('sends PUT with file contents', function() {
+			var promise = client.putFileContents(
+					'path/to space/文件夹/One.txt',
+					'some contents'
+			);
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('PUT');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+			expect(fakeServer.requests[0].requestBody).toEqual('some contents');
+			expect(fakeServer.requests[0].requestHeaders['If-None-Match']).toEqual('*');
+			expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/plain;charset=utf-8');
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('sends PUT with file contents with headers matching options', function() {
+			var promise = client.putFileContents(
+					'path/to space/文件夹/One.txt',
+					'some contents',
+					{
+						overwrite: false,
+						contentType: 'text/markdown'
+					}
+			);
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('PUT');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+			expect(fakeServer.requests[0].requestBody).toEqual('some contents');
+			expect(fakeServer.requests[0].requestHeaders['If-None-Match']).not.toBeDefined();
+			expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/markdown;charset=utf-8');
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.putFileContents(
+					'path/to space/文件夹/One.txt',
+					'some contents'
+			);
+			return respondAndCheckError(promise, 409);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('create directory', function() {
+		it('sends MKCOL with specified path', function() {
+			var promise = client.createDirectory('path/to space/文件夹/new dir');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('MKCOL');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/new%20dir');
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.createDirectory('path/to space/文件夹/new dir');
+			return respondAndCheckError(promise, 404);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('deletion', function() {
+		it('sends DELETE with specified path', function() {
+			var promise = client.remove('path/to space/文件夹');
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('DELETE');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.remove('path/to space/文件夹');
+			return respondAndCheckError(promise, 404);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+
+	describe('move', function() {
+		it('sends MOVE with specified paths with fail on overwrite by default', function() {
+			var promise = client.move(
+					'path/to space/文件夹',
+					'path/to space/anotherdir/文件夹'
+			);
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('MOVE');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Destination)
+				.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Overwrite)
+				.toEqual('F');
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('sends MOVE with silent overwrite mode when specified', function() {
+			var promise = client.move(
+					'path/to space/文件夹',
+					'path/to space/anotherdir/文件夹',
+					{allowOverwrite: true}
+			);
+
+			expect(fakeServer.requests.length).toEqual(1);
+			expect(fakeServer.requests[0].method).toEqual('MOVE');
+			expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Destination)
+				.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
+			expect(fakeServer.requests[0].requestHeaders.Overwrite)
+				.not.toBeDefined();
+
+			return respondAndCheckStatus(promise, 201);
+		});
+		it('rejects promise when an error occurred', function() {
+			var promise = client.move(
+					'path/to space/文件夹',
+					'path/to space/anotherdir/文件夹',
+					{allowOverwrite: true}
+			);
+			return respondAndCheckError(promise, 404);
+		});
+		it('throws exception if arguments are missing', function() {
+			// TODO
+		});
+	});
+});
diff --git a/core/vendor/.gitignore b/core/vendor/.gitignore
index bcbb59b6f2418b0c884c6eeed6159a8f2a1a05e3..09b6a47c72d5b0dc7f92a063763601c51669d830 100644
--- a/core/vendor/.gitignore
+++ b/core/vendor/.gitignore
@@ -122,3 +122,14 @@ bootstrap/js/*
 
 # backbone
 backbone/backbone-min*
+
+# davclient.js
+davclient.js/**
+!davclient.js/lib/*
+!davclient.js/LICENSE
+
+# es6-promise
+es6-promise/**
+!es6-promise/LICENSE
+!es6-promise/dist/es6-promise.js
+
diff --git a/core/vendor/davclient.js/LICENSE b/core/vendor/davclient.js/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..fd7293e8f324c027ec505c787655271afb36ec49
--- /dev/null
+++ b/core/vendor/davclient.js/LICENSE
@@ -0,0 +1,27 @@
+Copyright (C) 2013-2014 fruux GmbH (https://fruux.com/)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name Sabre nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+    POSSIBILITY OF SUCH DAMAGE.
diff --git a/core/vendor/davclient.js/lib/client.js b/core/vendor/davclient.js/lib/client.js
new file mode 100644
index 0000000000000000000000000000000000000000..121b5dcab5f7e3cec6b7a53b8a3427e056eac7af
--- /dev/null
+++ b/core/vendor/davclient.js/lib/client.js
@@ -0,0 +1,296 @@
+if (typeof dav == 'undefined') { dav = {}; };
+
+dav.Client = function(options) {
+    var i;
+    for(i in options) {
+        this[i] = options[i];
+    }
+
+};
+
+dav.Client.prototype = {
+
+    baseUrl : null,
+
+    userName : null,
+
+    password : null,
+
+
+    xmlNamespaces : {
+        'DAV:' : 'd'
+    },
+
+    /**
+     * Generates a propFind request.
+     *
+     * @param {string} url Url to do the propfind request on
+     * @param {Array} properties List of properties to retrieve.
+     * @return {Promise}
+     */
+    propFind : function(url, properties, depth) {
+
+        if(typeof depth == "undefined") {
+            depth = 0;
+        }
+
+        var headers = {
+            Depth          : depth,
+            'Content-Type' : 'application/xml; charset=utf-8'
+        };
+
+        var body =
+            '<?xml version="1.0"?>\n' +
+            '<d:propfind ';
+        var namespace;
+        for (namespace in this.xmlNamespaces) {
+            body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
+        }
+        body += '>\n' +
+            '  <d:prop>\n';
+
+        for(var ii in properties) {
+
+            var property = this.parseClarkNotation(properties[ii]);
+            if (this.xmlNamespaces[property.namespace]) {
+                body+='    <' + this.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
+            } else {
+                body+='    <x:' + property.name + ' xmlns:x="' + property.namespace + '" />\n';
+            }
+
+        }
+        body+='  </d:prop>\n';
+        body+='</d:propfind>';
+
+        return this.request('PROPFIND', url, headers, body).then(
+            function(result) {
+
+                var resultBody = this.parseMultiStatus(result.body);
+                if (depth===0) {
+                    return {
+                        status: result.status,
+                        body: resultBody[0],
+                        xhr: result.xhr
+                    };
+                } else {
+                    return {
+                        status: result.status,
+                        body: resultBody,
+                        xhr: result.xhr
+                    };
+                }
+
+            }.bind(this)
+        );
+
+    },
+
+    /**
+     * Performs a HTTP request, and returns a Promise
+     *
+     * @param {string} method HTTP method
+     * @param {string} url Relative or absolute url
+     * @param {Object} headers HTTP headers as an object.
+     * @param {string} body HTTP request body.
+     * @return {Promise}
+     */
+    request : function(method, url, headers, body) {
+
+        var xhr = this.xhrProvider();
+
+        if (this.userName) {
+            headers['Authorization'] = 'Basic ' + btoa(this.userName + ':' + this.password);
+            // xhr.open(method, this.resolveUrl(url), true, this.userName, this.password);
+        }
+        xhr.open(method, this.resolveUrl(url), true);
+        var ii;
+        for(ii in headers) {
+            xhr.setRequestHeader(ii, headers[ii]);
+        }
+        xhr.send(body);
+
+        return new Promise(function(fulfill, reject) {
+
+            xhr.onreadystatechange = function() {
+
+                if (xhr.readyState !== 4) {
+                    return;
+                }
+
+                fulfill({
+                    body: xhr.response,
+                    status: xhr.status,
+                    xhr: xhr
+                });
+
+            };
+
+            xhr.ontimeout = function() {
+
+                reject(new Error('Timeout exceeded'));
+
+            };
+
+        });
+
+    },
+
+    /**
+     * Returns an XMLHttpRequest object.
+     *
+     * This is in its own method, so it can be easily overridden.
+     *
+     * @return {XMLHttpRequest}
+     */
+    xhrProvider : function() {
+
+        return new XMLHttpRequest();
+
+    },
+
+
+    /**
+     * Parses a multi-status response body.
+     *
+     * @param {string} xmlBody
+     * @param {Array}
+     */
+    parseMultiStatus : function(xmlBody) {
+
+        var parser = new DOMParser();
+        var doc = parser.parseFromString(xmlBody, "application/xml");
+
+        var resolver = function(foo) {
+            var ii;
+            for(ii in this.xmlNamespaces) {
+                if (this.xmlNamespaces[ii] === foo) {
+                    return ii;
+                }
+            }
+        }.bind(this);
+
+        var responseIterator = doc.evaluate('/d:multistatus/d:response', doc, resolver, XPathResult.ANY_TYPE, null);
+
+        var result = [];
+        var responseNode = responseIterator.iterateNext();
+
+        while(responseNode) {
+
+            var response = {
+                href : null,
+                propStat : []
+            };
+
+            response.href = doc.evaluate('string(d:href)', responseNode, resolver, XPathResult.ANY_TYPE, null).stringValue;
+
+            var propStatIterator = doc.evaluate('d:propstat', responseNode, resolver, XPathResult.ANY_TYPE, null);
+            var propStatNode = propStatIterator.iterateNext();
+
+            while(propStatNode) {
+
+                var propStat = {
+                    status : doc.evaluate('string(d:status)', propStatNode, resolver, XPathResult.ANY_TYPE, null).stringValue,
+                    properties : [],
+                };
+
+                var propIterator = doc.evaluate('d:prop/*', propStatNode, resolver, XPathResult.ANY_TYPE, null);
+
+                var propNode = propIterator.iterateNext();
+                while(propNode) {
+                    var content = propNode.textContent;
+                    if (propNode.childNodes && propNode.childNodes.length > 0 && propNode.childNodes[0].nodeType === 1) {
+                        content = propNode.childNodes;
+                    }
+
+                    propStat.properties['{' + propNode.namespaceURI + '}' + propNode.localName] = content;
+                    propNode = propIterator.iterateNext();
+
+                }
+                response.propStat.push(propStat);
+                propStatNode = propStatIterator.iterateNext();
+
+
+            }
+
+            result.push(response);
+            responseNode = responseIterator.iterateNext();
+
+        }
+
+        return result;
+
+    },
+
+    /**
+     * Takes a relative url, and maps it to an absolute url, using the baseUrl
+     *
+     * @param {string} url
+     * @return {string}
+     */
+    resolveUrl : function(url) {
+
+        // Note: this is rudamentary.. not sure yet if it handles every case.
+        if (/^https?:\/\//i.test(url)) {
+            // absolute
+            return url;
+        }
+
+        var baseParts = this.parseUrl(this.baseUrl);
+        if (url.charAt('/')) {
+            // Url starts with a slash
+            return baseParts.root + url;
+        }
+
+        // Url does not start with a slash, we need grab the base url right up until the last slash.
+        var newUrl = baseParts.root + '/';
+        if (baseParts.path.lastIndexOf('/')!==-1) {
+            newUrl = newUrl = baseParts.path.subString(0, baseParts.path.lastIndexOf('/')) + '/';
+        }
+        newUrl+=url;
+        return url;
+
+    },
+
+    /**
+     * Parses a url and returns its individual components.
+     *
+     * @param {String} url
+     * @return {Object}
+     */
+    parseUrl : function(url) {
+
+         var parts = url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/);
+         var result = {
+             url : parts[0],
+             scheme : parts[1],
+             host : parts[3],
+             port : parts[4],
+             path : parts[5],
+             query : parts[6],
+             fragment : parts[7],
+         };
+         result.root =
+            result.scheme + '://' +
+            result.host +
+            (result.port ? ':' + result.port : '');
+
+         return result;
+
+    },
+
+    parseClarkNotation : function(propertyName) {
+
+        var result = propertyName.match(/^{([^}]+)}(.*)$/);
+        if (!result) {
+            return;
+        }
+
+        return {
+            name : result[2],
+            namespace : result[1]
+        };
+
+    }
+
+};
+
diff --git a/core/vendor/es6-promise/.bower.json b/core/vendor/es6-promise/.bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..f8c28b04e5327437376efd7b14ccd594e22de978
--- /dev/null
+++ b/core/vendor/es6-promise/.bower.json
@@ -0,0 +1,40 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.3.0",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ],
+  "homepage": "https://github.com/jakearchibald/es6-promise",
+  "_release": "2.3.0",
+  "_resolution": {
+    "type": "version",
+    "tag": "2.3.0",
+    "commit": "fcbab11a1a981eb2290bfff89017cb764335a2a5"
+  },
+  "_source": "https://github.com/jakearchibald/es6-promise.git",
+  "_target": "~2.3.0",
+  "_originalSource": "https://github.com/jakearchibald/es6-promise.git",
+  "_direct": true
+}
\ No newline at end of file
diff --git a/core/vendor/es6-promise/.npmignore b/core/vendor/es6-promise/.npmignore
new file mode 100644
index 0000000000000000000000000000000000000000..7a758111e9e21b3e2d2544533819d1f9e946239d
--- /dev/null
+++ b/core/vendor/es6-promise/.npmignore
@@ -0,0 +1,11 @@
+/node_modules/
+/tmp
+/tasks
+/test
+/vendor
+/.jshintrc
+/.npmignore
+/.travis.yml
+/Gruntfile.js
+/component.json
+/index.html
diff --git a/core/vendor/es6-promise/.release.json b/core/vendor/es6-promise/.release.json
new file mode 100644
index 0000000000000000000000000000000000000000..dee8cbc5d923c6869a968330d64c00f084a6e56a
--- /dev/null
+++ b/core/vendor/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/core/vendor/es6-promise/.spmignore b/core/vendor/es6-promise/.spmignore
new file mode 100644
index 0000000000000000000000000000000000000000..7a758111e9e21b3e2d2544533819d1f9e946239d
--- /dev/null
+++ b/core/vendor/es6-promise/.spmignore
@@ -0,0 +1,11 @@
+/node_modules/
+/tmp
+/tasks
+/test
+/vendor
+/.jshintrc
+/.npmignore
+/.travis.yml
+/Gruntfile.js
+/component.json
+/index.html
diff --git a/core/vendor/es6-promise/LICENSE b/core/vendor/es6-promise/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..954ec5992df7508499c41d0e9d7ed74ef5d5032c
--- /dev/null
+++ b/core/vendor/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/core/vendor/es6-promise/dist/es6-promise.js b/core/vendor/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000000000000000000000000000000000000..aff0482ee5e68f7b16c3a0ff0a81a5dbe252bbf8
--- /dev/null
+++ b/core/vendor/es6-promise/dist/es6-promise.js
@@ -0,0 +1,972 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.3.0
+ */
+
+(function() {
+    "use strict";
+    function lib$es6$promise$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function lib$es6$promise$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function lib$es6$promise$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var lib$es6$promise$utils$$_isArray;
+    if (!Array.isArray) {
+      lib$es6$promise$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      lib$es6$promise$utils$$_isArray = Array.isArray;
+    }
+
+    var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
+    var lib$es6$promise$asap$$len = 0;
+    var lib$es6$promise$asap$$toString = {}.toString;
+    var lib$es6$promise$asap$$vertxNext;
+    var lib$es6$promise$asap$$customSchedulerFn;
+
+    var lib$es6$promise$asap$$asap = function asap(callback, arg) {
+      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
+      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
+      lib$es6$promise$asap$$len += 2;
+      if (lib$es6$promise$asap$$len === 2) {
+        // If len is 2, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        if (lib$es6$promise$asap$$customSchedulerFn) {
+          lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush);
+        } else {
+          lib$es6$promise$asap$$scheduleFlush();
+        }
+      }
+    }
+
+    function lib$es6$promise$asap$$setScheduler(scheduleFn) {
+      lib$es6$promise$asap$$customSchedulerFn = scheduleFn;
+    }
+
+    function lib$es6$promise$asap$$setAsap(asapFn) {
+      lib$es6$promise$asap$$asap = asapFn;
+    }
+
+    var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
+    var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
+    var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
+    var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
+
+    // test for web worker but not in IE10
+    var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function lib$es6$promise$asap$$useNextTick() {
+      var nextTick = process.nextTick;
+      // node version 0.10.x displays a deprecation warning when nextTick is used recursively
+      // setImmediate should be used instead instead
+      var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
+      if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
+        nextTick = setImmediate;
+      }
+      return function() {
+        nextTick(lib$es6$promise$asap$$flush);
+      };
+    }
+
+    // vertx
+    function lib$es6$promise$asap$$useVertxTimer() {
+      return function() {
+        lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
+      };
+    }
+
+    function lib$es6$promise$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function lib$es6$promise$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = lib$es6$promise$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function lib$es6$promise$asap$$useSetTimeout() {
+      return function() {
+        setTimeout(lib$es6$promise$asap$$flush, 1);
+      };
+    }
+
+    var lib$es6$promise$asap$$queue = new Array(1000);
+    function lib$es6$promise$asap$$flush() {
+      for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
+        var callback = lib$es6$promise$asap$$queue[i];
+        var arg = lib$es6$promise$asap$$queue[i+1];
+
+        callback(arg);
+
+        lib$es6$promise$asap$$queue[i] = undefined;
+        lib$es6$promise$asap$$queue[i+1] = undefined;
+      }
+
+      lib$es6$promise$asap$$len = 0;
+    }
+
+    function lib$es6$promise$asap$$attemptVertex() {
+      try {
+        var r = require;
+        var vertx = r('vertx');
+        lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
+        return lib$es6$promise$asap$$useVertxTimer();
+      } catch(e) {
+        return lib$es6$promise$asap$$useSetTimeout();
+      }
+    }
+
+    var lib$es6$promise$asap$$scheduleFlush;
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (lib$es6$promise$asap$$isNode) {
+      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
+    } else if (lib$es6$promise$asap$$BrowserMutationObserver) {
+      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
+    } else if (lib$es6$promise$asap$$isWorker) {
+      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
+    } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') {
+      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertex();
+    } else {
+      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
+    }
+
+    function lib$es6$promise$$internal$$noop() {}
+
+    var lib$es6$promise$$internal$$PENDING   = void 0;
+    var lib$es6$promise$$internal$$FULFILLED = 1;
+    var lib$es6$promise$$internal$$REJECTED  = 2;
+
+    var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();
+
+    function lib$es6$promise$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function lib$es6$promise$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.');
+    }
+
+    function lib$es6$promise$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
+        return lib$es6$promise$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
+       lib$es6$promise$asap$$asap(function(promise) {
+        var sealed = false;
+        var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            lib$es6$promise$$internal$$resolve(promise, value);
+          } else {
+            lib$es6$promise$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          lib$es6$promise$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          lib$es6$promise$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
+        lib$es6$promise$$internal$$fulfill(promise, thenable._result);
+      } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
+        lib$es6$promise$$internal$$reject(promise, thenable._result);
+      } else {
+        lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
+          lib$es6$promise$$internal$$resolve(promise, value);
+        }, function(reason) {
+          lib$es6$promise$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = lib$es6$promise$$internal$$getThen(maybeThenable);
+
+        if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
+          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
+        } else if (lib$es6$promise$utils$$isFunction(then)) {
+          lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function lib$es6$promise$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFullfillment());
+      } else if (lib$es6$promise$utils$$objectOrFunction(value)) {
+        lib$es6$promise$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        lib$es6$promise$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function lib$es6$promise$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      lib$es6$promise$$internal$$publish(promise);
+    }
+
+    function lib$es6$promise$$internal$$fulfill(promise, value) {
+      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = lib$es6$promise$$internal$$FULFILLED;
+
+      if (promise._subscribers.length !== 0) {
+        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise);
+      }
+    }
+
+    function lib$es6$promise$$internal$$reject(promise, reason) {
+      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
+      promise._state = lib$es6$promise$$internal$$REJECTED;
+      promise._result = reason;
+
+      lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise);
+    }
+
+    function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + lib$es6$promise$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent);
+      }
+    }
+
+    function lib$es6$promise$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function lib$es6$promise$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();
+
+    function lib$es6$promise$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
+        return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = lib$es6$promise$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = lib$es6$promise$$internal$$tryCatch(callback, detail);
+
+        if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== lib$es6$promise$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        lib$es6$promise$$internal$$resolve(promise, value);
+      } else if (failed) {
+        lib$es6$promise$$internal$$reject(promise, error);
+      } else if (settled === lib$es6$promise$$internal$$FULFILLED) {
+        lib$es6$promise$$internal$$fulfill(promise, value);
+      } else if (settled === lib$es6$promise$$internal$$REJECTED) {
+        lib$es6$promise$$internal$$reject(promise, value);
+      }
+    }
+
+    function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          lib$es6$promise$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          lib$es6$promise$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        lib$es6$promise$$internal$$reject(promise, e);
+      }
+    }
+
+    function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
+      var enumerator = this;
+
+      enumerator._instanceConstructor = Constructor;
+      enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop);
+
+      if (enumerator._validateInput(input)) {
+        enumerator._input     = input;
+        enumerator.length     = input.length;
+        enumerator._remaining = input.length;
+
+        enumerator._init();
+
+        if (enumerator.length === 0) {
+          lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
+        } else {
+          enumerator.length = enumerator.length || 0;
+          enumerator._enumerate();
+          if (enumerator._remaining === 0) {
+            lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
+          }
+        }
+      } else {
+        lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError());
+      }
+    }
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return lib$es6$promise$utils$$isArray(input);
+    };
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
+      var enumerator = this;
+
+      var length  = enumerator.length;
+      var promise = enumerator.promise;
+      var input   = enumerator._input;
+
+      for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
+        enumerator._eachEntry(input[i], i);
+      }
+    };
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var enumerator = this;
+      var c = enumerator._instanceConstructor;
+
+      if (lib$es6$promise$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) {
+          entry._onerror = null;
+          enumerator._settledAt(entry._state, i, entry._result);
+        } else {
+          enumerator._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        enumerator._remaining--;
+        enumerator._result[i] = entry;
+      }
+    };
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var enumerator = this;
+      var promise = enumerator.promise;
+
+      if (promise._state === lib$es6$promise$$internal$$PENDING) {
+        enumerator._remaining--;
+
+        if (state === lib$es6$promise$$internal$$REJECTED) {
+          lib$es6$promise$$internal$$reject(promise, value);
+        } else {
+          enumerator._result[i] = value;
+        }
+      }
+
+      if (enumerator._remaining === 0) {
+        lib$es6$promise$$internal$$fulfill(promise, enumerator._result);
+      }
+    };
+
+    lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
+      });
+    };
+    function lib$es6$promise$promise$all$$all(entries) {
+      return new lib$es6$promise$enumerator$$default(this, entries).promise;
+    }
+    var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
+    function lib$es6$promise$promise$race$$race(entries) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor(lib$es6$promise$$internal$$noop);
+
+      if (!lib$es6$promise$utils$$isArray(entries)) {
+        lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        lib$es6$promise$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        lib$es6$promise$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
+        lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    }
+    var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
+    function lib$es6$promise$promise$resolve$$resolve(object) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor(lib$es6$promise$$internal$$noop);
+      lib$es6$promise$$internal$$resolve(promise, object);
+      return promise;
+    }
+    var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;
+    function lib$es6$promise$promise$reject$$reject(reason) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor(lib$es6$promise$$internal$$noop);
+      lib$es6$promise$$internal$$reject(promise, reason);
+      return promise;
+    }
+    var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;
+
+    var lib$es6$promise$promise$$counter = 0;
+
+    function lib$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function lib$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise's eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function lib$es6$promise$promise$$Promise(resolver) {
+      this._id = lib$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if (lib$es6$promise$$internal$$noop !== resolver) {
+        if (!lib$es6$promise$utils$$isFunction(resolver)) {
+          lib$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof lib$es6$promise$promise$$Promise)) {
+          lib$es6$promise$promise$$needsNew();
+        }
+
+        lib$es6$promise$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
+    lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
+    lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
+    lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
+    lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler;
+    lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap;
+    lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap;
+
+    lib$es6$promise$promise$$Promise.prototype = {
+      constructor: lib$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor(lib$es6$promise$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          lib$es6$promise$asap$$asap(function(){
+            lib$es6$promise$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+    function lib$es6$promise$polyfill$$polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+          local = global;
+      } else if (typeof self !== 'undefined') {
+          local = self;
+      } else {
+          try {
+              local = Function('return this')();
+          } catch (e) {
+              throw new Error('polyfill failed because global object is unavailable in this environment');
+          }
+      }
+
+      var P = local.Promise;
+
+      if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) {
+        return;
+      }
+
+      local.Promise = lib$es6$promise$promise$$default;
+    }
+    var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;
+
+    var lib$es6$promise$umd$$ES6Promise = {
+      'Promise': lib$es6$promise$promise$$default,
+      'polyfill': lib$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return lib$es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = lib$es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
+    }
+
+    lib$es6$promise$polyfill$$default();
+}).call(this);
+
diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php
index 96620838dfb0bd4faef913ee152f851e23b560fd..ea42c9a89672c4f9fd97d6fdcbe06b4ddd8b4570 100644
--- a/lib/private/appframework/http/request.php
+++ b/lib/private/appframework/http/request.php
@@ -42,7 +42,7 @@ use OCP\Security\ISecureRandom;
  */
 class Request implements \ArrayAccess, \Countable, IRequest {
 
-	const USER_AGENT_IE = '/MSIE/';
+	const USER_AGENT_IE = '/(MSIE)|(Trident)/';
 	const USER_AGENT_IE_8 = '/MSIE 8.0/';
 	// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
 	const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
@@ -674,6 +674,9 @@ class Request implements \ArrayAccess, \Countable, IRequest {
 	 * @return bool true if at least one of the given agent matches, false otherwise
 	 */
 	public function isUserAgent(array $agent) {
+		if (!isset($this->server['HTTP_USER_AGENT'])) {
+			return false;
+		}
 		foreach ($agent as $regex) {
 			if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
 				return true;
diff --git a/lib/private/template.php b/lib/private/template.php
index 97666f0b8dc7a394dd2cc9548506f18e75788ea8..1476a964ef3679c67dc3a045766ccb90ddd0697c 100644
--- a/lib/private/template.php
+++ b/lib/private/template.php
@@ -143,7 +143,9 @@ class OC_Template extends \OC\Template\Base {
 			OC_Util::addStyle("jquery.ocdialog");
 			OC_Util::addScript("compatibility", null, true);
 			OC_Util::addScript("placeholders", null, true);
-			
+			OC_Util::addScript('files/fileinfo');
+			OC_Util::addScript('files/client');
+
 			// Add the stuff we need always
 			// following logic will import all vendor libraries that are
 			// specified in core/js/core.json
@@ -158,7 +160,12 @@ class OC_Template extends \OC\Template\Base {
 			} else {
 				throw new \Exception('Cannot read core/js/core.json');
 			}
-			
+
+			if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
+				// shim for the davclient.js library
+				\OCP\Util::addScript('files/iedavclient');
+			}
+
 			self::$initTemplateEngineFirstRun = false;
 		}
 	
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 64a94ef230bcd83014dd85e841dd0d8e107129e8..dc621ae0f743fb3cc7ab85f74287e6ade2a403f8 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -164,15 +164,15 @@ module.exports = function(config) {
 	// need to test the core app as well ?
 	if (testCore) {
 		// core tests
-		files.push(corePath + 'tests/specs/*.js');
+		files.push(corePath + 'tests/specs/**/*.js');
 	}
 
 	function addApp(app) {
 		// if only a string was specified, expand to structure
 		if (typeof(app) === 'string') {
 			app = {
-				srcFiles: 'apps/' + app + '/js/*.js',
-				testFiles: 'apps/' + app + '/tests/js/*.js'
+				srcFiles: 'apps/' + app + '/js/**/*.js',
+				testFiles: 'apps/' + app + '/tests/js/**/*.js'
 			};
 		}
 
diff --git a/tests/lib/appframework/http/RequestTest.php b/tests/lib/appframework/http/RequestTest.php
index f628a30f1da93cfd107b2814b14f7deecf717441..ab5fe15444130b2e602c2465e38ff9cd8efb92a0 100644
--- a/tests/lib/appframework/http/RequestTest.php
+++ b/tests/lib/appframework/http/RequestTest.php
@@ -693,19 +693,36 @@ class RequestTest extends \Test\TestCase {
 	 */
 	public function testUserAgent($testAgent, $userAgent, $matches) {
 		$request = new Request(
-			[
-				'server' => [
-					'HTTP_USER_AGENT' => $testAgent,
-				]
-			],
-			$this->secureRandom,
-			$this->config,
-			$this->stream
+				[
+						'server' => [
+								'HTTP_USER_AGENT' => $testAgent,
+						]
+				],
+				$this->secureRandom,
+				$this->config,
+				$this->stream
 		);
 
 		$this->assertSame($matches, $request->isUserAgent($userAgent));
 	}
 
+	/**
+	 * @dataProvider userAgentProvider
+	 * @param string $testAgent
+	 * @param array $userAgent
+	 * @param bool $matches
+	 */
+	public function testUndefinedUserAgent($testAgent, $userAgent, $matches) {
+		$request = new Request(
+				[],
+				$this->secureRandom,
+				$this->config,
+				$this->stream
+		);
+
+		$this->assertFalse($request->isUserAgent($userAgent));
+	}
+
 	/**
 	 * @return array
 	 */