diff --git a/apps/dav/.gitignore b/apps/dav/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..885b6b3e6de4722840f1a11d8e0789cec64e5d6c
--- /dev/null
+++ b/apps/dav/.gitignore
@@ -0,0 +1 @@
+tests/travis/CalDAVTester
diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f3fd5079949403d87ec5d283b4196c70e20a1b15
--- /dev/null
+++ b/apps/dav/appinfo/database.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<database>
+
+	<!--
+CREATE TABLE addressbooks (
+    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    principaluri VARBINARY(255),
+    displayname VARCHAR(255),
+    uri VARBINARY(200),
+    description TEXT,
+    synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
+    UNIQUE(principaluri(100), uri(100))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+	-->
+	<table>
+
+		<name>*dbprefix*addressbooks</name>
+
+		<declaration>
+
+		<field>
+			<name>id</name>
+			<type>integer</type>
+			<default>0</default>
+			<notnull>true</notnull>
+			<autoincrement>1</autoincrement>
+			<unsigned>true</unsigned>
+			<length>11</length>
+		</field>
+
+			<field>
+				<name>principaluri</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>displayname</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>uri</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>description</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>synctoken</name>
+				<type>integer</type>
+				<default>1</default>
+				<notnull>true</notnull>
+				<unsigned>true</unsigned>
+			</field>
+			<index>
+				<name>addressbook_index</name>
+				<unique>true</unique>
+				<field>
+					<name>principaluri</name>
+				</field>
+				<field>
+					<name>uri</name>
+				</field>
+			</index>
+		</declaration>
+	</table>
+
+	<!--
+
+CREATE TABLE cards (
+    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    addressbookid INT(11) UNSIGNED NOT NULL,
+    carddata MEDIUMBLOB,
+    uri VARBINARY(200),
+    lastmodified INT(11) UNSIGNED,
+    etag VARBINARY(32),
+    size INT(11) UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+	-->
+	<table>
+		<name>*dbprefix*cards</name>
+		<declaration>
+			<field>
+				<name>id</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<autoincrement>1</autoincrement>
+				<unsigned>true</unsigned>
+				<length>11</length>
+			</field>
+			<field>
+				<name>addressbookid</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+			</field>
+			<field>
+				<name>carddata</name>
+				<type>blob</type>
+			</field>
+			<field>
+				<name>uri</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>lastmodified</name>
+				<type>integer</type>
+				<unsigned>true</unsigned>
+				<length>11</length>
+			</field>
+			<field>
+				<name>etag</name>
+				<type>text</type>
+				<length>32</length>
+			</field>
+			<field>
+				<name>size</name>
+				<type>integer</type>
+				<notnull>true</notnull>
+				<unsigned>true</unsigned>
+				<length>11</length>
+			</field>
+		</declaration>
+	</table>
+
+	<!--
+CREATE TABLE addressbookchanges (
+    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    uri VARBINARY(200) NOT NULL,
+    synctoken INT(11) UNSIGNED NOT NULL,
+    addressbookid INT(11) UNSIGNED NOT NULL,
+    operation TINYINT(1) NOT NULL,
+    INDEX addressbookid_synctoken (addressbookid, synctoken)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+	-->
+
+	<table>
+		<name>*dbprefix*addressbookchanges</name>
+		<declaration>
+			<field>
+				<name>id</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<autoincrement>1</autoincrement>
+				<unsigned>true</unsigned>
+				<length>11</length>
+			</field>
+			<field>
+				<name>uri</name>
+				<type>text</type>
+			</field>
+			<field>
+				<name>synctoken</name>
+				<type>integer</type>
+				<default>1</default>
+				<notnull>true</notnull>
+				<unsigned>true</unsigned>
+			</field>
+			<field>
+				<name>addressbookid</name>
+				<type>integer</type>
+				<notnull>true</notnull>
+			</field>
+			<field>
+				<name>operation</name>
+				<type>integer</type>
+				<notnull>true</notnull>
+				<length>1</length>
+			</field>
+
+			<index>
+				<name>addressbookid_synctoken</name>
+				<field>
+					<name>addressbookid</name>
+				</field>
+				<field>
+					<name>synctoken</name>
+				</field>
+			</index>
+
+		</declaration>
+	</table>
+
+</database>
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index 8f378f5e18d727f73adde84d4e3b0df694d493a5..11025115691e2e391e66bc175e6b20869e439f6b 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
 	<description>ownCloud WebDAV endpoint</description>
 	<licence>AGPL</licence>
 	<author>owncloud.org</author>
-	<version>0.1.1</version>
+	<version>0.1.2</version>
 	<requiremin>9.0</requiremin>
 	<shipped>true</shipped>
 	<standalone/>
diff --git a/apps/dav/appinfo/register_command.php b/apps/dav/appinfo/register_command.php
new file mode 100644
index 0000000000000000000000000000000000000000..c996dd44063a694e028ee94a2f112bf62957a0e1
--- /dev/null
+++ b/apps/dav/appinfo/register_command.php
@@ -0,0 +1,8 @@
+<?php
+
+use OCA\DAV\Command\CreateAddressBook;
+
+$dbConnection = \OC::$server->getDatabaseConnection();
+$userManager = OC::$server->getUserManager();
+/** @var Symfony\Component\Console\Application $application */
+$application->add(new CreateAddressBook($userManager, $dbConnection));
diff --git a/apps/dav/command/createaddressbook.php b/apps/dav/command/createaddressbook.php
new file mode 100644
index 0000000000000000000000000000000000000000..286871b39e2762d53abbacf6e89a8c59d9400531
--- /dev/null
+++ b/apps/dav/command/createaddressbook.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace OCA\DAV\Command;
+
+use OCA\DAV\CardDAV\CardDavBackend;
+use OCP\IDBConnection;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class CreateAddressBook extends Command {
+
+	/** @var IUserManager */
+	protected $userManager;
+
+	/** @var \OCP\IDBConnection */
+	protected $dbConnection;
+
+	/**
+	 * @param IUserManager $userManager
+	 * @param IDBConnection $dbConnection
+	 */
+	function __construct(IUserManager $userManager, IDBConnection $dbConnection) {
+		parent::__construct();
+		$this->userManager = $userManager;
+		$this->dbConnection = $dbConnection;
+	}
+
+	protected function configure() {
+		$this
+			->setName('dav:create-addressbook')
+			->setDescription('Create a dav addressbook')
+			->addArgument('user',
+				InputArgument::REQUIRED,
+				'User for whom the addressbook will be created')
+			->addArgument('name',
+				InputArgument::REQUIRED,
+				'Name of the addressbook');
+	}
+
+	protected function execute(InputInterface $input, OutputInterface $output) {
+		$user = $input->getArgument('user');
+		if (!$this->userManager->userExists($user)) {
+			throw new \InvalidArgumentException("User <$user> in unknown.");
+		}
+		$name = $input->getArgument('name');
+		$carddav = new CardDavBackend($this->dbConnection);
+		$carddav->createAddressBook("principals/$user", $name, []);
+	}
+}
diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b16262a680e3a776cd1eb852893d31a1c4a5f02
--- /dev/null
+++ b/apps/dav/lib/carddav/carddavbackend.php
@@ -0,0 +1,558 @@
+<?php
+
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CardDAV;
+
+use Sabre\CardDAV\Backend\BackendInterface;
+use Sabre\CardDAV\Backend\SyncSupport;
+use Sabre\CardDAV\Plugin;
+use Sabre\DAV\Exception\BadRequest;
+
+class CardDavBackend implements BackendInterface, SyncSupport {
+
+	public function __construct(\OCP\IDBConnection $db) {
+		$this->db = $db;
+	}
+
+	/**
+	 * Returns the list of addressbooks for a specific user.
+	 *
+	 * Every addressbook should have the following properties:
+	 *   id - an arbitrary unique id
+	 *   uri - the 'basename' part of the url
+	 *   principaluri - Same as the passed parameter
+	 *
+	 * Any additional clark-notation property may be passed besides this. Some
+	 * common ones are :
+	 *   {DAV:}displayname
+	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
+	 *   {http://calendarserver.org/ns/}getctag
+	 *
+	 * @param string $principalUri
+	 * @return array
+	 */
+	function getAddressBooksForUser($principalUri) {
+		$query = $this->db->getQueryBuilder();
+		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
+			->from('addressbooks')
+			->where($query->expr()->eq('principaluri', $query->createParameter('principaluri')))
+			->setParameter('principaluri', $principalUri);
+
+		$addressBooks = [];
+
+		$result = $query->execute();
+		while($row = $result->fetch()) {
+			$addressBooks[] = [
+				'id'  => $row['id'],
+				'uri' => $row['uri'],
+				'principaluri' => $row['principaluri'],
+				'{DAV:}displayname' => $row['displayname'],
+				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+			];
+		}
+		$result->closeCursor();
+
+		return $addressBooks;
+	}
+
+	/**
+	 * Updates properties for an address book.
+	 *
+	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+	 * To do the actual updates, you must tell this object which properties
+	 * you're going to process with the handle() method.
+	 *
+	 * Calling the handle method is like telling the PropPatch object "I
+	 * promise I can handle updating this property".
+	 *
+	 * Read the PropPatch documenation for more info and examples.
+	 *
+	 * @param string $addressBookId
+	 * @param \Sabre\DAV\PropPatch $propPatch
+	 * @return void
+	 */
+	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
+		$supportedProperties = [
+			'{DAV:}displayname',
+			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
+		];
+
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
+
+			$updates = [];
+			foreach($mutations as $property=>$newValue) {
+
+				switch($property) {
+					case '{DAV:}displayname' :
+						$updates['displayname'] = $newValue;
+						break;
+					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
+						$updates['description'] = $newValue;
+						break;
+				}
+			}
+			$query = $this->db->getQueryBuilder();
+			$query->update('addressbooks');
+
+			foreach($updates as $key=>$value) {
+				$query->set($key, $query->createNamedParameter($value));
+			}
+			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
+			->execute();
+
+			$this->addChange($addressBookId, "", 2);
+
+			return true;
+
+		});
+	}
+
+	/**
+	 * Creates a new address book
+	 *
+	 * @param string $principalUri
+	 * @param string $url Just the 'basename' of the url.
+	 * @param array $properties
+	 * @return void
+	 */
+	function createAddressBook($principalUri, $url, array $properties) {
+		$values = [
+			'displayname' => null,
+			'description' => null,
+			'principaluri' => $principalUri,
+			'uri' => $url,
+			'synctoken' => 1
+		];
+
+		foreach($properties as $property=>$newValue) {
+
+			switch($property) {
+				case '{DAV:}displayname' :
+					$values['displayname'] = $newValue;
+					break;
+				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
+					$values['description'] = $newValue;
+					break;
+				default :
+					throw new BadRequest('Unknown property: ' . $property);
+			}
+
+		}
+
+		$query = $this->db->getQueryBuilder();
+		$query->insert('addressbooks')
+			->values([
+				'uri' => $query->createParameter('uri'),
+				'displayname' => $query->createParameter('displayname'),
+				'description' => $query->createParameter('description'),
+				'principaluri' => $query->createParameter('principaluri'),
+				'synctoken' => $query->createParameter('synctoken'),
+			])
+			->setParameters($values)
+			->execute();
+	}
+
+	/**
+	 * Deletes an entire addressbook and all its contents
+	 *
+	 * @param mixed $addressBookId
+	 * @return void
+	 */
+	function deleteAddressBook($addressBookId) {
+		$query = $this->db->getQueryBuilder();
+		$query->delete('cards')
+			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
+			->setParameter('addressbookid', $addressBookId)
+			->execute();
+
+		$query->delete('addressbookchanges')
+			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
+			->setParameter('addressbookid', $addressBookId)
+			->execute();
+
+		$query->delete('addressbooks')
+			->where($query->expr()->eq('id', $query->createParameter('id')))
+			->setParameter('id', $addressBookId)
+			->execute();
+	}
+
+	/**
+	 * Returns all cards for a specific addressbook id.
+	 *
+	 * This method should return the following properties for each card:
+	 *   * carddata - raw vcard data
+	 *   * uri - Some unique url
+	 *   * lastmodified - A unix timestamp
+	 *
+	 * It's recommended to also return the following properties:
+	 *   * etag - A unique etag. This must change every time the card changes.
+	 *   * size - The size of the card in bytes.
+	 *
+	 * If these last two properties are provided, less time will be spent
+	 * calculating them. If they are specified, you can also ommit carddata.
+	 * This may speed up certain requests, especially with large cards.
+	 *
+	 * @param mixed $addressBookId
+	 * @return array
+	 */
+	function getCards($addressBookId) {
+		$query = $this->db->getQueryBuilder();
+		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
+			->from('cards')
+			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
+
+		$cards = [];
+
+		$result = $query->execute();
+		while($row = $result->fetch()) {
+			$row['etag'] = '"' . $row['etag'] . '"';
+			$row['carddata'] = $this->readBlob($row['carddata']);
+			$cards[] = $row;
+		}
+		$result->closeCursor();
+
+		return $cards;
+	}
+
+	/**
+	 * Returns a specfic card.
+	 *
+	 * The same set of properties must be returned as with getCards. The only
+	 * exception is that 'carddata' is absolutely required.
+	 *
+	 * If the card does not exist, you must return false.
+	 *
+	 * @param mixed $addressBookId
+	 * @param string $cardUri
+	 * @return array
+	 */
+	function getCard($addressBookId, $cardUri) {
+		$query = $this->db->getQueryBuilder();
+		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
+			->from('cards')
+			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
+			->setMaxResults(1);
+
+		$result = $query->execute();
+		$row = $result->fetch();
+		if (!$row) {
+			return false;
+		}
+		$row['etag'] = '"' . $row['etag'] . '"';
+		$row['carddata'] = $this->readBlob($row['carddata']);
+
+		return $row;
+	}
+
+	/**
+	 * Returns a list of cards.
+	 *
+	 * This method should work identical to getCard, but instead return all the
+	 * cards in the list as an array.
+	 *
+	 * If the backend supports this, it may allow for some speed-ups.
+	 *
+	 * @param mixed $addressBookId
+	 * @param array $uris
+	 * @return array
+	 */
+	function getMultipleCards($addressBookId, array $uris) {
+		$query = $this->db->getQueryBuilder();
+		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
+			->from('cards')
+			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+			->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
+			->setParameter('uri', $uris, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
+
+		$cards = [];
+
+		$result = $query->execute();
+		while($row = $result->fetch()) {
+			$row['etag'] = '"' . $row['etag'] . '"';
+			$row['carddata'] = $this->readBlob($row['carddata']);
+			$cards[] = $row;
+		}
+		$result->closeCursor();
+
+		return $cards;
+	}
+
+	/**
+	 * Creates a new card.
+	 *
+	 * The addressbook id will be passed as the first argument. This is the
+	 * same id as it is returned from the getAddressBooksForUser method.
+	 *
+	 * The cardUri is a base uri, and doesn't include the full path. The
+	 * cardData argument is the vcard body, and is passed as a string.
+	 *
+	 * It is possible to return an ETag from this method. This ETag is for the
+	 * newly created resource, and must be enclosed with double quotes (that
+	 * is, the string itself must contain the double quotes).
+	 *
+	 * You should only return the ETag if you store the carddata as-is. If a
+	 * subsequent GET request on the same card does not have the same body,
+	 * byte-by-byte and you did return an ETag here, clients tend to get
+	 * confused.
+	 *
+	 * If you don't return an ETag, you can just return null.
+	 *
+	 * @param mixed $addressBookId
+	 * @param string $cardUri
+	 * @param string $cardData
+	 * @return string|null
+	 */
+	function createCard($addressBookId, $cardUri, $cardData) {
+		$etag = md5($cardData);
+
+		$query = $this->db->getQueryBuilder();
+		$query->insert('cards')
+			->values([
+				'carddata' => $query->createNamedParameter($cardData),
+				'uri' => $query->createNamedParameter($cardUri),
+				'lastmodified' => $query->createNamedParameter(time()),
+				'addressbookid' => $query->createNamedParameter($addressBookId),
+				'size' => $query->createNamedParameter(strlen($cardData)),
+				'etag' => $query->createNamedParameter($etag),
+			])
+			->execute();
+
+		$this->addChange($addressBookId, $cardUri, 1);
+
+		return '"' . $etag . '"';
+	}
+
+	/**
+	 * Updates a card.
+	 *
+	 * The addressbook id will be passed as the first argument. This is the
+	 * same id as it is returned from the getAddressBooksForUser method.
+	 *
+	 * The cardUri is a base uri, and doesn't include the full path. The
+	 * cardData argument is the vcard body, and is passed as a string.
+	 *
+	 * It is possible to return an ETag from this method. This ETag should
+	 * match that of the updated resource, and must be enclosed with double
+	 * quotes (that is: the string itself must contain the actual quotes).
+	 *
+	 * You should only return the ETag if you store the carddata as-is. If a
+	 * subsequent GET request on the same card does not have the same body,
+	 * byte-by-byte and you did return an ETag here, clients tend to get
+	 * confused.
+	 *
+	 * If you don't return an ETag, you can just return null.
+	 *
+	 * @param mixed $addressBookId
+	 * @param string $cardUri
+	 * @param string $cardData
+	 * @return string|null
+	 */
+	function updateCard($addressBookId, $cardUri, $cardData) {
+
+		$etag = md5($cardData);
+		$query = $this->db->getQueryBuilder();
+		$query->update('cards')
+			->set('carddata', $query->createNamedParameter($cardData, \PDO::PARAM_LOB))
+			->set('lastmodified', $query->createNamedParameter(time()))
+			->set('size', $query->createNamedParameter(strlen($cardData)))
+			->set('etag', $query->createNamedParameter($etag))
+			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
+			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+			->execute();
+
+		$this->addChange($addressBookId, $cardUri, 2);
+
+		return '"' . $etag . '"';
+	}
+
+	/**
+	 * Deletes a card
+	 *
+	 * @param mixed $addressBookId
+	 * @param string $cardUri
+	 * @return bool
+	 */
+	function deleteCard($addressBookId, $cardUri) {
+		$query = $this->db->getQueryBuilder();
+		$ret = $query->delete('cards')
+			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
+			->execute();
+
+		$this->addChange($addressBookId, $cardUri, 3);
+
+		return $ret === 1;
+	}
+
+	/**
+	 * The getChanges method returns all the changes that have happened, since
+	 * the specified syncToken in the specified address book.
+	 *
+	 * This function should return an array, such as the following:
+	 *
+	 * [
+	 *   'syncToken' => 'The current synctoken',
+	 *   'added'   => [
+	 *      'new.txt',
+	 *   ],
+	 *   'modified'   => [
+	 *      'modified.txt',
+	 *   ],
+	 *   'deleted' => [
+	 *      'foo.php.bak',
+	 *      'old.txt'
+	 *   ]
+	 * ];
+	 *
+	 * The returned syncToken property should reflect the *current* syncToken
+	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
+	 * property. This is needed here too, to ensure the operation is atomic.
+	 *
+	 * If the $syncToken argument is specified as null, this is an initial
+	 * sync, and all members should be reported.
+	 *
+	 * The modified property is an array of nodenames that have changed since
+	 * the last token.
+	 *
+	 * The deleted property is an array with nodenames, that have been deleted
+	 * from collection.
+	 *
+	 * The $syncLevel argument is basically the 'depth' of the report. If it's
+	 * 1, you only have to report changes that happened only directly in
+	 * immediate descendants. If it's 2, it should also include changes from
+	 * the nodes below the child collections. (grandchildren)
+	 *
+	 * The $limit argument allows a client to specify how many results should
+	 * be returned at most. If the limit is not specified, it should be treated
+	 * as infinite.
+	 *
+	 * If the limit (infinite or not) is higher than you're willing to return,
+	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+	 *
+	 * If the syncToken is expired (due to data cleanup) or unknown, you must
+	 * return null.
+	 *
+	 * The limit is 'suggestive'. You are free to ignore it.
+	 *
+	 * @param string $addressBookId
+	 * @param string $syncToken
+	 * @param int $syncLevel
+	 * @param int $limit
+	 * @return array
+	 */
+	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
+		// Current synctoken
+		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
+		$stmt->execute([ $addressBookId ]);
+		$currentToken = $stmt->fetchColumn(0);
+
+		if (is_null($currentToken)) return null;
+
+		$result = [
+			'syncToken' => $currentToken,
+			'added'     => [],
+			'modified'  => [],
+			'deleted'   => [],
+		];
+
+		if ($syncToken) {
+
+			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
+			if ($limit>0) {
+				$query .= " `LIMIT` " . (int)$limit;
+			}
+
+			// Fetching all changes
+			$stmt = $this->db->prepare($query);
+			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
+
+			$changes = [];
+
+			// This loop ensures that any duplicates are overwritten, only the
+			// last change on a node is relevant.
+			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+				$changes[$row['uri']] = $row['operation'];
+
+			}
+
+			foreach($changes as $uri => $operation) {
+
+				switch($operation) {
+					case 1:
+						$result['added'][] = $uri;
+						break;
+					case 2:
+						$result['modified'][] = $uri;
+						break;
+					case 3:
+						$result['deleted'][] = $uri;
+						break;
+				}
+
+			}
+		} else {
+			// No synctoken supplied, this is the initial sync.
+			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
+			$stmt = $this->db->prepare($query);
+			$stmt->execute([$addressBookId]);
+
+			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+		}
+		return $result;
+	}
+
+	/**
+	 * Adds a change record to the addressbookchanges table.
+	 *
+	 * @param mixed $addressBookId
+	 * @param string $objectUri
+	 * @param int $operation 1 = add, 2 = modify, 3 = delete
+	 * @return void
+	 */
+	protected function addChange($addressBookId, $objectUri, $operation) {
+		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
+		$stmt = $this->db->prepare($sql);
+		$stmt->execute([
+			$objectUri,
+			$addressBookId,
+			$operation,
+			$addressBookId
+		]);
+		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
+		$stmt->execute([
+			$addressBookId
+		]);
+	}
+
+	private function readBlob($cardData) {
+		if (is_resource($cardData)) {
+			return stream_get_contents($cardData);
+		}
+
+		return $cardData;
+	}
+
+}
diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php
index 7de2c2aabe381648c1c4d1f2090744229ea92479..850180d8481bedf6d54c2053f019619733f05804 100644
--- a/apps/dav/lib/rootcollection.php
+++ b/apps/dav/lib/rootcollection.php
@@ -2,8 +2,10 @@
 
 namespace OCA\DAV;
 
