Skip to content

Commit

Permalink
Merge pull request #16555 from iterate-ch/bugfix/MD-22149
Browse files Browse the repository at this point in the history
Include resource in If: header for lock tokens
  • Loading branch information
dkocher authored Nov 20, 2024
2 parents fc71d79 + e82e4c2 commit 5092773
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import ch.cyberduck.core.ConnectionCallback;
import ch.cyberduck.core.DescriptiveUrl;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Copy;
import ch.cyberduck.core.features.Lock;
Expand Down Expand Up @@ -48,16 +49,16 @@ public DAVCopyFeature(final DAVSession session) {
public Path copy(final Path source, final Path copy, final TransferStatus status, final ConnectionCallback callback, final StreamListener listener) throws BackgroundException {
try {
final String target = new DefaultUrlProvider(session.getHost()).toUrl(copy).find(DescriptiveUrl.Type.provider).getUrl();
if(session.getFeature(Lock.class) != null && status.getLockId() != null) {
if(status.getLockId() != null && session.getFeature(Lock.class) != null) {
// Indicate that the client has knowledge of that state token
session.getClient().copy(new DAVPathEncoder().encode(source), target, status.isExists(),
Collections.singletonMap(HttpHeaders.IF, String.format("(<%s>)", status.getLockId())));
Collections.singletonMap(HttpHeaders.IF, String.format("<%s> (<%s>)", new DAVPathEncoder().encode(source), status.getLockId())));
}
else {
session.getClient().copy(new DAVPathEncoder().encode(source), target, status.isExists());
}
listener.sent(status.getLength());
return copy.withAttributes(source.attributes());
return copy.withAttributes(new PathAttributes(source.attributes()).withLockId(null));
}
catch(SardineException e) {
throw new DAVExceptionMappingService().map("Cannot copy {0}", e, source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public void delete(final Map<Path, TransferStatus> files, final PasswordCallback

protected HttpRequestBase toRequest(final Path file, final TransferStatus status) {
final HttpDelete request = new HttpDelete(new DAVPathEncoder().encode(file));
if(session.getFeature(Lock.class) != null && status.getLockId() != null) {
if(status.getLockId() != null && session.getFeature(Lock.class) != null) {
// Indicate that the client has knowledge of that state token
request.setHeader(HttpHeaders.IF, String.format("(<%s>)", status.getLockId()));
request.setHeader(HttpHeaders.IF, String.format("<%s> (<%s>)", new DAVPathEncoder().encode(file), status.getLockId()));
}
return request;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import ch.cyberduck.core.ConnectionCallback;
import ch.cyberduck.core.DescriptiveUrl;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.features.Delete;
import ch.cyberduck.core.features.Lock;
Expand Down Expand Up @@ -49,18 +50,18 @@ public DAVMoveFeature(final DAVSession session) {
public Path move(final Path file, final Path renamed, final TransferStatus status, final Delete.Callback callback, final ConnectionCallback connectionCallback) throws BackgroundException {
try {
final String target = new DefaultUrlProvider(session.getHost()).toUrl(renamed).find(DescriptiveUrl.Type.provider).getUrl();
if(session.getFeature(Lock.class) != null && status.getLockId() != null) {
if(status.getLockId() != null && session.getFeature(Lock.class) != null) {
// Indicate that the client has knowledge of that state token
session.getClient().move(new DAVPathEncoder().encode(file), file.isDirectory() ? String.format("%s/", target) : target,
status.isExists(),
Collections.singletonMap(HttpHeaders.IF, String.format("(<%s>)", status.getLockId())));
Collections.singletonMap(HttpHeaders.IF, String.format("<%s> (<%s>)", new DAVPathEncoder().encode(file), status.getLockId())));
}
else {
session.getClient().move(new DAVPathEncoder().encode(file), file.isDirectory() ? String.format("%s/", target) : target,
status.isExists());
}
// Copy original file attributes
return renamed.withAttributes(file.attributes());
return renamed.withAttributes(new PathAttributes(file.attributes()).withLockId(null));
}
catch(SardineException e) {
throw new DAVExceptionMappingService().map("Cannot rename {0}", e, file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import ch.cyberduck.core.DisabledLoginCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.exception.ConflictException;
import ch.cyberduck.core.exception.InteroperabilityException;
import ch.cyberduck.core.features.Delete;
import ch.cyberduck.core.features.Find;
import ch.cyberduck.core.io.DisabledStreamListener;
Expand Down Expand Up @@ -55,6 +56,13 @@ public void testCopyFile() throws Exception {
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(copy), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyWithLock() throws Exception {
final Path test = new DAVTouchFeature(session).touch(new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
assertThrows(InteroperabilityException.class, () -> new DAVLockFeature(session).lock(test));
new DAVDeleteFeature(session).delete(Collections.singletonMap(test, new TransferStatus()), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyToExistingFile() throws Exception {
final Path folder = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
Expand Down
23 changes: 6 additions & 17 deletions webdav/src/test/java/ch/cyberduck/core/dav/DAVMoveFeatureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import ch.cyberduck.core.PathAttributes;
import ch.cyberduck.core.exception.ConflictException;
import ch.cyberduck.core.exception.InteroperabilityException;
import ch.cyberduck.core.exception.LockedException;
import ch.cyberduck.core.exception.NotfoundException;
import ch.cyberduck.core.features.Delete;
import ch.cyberduck.core.http.HttpResponseOutputStream;
Expand Down Expand Up @@ -58,26 +59,14 @@ public void testMove() throws Exception {
assertTrue(new DAVFindFeature(session).find(target));
assertEquals(status.getResponse(), target.attributes());
assertEquals(attr, new DAVAttributesFinderFeature(session).find(target));
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testMoveWithLock() throws Exception {
final Path test = new DAVTouchFeature(session).touch(new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
String lock = null;
try {
lock = new DAVLockFeature(session).lock(test);
}
catch(InteroperabilityException e) {
// Not supported
}
assertEquals(TransferStatus.UNKNOWN_LENGTH, test.attributes().getSize());
final Path target = new DAVMoveFeature(session).move(test,
new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLockId(lock), new Delete.DisabledCallback(), new DisabledConnectionCallback());
assertFalse(new DAVFindFeature(session).find(test));
assertTrue(new DAVFindFeature(session).find(target));
assertEquals(test.attributes(), target.attributes());
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
assertThrows(InteroperabilityException.class, () -> new DAVLockFeature(session).lock(test));
new DAVDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
Expand Down Expand Up @@ -105,7 +94,7 @@ public void testMoveDirectory() throws Exception {
assertEquals(attr, new DAVAttributesFinderFeature(session).find(new Path(target, test.getName(), EnumSet.of(Path.Type.file))));
assertEquals(attr.getModificationDate(), new DAVAttributesFinderFeature(session).find(new Path(target, test.getName(), EnumSet.of(Path.Type.file))).getModificationDate());
assertNotEquals(attr.getETag(), new DAVAttributesFinderFeature(session).find(new Path(target, test.getName(), EnumSet.of(Path.Type.file))).getETag());
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
Expand All @@ -118,7 +107,7 @@ public void testMoveOverride() throws Exception {
new DAVMoveFeature(session).move(test, target, new TransferStatus().exists(true), new Delete.DisabledCallback(), new DisabledConnectionCallback());
assertFalse(new DAVFindFeature(session).find(test));
assertTrue(new DAVFindFeature(session).find(target));
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.singletonList(target), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test(expected = NotfoundException.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package ch.cyberduck.core.dav.microsoft;

/*
* Copyright (c) 2002-2013 David Kocher. All rights reserved.
* http://cyberduck.ch/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch
*/

import ch.cyberduck.core.AlphanumericRandomStringService;
import ch.cyberduck.core.DisabledConnectionCallback;
import ch.cyberduck.core.DisabledLoginCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.dav.AbstractDAVTest;
import ch.cyberduck.core.dav.DAVAttributesFinderFeature;
import ch.cyberduck.core.dav.DAVCopyFeature;
import ch.cyberduck.core.dav.DAVDeleteFeature;
import ch.cyberduck.core.dav.DAVDirectoryFeature;
import ch.cyberduck.core.dav.DAVLockFeature;
import ch.cyberduck.core.dav.DAVTouchFeature;
import ch.cyberduck.core.exception.ConflictException;
import ch.cyberduck.core.features.Delete;
import ch.cyberduck.core.features.Find;
import ch.cyberduck.core.io.DisabledStreamListener;
import ch.cyberduck.core.shared.DefaultFindFeature;
import ch.cyberduck.core.shared.DefaultHomeFinderService;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.test.IntegrationTest;

import org.junit.Test;
import org.junit.experimental.categories.Category;

import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;

import static org.junit.Assert.*;

@Category(IntegrationTest.class)
public class MicrosoftIISDAVCopyFeatureTest extends AbstractMicrosoftIISDAVTest {

@Test
public void testCopyFile() throws Exception {
final Path test = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVTouchFeature(session).touch(test, new TransferStatus());
final Path copy = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVCopyFeature(session).copy(test, copy, new TransferStatus(), new DisabledConnectionCallback(), new DisabledStreamListener());
assertEquals(new DAVAttributesFinderFeature(session).find(test), new DAVAttributesFinderFeature(session).find(copy));
assertTrue(new MicrosoftIISDAVFindFeature(session).find(test));
assertTrue(new MicrosoftIISDAVFindFeature(session).find(copy));
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.<Path>singletonList(copy), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyWithLock() throws Exception {
final Path test = new DAVTouchFeature(session).touch(new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
final String lock = new DAVLockFeature(session).lock(test);
assertEquals(TransferStatus.UNKNOWN_LENGTH, test.attributes().getSize());
final Path target = new DAVCopyFeature(session).copy(test,
new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus().withLockId(lock), new DisabledConnectionCallback(), new DisabledStreamListener());
assertTrue(new MicrosoftIISDAVFindFeature(session).find(test));
assertTrue(new MicrosoftIISDAVFindFeature(session).find(target));
assertEquals(test.attributes(), target.attributes());
new DAVDeleteFeature(session).delete(Collections.singletonMap(test, new TransferStatus().withLockId(lock)), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.singletonMap(target, new TransferStatus()), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyToExistingFile() throws Exception {
final Path folder = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
new DAVDirectoryFeature(session).mkdir(folder, new TransferStatus());
final Path test = new Path(folder, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVTouchFeature(session).touch(test, new TransferStatus());
final Path copy = new Path(folder, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVTouchFeature(session).touch(copy, new TransferStatus());
assertThrows(ConflictException.class, () -> new DAVCopyFeature(session).copy(test, copy, new TransferStatus().exists(false), new DisabledConnectionCallback(), new DisabledStreamListener()));
new DAVCopyFeature(session).copy(test, copy, new TransferStatus().exists(true), new DisabledConnectionCallback(), new DisabledStreamListener());
final Find find = new DefaultFindFeature(session);
assertTrue(find.find(test));
assertTrue(find.find(copy));
new DAVDeleteFeature(session).delete(Arrays.asList(test, copy), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyWithLockToExistingFile() throws Exception {
final Path folder = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
new DAVDirectoryFeature(session).mkdir(folder, new TransferStatus());
final Path test = new Path(folder, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVTouchFeature(session).touch(test, new TransferStatus());
final String lock = new DAVLockFeature(session).lock(test);
final Path copy = new Path(folder, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
new DAVTouchFeature(session).touch(copy, new TransferStatus());
assertThrows(ConflictException.class, () -> new DAVCopyFeature(session).copy(test, copy, new TransferStatus().exists(false), new DisabledConnectionCallback(), new DisabledStreamListener()));
new DAVCopyFeature(session).copy(test, copy, new TransferStatus().exists(true).withLockId(lock), new DisabledConnectionCallback(), new DisabledStreamListener());
final Find find = new DefaultFindFeature(session);
assertTrue(find.find(test));
assertTrue(find.find(copy));
new DAVDeleteFeature(session).delete(Collections.singletonMap(test, new TransferStatus().withLockId(lock)), new DisabledLoginCallback(), new Delete.DisabledCallback());
new DAVDeleteFeature(session).delete(Collections.singletonMap(copy, new TransferStatus()), new DisabledLoginCallback(), new Delete.DisabledCallback());
}

@Test
public void testCopyDirectory() throws Exception {
final Path directory = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
final String name = new AlphanumericRandomStringService().random();
final Path file = new Path(directory, name, EnumSet.of(Path.Type.file));
new DAVDirectoryFeature(session).mkdir(directory, new TransferStatus());
new DAVTouchFeature(session).touch(file, new TransferStatus());
final Path copy = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
new DAVDirectoryFeature(session).mkdir(copy, new TransferStatus());
assertThrows(ConflictException.class, () -> new DAVCopyFeature(session).copy(directory, copy, new TransferStatus().exists(false), new DisabledConnectionCallback(), new DisabledStreamListener()));
new DAVCopyFeature(session).copy(directory, copy, new TransferStatus().exists(true), new DisabledConnectionCallback(), new DisabledStreamListener());
assertTrue(new MicrosoftIISDAVFindFeature(session).find(file));
assertTrue(new MicrosoftIISDAVFindFeature(session).find(copy));
assertTrue(new MicrosoftIISDAVFindFeature(session).find(new Path(copy, name, EnumSet.of(Path.Type.file))));
new DAVDeleteFeature(session).delete(Arrays.asList(copy, directory), new DisabledLoginCallback(), new Delete.DisabledCallback());
}
}
Loading

0 comments on commit 5092773

Please sign in to comment.