Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ WebDAV returns the following additional properties in response to a `PROPFIND` r
- `{http://nextcloud.org/ns}lock-owner-displayname`: Display name of the lock owner
- `{http://nextcloud.org/ns}lock-owner-editor`: App ID for an app-owned lock. Clients can use it to suggest joining the collaborative editing session in the web interface or through direct editing. In the response to an `X-User-Lock` `LOCK` request, this property currently contains the lock owner regardless of lock type.
- `{http://nextcloud.org/ns}lock-time`: Timestamp at which the lock was created
- `{http://nextcloud.org/ns}lock-timeout`: Configured lock timeout in seconds from creation. A value of <=`-1` indicates that the lock does not expire.
- `{http://nextcloud.org/ns}lock-timeout`: Configured lock timeout in seconds from creation. A value of `0` indicates that the lock does not expire.
- `{http://nextcloud.org/ns}lock-token`: Lock token. Clients using native WebDAV locking must retain it while holding the lock and provide it when unlocking.

```bash
Expand Down
13 changes: 11 additions & 2 deletions lib/DAV/LockPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public function customProperties(PropFind $propFind, INode $node): void {
return null;
}

return $lock->getTimeout();
return $this->davTimeout($lock);
});

$propFind->handle(Application::DAV_PROPERTY_LOCK_OWNER_DISPLAYNAME, function () use ($nodeId, $node): ?string {
Expand Down Expand Up @@ -288,8 +288,17 @@ private function getLockProperties(?FileLock $lock, Node $file): array {
? $lock->getOwner()
: null,
Application::DAV_PROPERTY_LOCK_TIME => $lock ? $lock->getCreatedAt() : null,
Application::DAV_PROPERTY_LOCK_TIMEOUT => $lock ? $lock->getTimeout() : null,
Application::DAV_PROPERTY_LOCK_TIMEOUT => $lock ? $this->davTimeout($lock) : null,
Application::DAV_PROPERTY_LOCK_TOKEN => $lock ? $lock->getToken() : null,
];
}

/**
* Lifetime as clients read it: 0 means the lock never expires. A lock that
* never expires has a negative lifetime internally, and sending that raw put
* the expiry date in the past.
*/
private function davTimeout(FileLock $lock): int {
return max(0, $lock->getTimeout());
}
}
2 changes: 1 addition & 1 deletion lib/Model/FileLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public function toLockInfo(): LockInfo {
$lock = new LockInfo();
$lock->owner = $this->getDisplayName();
$lock->token = $this->getToken();
$lock->timeout = $this->getTimeout();
$lock->timeout = $this->getTimeout() <= 0 ? LockInfo::TIMEOUT_INFINITE : $this->getTimeout();
$lock->created = $this->getCreatedAt();
$lock->scope = LockInfo::EXCLUSIVE;
$lock->depth = 1;
Expand Down
47 changes: 47 additions & 0 deletions tests/Feature/LockFeatureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
*/

use OC\Files\Lock\LockManager;
use OC\Files\View;
use OCA\DAV\Connector\Sabre\File as DavFile;
use OCA\FilesLock\AppInfo\Application;
use OCA\FilesLock\ConfigLexicon;
use OCA\FilesLock\DAV\LockPlugin;
use OCA\FilesLock\Model\FileLock;
use OCA\FilesLock\Service\LockService;
use OCP\AppFramework\Utility\ITimeFactory;
Expand All @@ -23,6 +26,8 @@
use OCP\Share\IShare;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Sabre\DAV\Locks\LockInfo;
use Sabre\DAV\PropFind;
use Test\TestCase;
use Test\Util\User\Dummy;

Expand All @@ -42,6 +47,8 @@ class LockFeatureTest extends TestCase {
'test-file3',
'test-file-expire',
'test-file-infinite',
'test-file-dav-infinite',
'test-file-dav-expiring',
'test-file_public',
'test-file-client',
'etag_test',
Expand All @@ -66,6 +73,7 @@ public static function setUpBeforeClass(): void {
public function setUp(): void {
parent::setUp();
$this->time = null;
\OCP\Server::get(IConfig::class)->deleteAppValue(Application::APP_ID, ConfigLexicon::LOCK_TIMEOUT);
$this->lockManager = \OCP\Server::get(ILockManager::class);
$this->rootFolder = \OCP\Server::get(IRootFolder::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
Expand Down Expand Up @@ -314,6 +322,37 @@ public function testLockUserInfinite(): void {
}
}

/**
* Clients read the remaining lifetime from the WebDAV property and treat 0 as
* "no expiry". Under the default configuration the internal lifetime is
* negative, and sending that raw put the expiry date in the past.
*/
public function testInfiniteLockReportsNoExpiryOverWebdav(): void {
$folder = $this->loginAndGetUserFolder(self::TEST_USER1);

$infinite = $folder->newFile('test-file-dav-infinite', 'AAA');
$this->lockManager->lock(new LockContext($infinite, ILock::TYPE_USER, self::TEST_USER1));
self::assertSame(0, $this->davLockTimeout($infinite));

\OCP\Server::get(IConfig::class)->setAppValue(Application::APP_ID, ConfigLexicon::LOCK_TIMEOUT, '30');
$expiring = $folder->newFile('test-file-dav-expiring', 'AAA');
$this->lockManager->lock(new LockContext($expiring, ILock::TYPE_USER, self::TEST_USER1));
self::assertSame(30 * 60, $this->davLockTimeout($expiring));
}

/**
* The standard {DAV:}timeout property has its own sentinel for a lock that
* never expires (RFC4918 renders it as "Infinite", which Sabre only emits
* for a timeout of exactly -1). Sending the raw negative internal lifetime
* there produced the invalid "Second--60"; -60 and 0 are what
* LockService::lock() actually stores for the two "never expires" configs.
*/
public function testInfiniteLockReportsStandardWebdavTimeoutAsInfinite(): void {
self::assertSame(LockInfo::TIMEOUT_INFINITE, (new FileLock(-60))->toLockInfo()->timeout);
self::assertSame(LockInfo::TIMEOUT_INFINITE, (new FileLock(0))->toLockInfo()->timeout);
self::assertSame(30 * 60, (new FileLock(30 * 60))->toLockInfo()->timeout);
}

public function testLockApp(): void {
$file = $this->loginAndGetUserFolder(self::TEST_USER1)
->newFile('test-file2', 'AAA');
Expand Down Expand Up @@ -533,6 +572,14 @@ public function testRemoteLockKeepsTheRemoteDisplayName(): void {
);
}

private function davLockTimeout(File $file): ?int {
$view = new View('/' . self::TEST_USER1 . '/files');
$propFind = new PropFind($file->getName(), [Application::DAV_PROPERTY_LOCK_TIMEOUT]);
\OCP\Server::get(LockPlugin::class)->customProperties($propFind, new DavFile($view, $file));

return $propFind->get(Application::DAV_PROPERTY_LOCK_TIMEOUT);
}

private function loginAndGetUserFolder(string $userId) {
$this->loginAsUser($userId);
return $this->rootFolder->getUserFolder($userId);
Expand Down
Loading