+use OCA\DAV\CardDAV\CardDavBackend;
 use OCA\DAV\Connector\Sabre\Principal;
 use Sabre\CalDAV\Principal\Collection;
+use Sabre\CardDAV\AddressBookRoot;
 use Sabre\DAV\SimpleCollection;
 
 class RootCollection extends SimpleCollection {
@@ -22,10 +24,14 @@ class RootCollection extends SimpleCollection {
 		$principalCollection->disableListing = $disableListing;
 		$filesCollection = new Files\RootCollection($principalBackend);
 		$filesCollection->disableListing = $disableListing;
+		$cardDavBackend = new CardDavBackend(\OC::$server->getDatabaseConnection());
+		$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend);
+		$addressBookRoot->disableListing = $disableListing;
 
 		$children = [
 			$principalCollection,
 			$filesCollection,
+			$addressBookRoot,
 		];
 
 		parent::__construct('root', $children);
diff --git a/apps/dav/tests/unit/bootstrap.php b/apps/dav/tests/unit/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..28f6b971dec378149bb6709b14feb9970d36c0b9
--- /dev/null
+++ b/apps/dav/tests/unit/bootstrap.php
@@ -0,0 +1,13 @@
+<?php
+
+define('PHPUNIT_RUN', 1);
+
+require_once __DIR__.'/../../../../lib/base.php';
+
+if(!class_exists('PHPUnit_Framework_TestCase')) {
+	require_once('PHPUnit/Autoload.php');
+}
+
+\OC_App::loadApp('dav');
+
+OC_Hook::clear();
diff --git a/apps/dav/tests/unit/carddav/carddavbackendtest.php b/apps/dav/tests/unit/carddav/carddavbackendtest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7456e9634c3511fb4cf462d23c1b3de8a7329ea
--- /dev/null
+++ b/apps/dav/tests/unit/carddav/carddavbackendtest.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Tests\Unit\CardDAV;
+
+use OCA\DAV\CardDAV\CardDavBackend;
+use Sabre\DAV\PropPatch;
+use Test\TestCase;
+
+class CardDavBackendTest extends TestCase {
+
+	/** @var CardDavBackend */
+	private $backend;
+
+	const UNIT_TEST_USER = 'carddav-unit-test';
+
+
+	public function setUp() {
+		parent::setUp();
+
+		$db = \OC::$server->getDatabaseConnection();
+		$this->backend = new CardDavBackend($db);
+
+		$this->tearDown();
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+
+		if (is_null($this->backend)) {
+			return;
+		}
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		foreach ($books as $book) {
+			$this->backend->deleteAddressBook($book['id']);
+		}
+	}
+
+	public function testAddressBookOperations() {
+
+		// create a new address book
+		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(1, count($books));
+
+		// update it's display name
+		$patch = new PropPatch([
+			'{DAV:}displayname' => 'Unit test',
+			'{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Addressbook used for unit testing'
+		]);
+		$this->backend->updateAddressBook($books[0]['id'], $patch);
+		$patch->commit();
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(1, count($books));
+		$this->assertEquals('Unit test', $books[0]['{DAV:}displayname']);
+		$this->assertEquals('Addressbook used for unit testing', $books[0]['{urn:ietf:params:xml:ns:carddav}addressbook-description']);
+
+		// delete the address book
+		$this->backend->deleteAddressBook($books[0]['id']);
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(0, count($books));
+	}
+
+	public function testCardOperations() {
+		// create a new address book
+		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(1, count($books));
+		$bookId = $books[0]['id'];
+
+		// create a card
+		$uri = $this->getUniqueID('card');
+		$this->backend->createCard($bookId, $uri, '');
+
+		// get all the cards
+		$cards = $this->backend->getCards($bookId);
+		$this->assertEquals(1, count($cards));
+		$this->assertEquals('', $cards[0]['carddata']);
+
+		// get the cards
+		$card = $this->backend->getCard($bookId, $uri);
+		$this->assertNotNull($card);
+		$this->assertArrayHasKey('id', $card);
+		$this->assertArrayHasKey('uri', $card);
+		$this->assertArrayHasKey('lastmodified', $card);
+		$this->assertArrayHasKey('etag', $card);
+		$this->assertArrayHasKey('size', $card);
+		$this->assertEquals('', $card['carddata']);
+
+		// update the card
+		$this->backend->updateCard($bookId, $uri, '***');
+		$card = $this->backend->getCard($bookId, $uri);
+		$this->assertEquals('***', $card['carddata']);
+
+		// delete the card
+		$this->backend->deleteCard($bookId, $uri);
+		$cards = $this->backend->getCards($bookId);
+		$this->assertEquals(0, count($cards));
+	}
+
+	public function testMultiCard() {
+		// create a new address book
+		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(1, count($books));
+		$bookId = $books[0]['id'];
+
+		// create a card
+		$uri0 = $this->getUniqueID('card');
+		$this->backend->createCard($bookId, $uri0, '');
+		$uri1 = $this->getUniqueID('card');
+		$this->backend->createCard($bookId, $uri1, '');
+		$uri2 = $this->getUniqueID('card');
+		$this->backend->createCard($bookId, $uri2, '');
+
+		// get all the cards
+		$cards = $this->backend->getCards($bookId);
+		$this->assertEquals(3, count($cards));
+		$this->assertEquals('', $cards[0]['carddata']);
+		$this->assertEquals('', $cards[1]['carddata']);
+		$this->assertEquals('', $cards[2]['carddata']);
+
+		// get the cards
+		$cards = $this->backend->getMultipleCards($bookId, [$uri1, $uri2]);
+		$this->assertEquals(2, count($cards));
+		foreach($cards as $card) {
+			$this->assertArrayHasKey('id', $card);
+			$this->assertArrayHasKey('uri', $card);
+			$this->assertArrayHasKey('lastmodified', $card);
+			$this->assertArrayHasKey('etag', $card);
+			$this->assertArrayHasKey('size', $card);
+			$this->assertEquals('', $card['carddata']);
+		}
+
+		// delete the card
+		$this->backend->deleteCard($bookId, $uri0);
+		$this->backend->deleteCard($bookId, $uri1);
+		$this->backend->deleteCard($bookId, $uri2);
+		$cards = $this->backend->getCards($bookId);
+		$this->assertEquals(0, count($cards));
+	}
+
+	public function testSyncSupport() {
+		// create a new address book
+		$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+		$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+		$this->assertEquals(1, count($books));
+		$bookId = $books[0]['id'];
+
+		// fist call without synctoken
+		$changes = $this->backend->getChangesForAddressBook($bookId, '', 1);
+		$syncToken = $changes['syncToken'];
+
+		// add a change
+		$uri0 = $this->getUniqueID('card');
+		$this->backend->createCard($bookId, $uri0, '');
+
+		// look for changes
+		$changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1);
+		$this->assertEquals($uri0, $changes['added'][0]);
+	}
+}
diff --git a/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php
index 1e390cf15f7f6b81d336620ba2094bf9a645cd60..3004c03b2660e5509a27390b88ea118985919f4d 100644
--- a/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php
+++ b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php
@@ -19,7 +19,7 @@
  *
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
 use Test\TestCase;
diff --git a/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php
index 1fd89c84ff61920fcb283b67e55f54958436cb2f..d2d4a849a515dc4160dd1b33cfbade751343c031 100644
--- a/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php
+++ b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php
@@ -19,7 +19,7 @@
  *
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin;
 use Test\TestCase;
diff --git a/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php
index c0acd4fc3dea51110b8c56a5a7489ab9d341c0fd..34fa7f7eef92277ba7675b10772261bac75cb329 100644
--- a/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php
+++ b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php
@@ -19,7 +19,7 @@
  *
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use OCA\DAV\Connector\Sabre\MaintenancePlugin;
 use Test\TestCase;
diff --git a/apps/dav/tests/unit/connector/sabre/auth.php b/apps/dav/tests/unit/connector/sabre/auth.php
index 0466f3aab7704ff4da13df1536acd2aabb048985..d18747d732aa6c59855c62f25af151a956406176 100644
--- a/apps/dav/tests/unit/connector/sabre/auth.php
+++ b/apps/dav/tests/unit/connector/sabre/auth.php
@@ -18,7 +18,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  *
  */
-namespace Tests\Connector\Sabre;
+
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use Test\TestCase;
 use OCP\ISession;
diff --git a/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php
index 2080755cd51f1be47573e5ee2245e1f211f19c99..74dd4edd8cf21bfe69ee77024feec541c34dd83e 100644
--- a/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php
+++ b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 /**
  * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
diff --git a/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php
index 973a5d4c27b44c0ad428b1719d2531d0638e1b48..e1bcc996908b39177d34abde8d8991a79d5bab76 100644
--- a/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php
+++ b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 /**
  * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
@@ -16,7 +16,7 @@ class CustomPropertiesBackend extends \Test\TestCase {
 	private $server;
 
 	/**
-	 * @var \Sabre\DAV\ObjectTree
+	 * @var \Sabre\DAV\Tree
 	 */
 	private $tree;
 
diff --git a/apps/dav/tests/unit/connector/sabre/directory.php b/apps/dav/tests/unit/connector/sabre/directory.php
index d85290df80abe286347dfb9b7fa3a8a9a6f00c64..148a91d26dbb5da0b8c7f14bc4aaf68dd353e755 100644
--- a/apps/dav/tests/unit/connector/sabre/directory.php
+++ b/apps/dav/tests/unit/connector/sabre/directory.php
@@ -6,11 +6,14 @@
  * later.
  * See the COPYING-README file.
  */
-class Test_OC_Connector_Sabre_Directory extends \Test\TestCase {
 
-	/** @var OC\Files\View | PHPUnit_Framework_MockObject_MockObject */
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
+
+class Directory extends \Test\TestCase {
+
+	/** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */
 	private $view;
-	/** @var OC\Files\FileInfo | PHPUnit_Framework_MockObject_MockObject */
+	/** @var \OC\Files\FileInfo | \PHPUnit_Framework_MockObject_MockObject */
 	private $info;
 
 	protected function setUp() {
diff --git a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
index 4c0af58ffeadb3c5ab916000fd8ffd56bcec8f0e..19e82320d55a28d72d80d7014e74979e84ab0a7c 100644
--- a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
+++ b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Test\Connector\Sabre\Exception;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
 
 use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
 
diff --git a/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php
index d85aa5a9cc35e3c0b6ef305149f8c3a48af3276d..0c364df012bb490227727e837e3ea65b67950aba 100644
--- a/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php
@@ -7,7 +7,7 @@
  * See the COPYING-README file.
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
 use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin as PluginToTest;
diff --git a/apps/dav/tests/unit/connector/sabre/file.php b/apps/dav/tests/unit/connector/sabre/file.php
index d874b7f33c227ffdf17c31df4c47bbf414292ffe..94dadf88fe4d2bf73537b6464e15d819b9280b9c 100644
--- a/apps/dav/tests/unit/connector/sabre/file.php
+++ b/apps/dav/tests/unit/connector/sabre/file.php
@@ -6,7 +6,7 @@
  * See the COPYING-README file.
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use OC\Files\Storage\Local;
 use Test\HookHelper;
diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php
index db3bbabefd0a2c5928e90219a8528f4f0d2936f6..f3c862941c0a57ea5bc64f4a1d5f739cde5e4a70 100644
--- a/apps/dav/tests/unit/connector/sabre/filesplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 /**
  * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
diff --git a/apps/dav/tests/unit/connector/sabre/node.php b/apps/dav/tests/unit/connector/sabre/node.php
index a9610fd84b37cd410088ce1be34d9144a7c808f7..cee64fb7dff2ae170a3b09dee4cc54b1f5493556 100644
--- a/apps/dav/tests/unit/connector/sabre/node.php
+++ b/apps/dav/tests/unit/connector/sabre/node.php
@@ -7,7 +7,7 @@
  * See the COPYING-README file.
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 class Node extends \Test\TestCase {
 	public function davPermissionsProvider() {
diff --git a/apps/dav/tests/unit/connector/sabre/objecttree.php b/apps/dav/tests/unit/connector/sabre/objecttree.php
index 2691385c1c11c8e9010b6a6b12ac5abeee2bb5a3..3a56404e552ac3380d346e43552744293d81ca39 100644
--- a/apps/dav/tests/unit/connector/sabre/objecttree.php
+++ b/apps/dav/tests/unit/connector/sabre/objecttree.php
@@ -6,11 +6,10 @@
  * See the COPYING-README file.
  */
 
-namespace Test\OCA\DAV\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 
 use OC\Files\FileInfo;
-use OCA\DAV\Connector\Sabre\Directory;
 use OC\Files\Storage\Temporary;
 
 class TestDoubleFileView extends \OC\Files\View {
@@ -103,7 +102,7 @@ class ObjectTree extends \Test\TestCase {
 
 		$info = new FileInfo('', null, null, array(), null);
 
-		$rootDir = new Directory($view, $info);
+		$rootDir = new \OCA\DAV\Connector\Sabre\Directory($view, $info);
 		$objectTree = $this->getMock('\OCA\DAV\Connector\Sabre\ObjectTree',
 			array('nodeExists', 'getNodeForPath'),
 			array($rootDir, $view));
diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php
index 3c0abeac3f19d56a3c7d12c57ff7a146798bcc59..2fbab124fb7e1cd4c0813748d1bdc90cd158b84f 100644
--- a/apps/dav/tests/unit/connector/sabre/principal.php
+++ b/apps/dav/tests/unit/connector/sabre/principal.php
@@ -8,7 +8,7 @@
  * See the COPYING-README file.
  */
 
-namespace Test\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 use \Sabre\DAV\PropPatch;
 use OCP\IUserManager;
diff --git a/apps/dav/tests/unit/connector/sabre/quotaplugin.php b/apps/dav/tests/unit/connector/sabre/quotaplugin.php
index 5d3364e1f8cf23d83d1b24f5cf13a11db0eca310..470fd9cbf850c17a0db0e00230f5acc086ac13d2 100644
--- a/apps/dav/tests/unit/connector/sabre/quotaplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/quotaplugin.php
@@ -1,12 +1,13 @@
 <?php
 
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 /**
  * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu>
  * This file is licensed under the Affero General Public License version 3 or
  * later.
  * See the COPYING-README file.
  */
-class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase {
+class QuotaPlugin extends \Test\TestCase {
 
 	/**
 	 * @var \Sabre\DAV\Server
diff --git a/apps/dav/tests/unit/connector/sabre/tagsplugin.php b/apps/dav/tests/unit/connector/sabre/tagsplugin.php
index 4731e770cfa79a0f5374ff200767aeff1acae2b7..f1f6cc40dabb46a55799e1dfe3d1596314825cc1 100644
--- a/apps/dav/tests/unit/connector/sabre/tagsplugin.php
+++ b/apps/dav/tests/unit/connector/sabre/tagsplugin.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Tests\Connector\Sabre;
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
 
 /**
  * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
@@ -20,7 +20,7 @@ class TagsPlugin extends \Test\TestCase {
 	private $server;
 
 	/**
-	 * @var \Sabre\DAV\ObjectTree
+	 * @var \Sabre\DAV\Tree
 	 */
 	private $tree;
 
diff --git a/apps/dav/tests/unit/phpunit.xml b/apps/dav/tests/unit/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..46c3cdfb345fe1a136808337d66bc5094ac8ed21
--- /dev/null
+++ b/apps/dav/tests/unit/phpunit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<phpunit bootstrap="bootstrap.php"
+		 verbose="true"
+		 timeoutForSmallTests="900"
+		 timeoutForMediumTests="900"
+		 timeoutForLargeTests="900"
+		>
+	<testsuite name='unit'>
+		<directory suffix='test.php'>.</directory>
+	</testsuite>
+	<!-- filters for code coverage -->
+	<filter>
+		<whitelist>
+			<directory suffix=".php">../../dav</directory>
+			<exclude>
+				<directory suffix=".php">../../dav/tests</directory>
+			</exclude>
+		</whitelist>
+	</filter>
+	<logging>
+		<!-- and this is where your report will be written -->
+		<log type="coverage-clover" target="./clover.xml"/>
+	</logging>
+</phpunit>
+