diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14459c4898..b89609259d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ linux-self-hosted, macos-self-hosted, windows-self-hosted ] + os: [ linux-self-hosted ] steps: - uses: actions/checkout@v6 - name: Set up JDK 21 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4a3e4100..c5f2419082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * [Bugfix] Browser window does not show up in Exposé & Mission Control (macOS) ([#17703](https://trac.cyberduck.io/ticket/17703)) * [Bugfix] Compare public key blob instead of comment when retrieving key from agent (SFTP) * [Bugfix] Support "Include" directive when reading from OpenSSH config (SFTP) ([#10451](https://trac.cyberduck.io/ticket/10451)) +* [Bugfix] Exclude trashed folders in list by default (Backblaze B2) ([#18101](https://trac.cyberduck.io/ticket/18101)) [9.4.1](https://github.com/iterate-ch/cyberduck/compare/release-9-4-0...release-9-4-1) * [Bugfix] Cleartext uploads to unlocked vault with auto detect disabled in Preferences ( diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ObjectListService.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ObjectListService.java index 196c03d85c..85143d1476 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2ObjectListService.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2ObjectListService.java @@ -17,6 +17,7 @@ package ch.cyberduck.core.b2; import ch.cyberduck.core.AttributedList; import ch.cyberduck.core.DefaultIOExceptionMappingService; +import ch.cyberduck.core.DefaultPathAttributes; import ch.cyberduck.core.DefaultPathContainerService; import ch.cyberduck.core.ListProgressListener; import ch.cyberduck.core.ListService; @@ -28,15 +29,27 @@ import ch.cyberduck.core.VersioningConfiguration; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.preferences.HostPreferencesFactory; +import ch.cyberduck.core.threading.BackgroundExceptionCallable; +import ch.cyberduck.core.threading.ThreadPool; +import ch.cyberduck.core.threading.ThreadPoolFactory; +import ch.cyberduck.core.worker.DefaultExceptionMappingService; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import com.google.common.base.Throwables; +import com.google.common.util.concurrent.Uninterruptibles; import synapticloop.b2.Action; import synapticloop.b2.exception.B2ApiException; @@ -69,6 +82,7 @@ public class B2ObjectListService implements ListService { @Override public AttributedList list(final Path directory, final ListProgressListener listener) throws BackgroundException { + final ThreadPool pool = ThreadPoolFactory.get("list", HostPreferencesFactory.get(session.getHost()).getInteger("b2.listing.concurrency")); try { final AttributedList objects = new AttributedList<>(); Marker marker = new Marker(null, null); @@ -93,7 +107,20 @@ public class B2ObjectListService implements ListService { this.createPrefix(directory), String.valueOf(Path.DELIMITER)); } - marker = this.parse(directory, objects, response, revisions); + final List> folders = new ArrayList<>(); + marker = this.parse(directory, objects, response, revisions, containerId, pool, folders); + for(Future f : folders) { + try { + objects.add(Uninterruptibles.getUninterruptibly(f)); + } + catch(ExecutionException e) { + log.warn("Listing versioned objects failed with execution failure {}", e.getMessage()); + for(Throwable cause : ExceptionUtils.getThrowableList(e)) { + Throwables.throwIfInstanceOf(cause, BackgroundException.class); + } + throw new DefaultExceptionMappingService().map(Throwables.getRootCause(e)); + } + } if(null == marker.nextFileId) { if(!response.getFiles().isEmpty()) { hasDirectoryPlaceholder = true; @@ -114,6 +141,9 @@ public class B2ObjectListService implements ListService { catch(IOException e) { throw new DefaultIOExceptionMappingService().map(e); } + finally { + pool.shutdown(false); + } } private String createPrefix(final Path directory) { @@ -122,7 +152,8 @@ public class B2ObjectListService implements ListService { } private Marker parse(final Path directory, final AttributedList objects, - final B2ListFilesResponse response, final Map revisions) { + final B2ListFilesResponse response, final Map revisions, + final String containerId, final ThreadPool pool, final List> folders) { final B2AttributesFinderFeature attr = new B2AttributesFinderFeature(session, fileid); for(B2FileInfoResponse info : response.getFiles()) { if(StringUtils.equals(PathNormalizer.name(info.getFileName()), B2PathContainerService.PLACEHOLDER)) { @@ -136,10 +167,15 @@ public class B2ObjectListService implements ListService { } if(StringUtils.isBlank(info.getFileId())) { // Common prefix - final Path placeholder = new Path(directory.isDirectory() ? directory : directory.getParent(), - PathNormalizer.name(StringUtils.removeEnd(info.getFileName(), String.valueOf(Path.DELIMITER))), - EnumSet.of(Path.Type.directory, Path.Type.placeholder)); - objects.add(placeholder); + if(versioning.isEnabled()) { + // Determine trashed state asynchronously by checking for live content beneath the prefix + folders.add(this.submit(pool, directory, containerId, info.getFileName())); + } + else { + objects.add(new Path(directory.isDirectory() ? directory : directory.getParent(), + PathNormalizer.name(StringUtils.removeEnd(info.getFileName(), String.valueOf(Path.DELIMITER))), + EnumSet.of(Path.Type.directory, Path.Type.placeholder))); + } continue; } final PathAttributes attributes = attr.toAttributes(info); @@ -164,6 +200,44 @@ public class B2ObjectListService implements ListService { return new Marker(response.getNextFileName(), response.getNextFileId()); } + /** + * Determine path from prefix. Path will have trashed attribute set when no live (non-hidden) content + * exists beneath the prefix. + * + * @param pool Thread pool to run task with + * @param directory The directory for which contents are listed + * @param containerId B2 bucket ID + * @param prefix Common prefix found in directory listing (ends with delimiter) + * @return Path to add to directory list + */ + private Future submit(final ThreadPool pool, final Path directory, final String containerId, final String prefix) { + return pool.execute(new BackgroundExceptionCallable() { + @Override + public Path call() throws BackgroundException { + try { + final PathAttributes folderAttributes = new DefaultPathAttributes(); + // Query without delimiter to check recursively for any live files beneath this prefix. + // Hide markers are excluded by listFileNames, so an empty result means all content is deleted. + final B2ListFilesResponse liveContent = session.getClient().listFileNames( + containerId, null, 1, prefix, null); + if(liveContent.getFiles().isEmpty()) { + log.debug("Set trashed attribute for prefix {}", prefix); + folderAttributes.setTrashed(true); + } + return new Path(directory.isDirectory() ? directory : directory.getParent(), + PathNormalizer.name(StringUtils.removeEnd(prefix, String.valueOf(Path.DELIMITER))), + EnumSet.of(Path.Type.directory, Path.Type.placeholder), folderAttributes); + } + catch(B2ApiException e) { + throw new B2ExceptionMappingService(fileid).map("Listing directory {0} failed", e, directory); + } + catch(IOException e) { + throw new DefaultIOExceptionMappingService().map(e); + } + } + }); + } + private static final class Marker { public final String nextFilename; public final String nextFileId; diff --git a/backblaze/src/test/java/ch/cyberduck/core/b2/B2ObjectListServiceTest.java b/backblaze/src/test/java/ch/cyberduck/core/b2/B2ObjectListServiceTest.java index 07393f64bf..1002d2c3a8 100644 --- a/backblaze/src/test/java/ch/cyberduck/core/b2/B2ObjectListServiceTest.java +++ b/backblaze/src/test/java/ch/cyberduck/core/b2/B2ObjectListServiceTest.java @@ -412,9 +412,13 @@ public class B2ObjectListServiceTest extends AbstractB2Test { } // Nullify version to add delete marker new B2DeleteFeature(session, fileid).delete(Collections.singletonList(file.withAttributes(new DefaultPathAttributes(file.attributes()).setVersionId(null))), LoginCallback.noop, new Delete.DisabledCallback()); - assertTrue(new DefaultFindFeature(session).find(folder1, new DisabledListProgressListener())); - assertTrue(new B2ObjectListService(session, fileid).list(folder1, new DisabledListProgressListener()).contains(folder2)); - assertTrue(new DefaultFindFeature(session).find(folder2, new DisabledListProgressListener())); + assertFalse(new DefaultFindFeature(session).find(folder1, new DisabledListProgressListener())); + final AttributedList list = new B2ObjectListService(session, fileid).list(folder1, new DisabledListProgressListener()); + assertTrue(list.contains(folder2)); + for(Path f : list) { + assertTrue(f.attributes().isTrashed()); + } + assertFalse(new DefaultFindFeature(session).find(folder2, new DisabledListProgressListener())); assertEquals(2, new B2ObjectListService(session, fileid).list(folder2, new DisabledListProgressListener()).size()); assertThrows(NotfoundException.class, () -> new B2ObjectListService(session, fileid, 1, VersioningConfiguration.empty()).list(folder2, new DisabledListProgressListener())); for(Path f : new B2ObjectListService(session, fileid).list(folder2, new DisabledListProgressListener())) { @@ -438,6 +442,79 @@ public class B2ObjectListServiceTest extends AbstractB2Test { new B2DeleteFeature(session, fileid).delete(Arrays.asList(file1, folder1, bucket), LoginCallback.noop, new Delete.DisabledCallback()); } + @Test + public void testListFolderCreateModifyDelete() throws Exception { + final B2VersionIdProvider fileid = new B2VersionIdProvider(session); + final Path bucket = new B2DirectoryFeature(session, fileid).mkdir( + new B2WriteFeature(session, fileid), + new Path(String.format("test-%s", new AsciiRandomStringService().random()), EnumSet.of(Path.Type.directory, Path.Type.volume)), + new TransferStatus()); + // Create folder + final Path folder = new B2DirectoryFeature(session, fileid).mkdir( + new B2WriteFeature(session, fileid), + new Path(bucket, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.directory)), + new TransferStatus()); + // Create file in folder + final Path file = new Path(folder, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file)); + { + final byte[] content = RandomUtils.nextBytes(32); + final TransferStatus status = new TransferStatus().setLength(content.length); + status.setChecksum(new SHA1ChecksumCompute().compute(new ByteArrayInputStream(content), status)); + final HttpResponseOutputStream out = new B2WriteFeature(session, fileid).write(file, status, ConnectionCallback.noop); + IOUtils.write(content, out); + out.close(); + file.attributes().setVersionId(((B2FileResponse) out.getStatus()).getFileId()); + } + // Versioned listing: file and folder appear (1 version each) + assertTrue(new B2ObjectListService(session, fileid, 10, new VersioningConfiguration(true)).list(folder, new DisabledListProgressListener()).contains(file)); + assertTrue(new B2ObjectListService(session, fileid, 10, new VersioningConfiguration(true)).list(bucket, new DisabledListProgressListener()).contains(folder)); + // Modify file (overwrite with new content) + { + final byte[] content = RandomUtils.nextBytes(64); + final TransferStatus status = new TransferStatus().setLength(content.length); + status.setChecksum(new SHA1ChecksumCompute().compute(new ByteArrayInputStream(content), status)); + final HttpResponseOutputStream out = new B2WriteFeature(session, fileid).write(file, status, ConnectionCallback.noop); + IOUtils.write(content, out); + out.close(); + file.attributes().setVersionId(((B2FileResponse) out.getStatus()).getFileId()); + } + // Versioned listing: current version + previous duplicate visible + { + final AttributedList list = new B2ObjectListService(session, fileid, 10, new VersioningConfiguration(true)).list(folder, new DisabledListProgressListener()); + assertEquals(2, list.size()); + assertTrue(list.contains(file)); + assertNull(list.find(new SimplePathPredicate(file)).attributes().getRevision()); + assertEquals(Long.valueOf(1L), list.find(path -> path.attributes().isDuplicate()).attributes().getRevision()); + } + // Delete file: add hide marker by nullifying version + new B2DeleteFeature(session, fileid).delete( + Collections.singletonList(new Path(file).withAttributes(new DefaultPathAttributes(file.attributes()).setVersionId(null))), + LoginCallback.noop, new Delete.DisabledCallback()); + // Delete folder placeholder + new B2DeleteFeature(session, fileid).delete( + Collections.singletonList(folder), LoginCallback.noop, new Delete.DisabledCallback()); + // Versioned listing: all file versions are trashed or duplicate (hide marker present, no live entries) + final AttributedList versions = new B2ObjectListService(session, fileid, 10, new VersioningConfiguration(true)).list(folder, new DisabledListProgressListener()); + assertFalse(versions.isEmpty()); + for(Path f : versions) { + assertTrue(f.attributes().isTrashed() || f.attributes().isDuplicate()); + } + // Versioned bucket listing: folder appears as trashed (no live content beneath prefix) + { + final AttributedList bucketVersioned = new B2ObjectListService(session, fileid, 10, new VersioningConfiguration(true)).list(bucket, new DisabledListProgressListener()); + assertNotNull(bucketVersioned.find(new SimplePathPredicate(folder))); + assertTrue(bucketVersioned.find(new SimplePathPredicate(folder)).attributes().isTrashed()); + } + // Non-versioned listing: folder not accessible, file not accessible + assertTrue(new B2ObjectListService(session, fileid, 10, VersioningConfiguration.empty()).list(bucket, new DisabledListProgressListener()).isEmpty()); + assertThrows(NotfoundException.class, () -> new B2ObjectListService(session, fileid, 10, VersioningConfiguration.empty()).list(folder, new DisabledListProgressListener())); + // Cleanup + for(Path f : versions) { + new B2DeleteFeature(session, fileid).delete(Collections.singletonList(f), LoginCallback.noop, new Delete.DisabledCallback()); + } + new B2DeleteFeature(session, fileid).delete(Collections.singletonList(bucket), LoginCallback.noop, new Delete.DisabledCallback()); + } + @Test public void testListLexicographicSortOrderAssumption() throws Exception { final B2VersionIdProvider fileid = new B2VersionIdProvider(session); diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java index 8c10d55d0e..10e8cc4696 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java @@ -19,7 +19,6 @@ import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Read; -import ch.cyberduck.core.features.VersionIdProvider; import ch.cyberduck.core.preferences.HostPreferencesFactory; import ch.cyberduck.core.transfer.TransferStatus; @@ -33,12 +32,10 @@ public class CteraDelegatingReadFeature implements Read { private static final Logger log = LogManager.getLogger(CteraDelegatingReadFeature.class); private final CteraSession session; - private final VersionIdProvider versionid; private final boolean directio; - public CteraDelegatingReadFeature(final CteraSession session, final VersionIdProvider versionid) { + public CteraDelegatingReadFeature(final CteraSession session) { this.session = session; - this.versionid = versionid; this.directio = HostPreferencesFactory.get(session.getHost()).getBoolean("ctera.download.directio.enable"); } diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java index 4e38a7cb8a..97dc95adc5 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java @@ -277,7 +277,7 @@ public class CteraSession extends DAVSession { return (T) new CteraListService(this); } if(type == Read.class) { - return (T) new CteraDelegatingReadFeature(this, versionid); + return (T) new CteraDelegatingReadFeature(this); } if(type == Write.class) { return (T) new CteraWriteFeature(this); diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/README.md b/ctera/src/main/java/ch/cyberduck/core/ctera/README.md index 208e6c756a..24adc15c03 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/README.md +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/README.md @@ -84,12 +84,12 @@ N.B. no need to check `readpermission` upon mv/cp. | ACL (CTERA) | POSIX (Folder) | POSIX (File) | Windows `FileSystemRights` (Folder) | Windows `FileSystemRights` (File) | Example (Folder) | Example (File) | |----------------------------------------------------------------------------------------|-----------------------------------------------------|--------------------------------------|-----------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------|--------------------------------------------------------------| -| - | `---` | - | empty | - | `/ACL test (new user)/NoAccess/` | - | -| `readpermission` | `r-x` | `r--` | `ReadAndExecute` | `Read` | `/ACL test (new user)/ReadOnly/` | `/ACL test (new user)/ReadOnly/ReadOnly.txt` | -| `readpermission`, `createdirectoriespermission` | `rwx` (delete prevented in preflight) | - | `ReadAndExecute`, `CreateDirectories`, `CreateFiles` (!), | - | `/WORM test (new user)/Retention Folder (no write, no delete)/` | - | -| `readpermission`, `deletepermission` | `rwx` (folder/file creation prevented in preflight) | `rw-` (write prevented in preflight) | `ReadAndExecute`, `Delete` | `Read`, `Delete` | `/ACL test (new user)/NoCreateFolderPermission` | `/ACL test (new user)/NoCreateFolderPermission/trayIcon.png` | -| `readpermission`, `deletepermission`, `writepermission` | - | `rwx` | - | `Read`, `Delete`, `Write` | - | `/ACL test (new user)/ReadWrite/Free Access.txt` | -| `readpermission`, `deletepermission`, `writepermission`, `createdirectoriespermission` | `rwx` | - | `ReadAndExecute`, `Delete`, `Write` | - | `/ACL test (new user)/ReadWrite/` | - | +| - | `---` | - | empty | - | `/ACL test/NoAccess/` | - | +| `readpermission` | `r-x` | `r--` | `ReadAndExecute` | `Read` | `/ACL test/ReadOnly/` | `/ACL test/ReadOnly/ReadOnly.txt` | +| `readpermission`, `createdirectoriespermission` | `rwx` (delete prevented in preflight) | - | `ReadAndExecute`, `CreateDirectories`, `CreateFiles` (!), | - | `/WORM test/Retention Folder (no write, no delete)/` | - | +| `readpermission`, `deletepermission` | `rwx` (folder/file creation prevented in preflight) | `rw-` (write prevented in preflight) | `ReadAndExecute`, `Delete` | `Read`, `Delete` | `/ACL test/NoCreateFolderPermission` | `/ACL test/NoCreateFolderPermission/trayIcon.png` | +| `readpermission`, `deletepermission`, `writepermission` | - | `rwx` | - | `Read`, `Delete`, `Write` | - | `/ACL test/ReadWrite/Free Access.txt` | +| `readpermission`, `deletepermission`, `writepermission`, `createdirectoriespermission` | `rwx` | - | `ReadAndExecute`, `Delete`, `Write` | - | `/ACL test/ReadWrite/` | - | #### References diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java deleted file mode 100644 index d6fa155210..0000000000 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package ch.cyberduck.core.ctera; - -/* - * Copyright (c) 2002-2022 iterate GmbH. All rights reserved. - * https://cyberduck.io/ - * - * 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 3 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. - */ - -import ch.cyberduck.core.Credentials; -import ch.cyberduck.core.Host; -import ch.cyberduck.core.HostKeyCallback; -import ch.cyberduck.core.LoginCallback; -import ch.cyberduck.core.LoginConnectionService; -import ch.cyberduck.core.ProgressListener; -import ch.cyberduck.core.proxy.DisabledProxyFinder; -import ch.cyberduck.core.ssl.DefaultX509KeyManager; -import ch.cyberduck.core.ssl.DisabledX509TrustManager; -import ch.cyberduck.core.threading.CancelCallback; -import ch.cyberduck.test.VaultTest; - -import org.junit.After; -import org.junit.Before; - -public class AbstractCteraDirectIOTest extends VaultTest { - - protected CteraSession session; - private TestPasswordStore keychain; - - @After - public void disconnect() throws Exception { - session.close(); - keychain.save(session.getHost()); - } - - @Before - public void setup() throws Exception { - final Host host = new Host(new CteraProtocol(), "dcdirect.ctera.me", new Credentials(PROPERTIES.get("ctera.directio.user"))) { - @Override - public String getProperty(final String key) { - if("ctera.download.directio.enable".equals(key)) { - return String.valueOf(true); - } - return super.getProperty(key); - } - }; - host.setDefaultPath("/ServicesPortal/webdav/My Files"); - keychain = new TestPasswordStore(); - session = new CteraSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager(), keychain); - final LoginConnectionService connect = new LoginConnectionService(LoginCallback.noop, HostKeyCallback.noop, - keychain, ProgressListener.noop, new DisabledProxyFinder()); - connect.check(session, CancelCallback.noop); - } -} diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraTest.java index aaac4b6459..4412965d7e 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraTest.java @@ -43,7 +43,15 @@ public class AbstractCteraTest extends VaultTest { @Before public void setup() throws Exception { - final Host host = new Host(new CteraProtocol(), "driveconnect.ctera.me", new Credentials(PROPERTIES.get("ctera.user"))); + final Host host = new Host(new CteraProtocol(), PROPERTIES.get("ctera.hostname"), new Credentials(PROPERTIES.get("ctera.user"))) { + @Override + public String getProperty(final String key) { + if("ctera.download.directio.enable".equals(key)) { + return String.valueOf(true); + } + return super.getProperty(key); + } + }; host.setDefaultPath("/ServicesPortal/webdav/My Files"); session = new CteraSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager()); final LoginConnectionService login = new LoginConnectionService(new DisabledLoginCallback() { diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraAttributesFinderFeatureTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraAttributesFinderFeatureTest.java index a8c6061bf6..4e3856fb9c 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraAttributesFinderFeatureTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraAttributesFinderFeatureTest.java @@ -108,7 +108,7 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testNoAccessAcl() throws Exception { - final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test", EnumSet.of(AbstractPath.Type.directory)); // list parent folder to inspect attributes final List noAccess = new CteraListService(session).propfind(home).stream().filter(r -> r.getName().equals("NoAccess")).collect(Collectors.toList()); @@ -119,8 +119,8 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { assertEquals("false", resource.getCustomProps().get(READPERMISSION.getName())); assertEquals("false", resource.getCustomProps().get(DELETEPERMISSION.getName())); assertEquals("false", resource.getCustomProps().get(CREATEDIRECTORIESPERMISSION.getName())); - assertEquals("bb64b3a4-399e-45d0-95af-43f1ace6e250:105620641", resource.getCustomProps().get(CTERA_GUID)); - assertEquals("105620644", resource.getCustomProps().get(CTERA_FILEID)); + assertEquals("05c75d64-bb1e-4be8-8ec3-19370247dfec:913", resource.getCustomProps().get(CTERA_GUID)); + assertEquals("47836", resource.getCustomProps().get(CTERA_FILEID)); assertEquals(new Acl(new Acl.CanonicalUser()), new CteraAttributesFinderFeature(session).toAttributes(resource).getAcl()); // find fails with 403 in backend final AccessDeniedException findException = assertThrows(AccessDeniedException.class, () -> new CteraAttributesFinderFeature(session).find(new Path(home, "NoAccess", EnumSet.of(AbstractPath.Type.directory)))); @@ -133,10 +133,12 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testNoDeleteAcl() throws Exception { - final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test", EnumSet.of(AbstractPath.Type.directory)); final Path folder = new Path(home, "NoDelete", EnumSet.of(AbstractPath.Type.directory)); final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); - assertEquals(new Acl(new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION)), folderAcl); + assertEquals(new Acl(new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION), + new Acl.UserAndRole(new Acl.CanonicalUser(), WRITEPERMISSION), + new Acl.UserAndRole(new Acl.CanonicalUser(), CREATEDIRECTORIESPERMISSION)), folderAcl); final Path file = new Path(folder, "RW no delete.txt", EnumSet.of(AbstractPath.Type.file)); final Acl fileAcl = new CteraAttributesFinderFeature(session).find(file).getAcl(); @@ -145,7 +147,7 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testReadOnlyAcl() throws Exception { - final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test", EnumSet.of(AbstractPath.Type.directory)); final Path folder = new Path(home, "ReadOnly", EnumSet.of(AbstractPath.Type.directory)); final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); assertEquals(new Acl(new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION)), folderAcl); @@ -157,7 +159,7 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testNoCreateFolderAcl() throws Exception { - final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test", EnumSet.of(AbstractPath.Type.directory)); final Path folder = new Path(home, "NoCreateFolderPermission", EnumSet.of(AbstractPath.Type.directory)); final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); assertEquals(new Acl( @@ -176,7 +178,7 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testReadWriteAcl() throws Exception { - final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path home = new Path("/ServicesPortal/webdav/Shared With Me/ACL test", EnumSet.of(AbstractPath.Type.directory)); final Path folder = new Path(home, "ReadWrite", EnumSet.of(AbstractPath.Type.directory)); final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); assertEquals(new Acl( @@ -206,15 +208,7 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { @Test public void testWORMAcl() throws Exception { final Path home = new Path("/ServicesPortal/webdav/Shared With Me", EnumSet.of(AbstractPath.Type.directory)); - final Path folder = new Path(home, "WORM test (new user)", EnumSet.of(AbstractPath.Type.directory)); - final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); - assertEquals(new Acl( - new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), WRITEPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), DELETEPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), CREATEDIRECTORIESPERMISSION) - ), folderAcl); - + final Path folder = new Path(home, "WORM test", EnumSet.of(AbstractPath.Type.directory)); final Path subfolder = new Path(folder, "Retention Folder (no write, no delete)", EnumSet.of(AbstractPath.Type.directory)); final Acl subfolderAcl = new CteraAttributesFinderFeature(session).find(subfolder).getAcl(); assertEquals(new Acl( @@ -227,26 +221,14 @@ public class CteraAttributesFinderFeatureTest extends AbstractCteraTest { assertEquals(new Acl( new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION) ), fileAcl); - - final Path emptySubfolder = new Path(folder, "Empty WORM folder", EnumSet.of(AbstractPath.Type.directory)); - final Acl emptySubfolderAcl = new CteraAttributesFinderFeature(session).find(emptySubfolder).getAcl(); - assertEquals(new Acl( - new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), CREATEDIRECTORIESPERMISSION) - ), emptySubfolderAcl); } @Test public void testWORMNoRetentionAcl() throws Exception { final Path home = new Path("/ServicesPortal/webdav/Shared With Me", EnumSet.of(AbstractPath.Type.directory)); - final Path folder = new Path(home, "WORM-NoRetention(Delete allowed) (new user)", EnumSet.of(AbstractPath.Type.directory)); + final Path folder = new Path(home, "WORM-NoRetention(Delete allowed)", EnumSet.of(AbstractPath.Type.directory)); final Acl folderAcl = new CteraAttributesFinderFeature(session).find(folder).getAcl(); - assertEquals(new Acl( - new Acl.UserAndRole(new Acl.CanonicalUser(), READPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), WRITEPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), DELETEPERMISSION), - new Acl.UserAndRole(new Acl.CanonicalUser(), CREATEDIRECTORIESPERMISSION) - ), folderAcl); + assertEquals(Acl.EMPTY, folderAcl); final Path file = new Path(folder, "WORM-DeleteAllowed.txt", EnumSet.of(AbstractPath.Type.file)); final Acl fileAcle = new CteraAttributesFinderFeature(session).find(file).getAcl(); diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraConcurrentTransferWorkerTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraConcurrentTransferWorkerTest.java index e3c1fefd93..41d1a16040 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraConcurrentTransferWorkerTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraConcurrentTransferWorkerTest.java @@ -58,7 +58,7 @@ import java.util.EnumSet; import static org.junit.Assert.*; @Category(IntegrationTest.class) -public class CteraConcurrentTransferWorkerTest extends AbstractCteraDirectIOTest { +public class CteraConcurrentTransferWorkerTest extends AbstractCteraTest { @Test public void testBelowSegmentSizeUpAndDownload() throws Exception { diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDeleteFeatureTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDeleteFeatureTest.java index 275ba047af..2342df4add 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDeleteFeatureTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDeleteFeatureTest.java @@ -5,11 +5,8 @@ import ch.cyberduck.core.AlphanumericRandomStringService; import ch.cyberduck.core.LoginCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.dav.DAVFindFeature; -import ch.cyberduck.core.dav.DAVLockFeature; import ch.cyberduck.core.exception.AccessDeniedException; -import ch.cyberduck.core.exception.InteroperabilityException; import ch.cyberduck.core.exception.NotfoundException; -import ch.cyberduck.core.exception.RetriableAccessDeniedException; import ch.cyberduck.core.features.Delete; import ch.cyberduck.core.shared.DefaultHomeFinderService; import ch.cyberduck.core.transfer.TransferStatus; @@ -37,22 +34,6 @@ public class CteraDeleteFeatureTest extends AbstractCteraTest { assertFalse(new DAVFindFeature(session).find(test)); } - @Test(expected = RetriableAccessDeniedException.class) - public void testDeleteFileWithLock() throws Exception { - final Path test = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)); - new CteraTouchFeature(session).touch(new CteraWriteFeature(session), test, new TransferStatus()); - String lock = null; - try { - lock = new DAVLockFeature(session).lock(test); - } - catch(InteroperabilityException e) { - // Not supported - } - assertTrue(new DAVFindFeature(session).find(test)); - new CteraDeleteFeature(session).delete(Collections.singletonMap(test, new TransferStatus().setLockId(lock)), LoginCallback.noop, new Delete.DisabledCallback()); - assertFalse(new DAVFindFeature(session).find(test)); - } - @Test public void testDeleteDirectory() throws Exception { final Path test = new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory)); diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java index 4067eb5400..ad928f030b 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java @@ -50,7 +50,7 @@ import java.util.EnumSet; import static org.junit.Assert.*; @Category(IntegrationTest.class) -public class CteraDirectIOReadFeatureTest extends AbstractCteraDirectIOTest { +public class CteraDirectIOReadFeatureTest extends AbstractCteraTest { @Test public void testReadSingleChunk() throws Exception { diff --git a/defaults/src/main/resources/default.properties b/defaults/src/main/resources/default.properties index 88adba1019..a867337819 100644 --- a/defaults/src/main/resources/default.properties +++ b/defaults/src/main/resources/default.properties @@ -449,6 +449,7 @@ googledrive.delete.multiple.partition=50 b2.bucket.acl.default=allPrivate b2.listing.chunksize=1000 +b2.listing.concurrency=25 b2.listing.versioning.enable=true b2.upload.checksum.verify=true b2.upload.largeobject.auto=true diff --git a/pom.xml b/pom.xml index 59b1103d59..70d1399f53 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ io.swagger.core.v3 swagger-annotations - 2.2.49 + 2.2.50 org.glassfish.jersey.core @@ -741,7 +741,7 @@ io.swagger.codegen.v3 swagger-codegen-maven-plugin - 3.0.79 + 3.0.80 src/main/java diff --git a/ssh/pom.xml b/ssh/pom.xml index 146cfc072a..96d6c72f00 100644 --- a/ssh/pom.xml +++ b/ssh/pom.xml @@ -85,12 +85,6 @@ ${project.version} test - - org.cryptomator - cryptofs - 2.9.0 - test - org.apache.sshd sshd-sftp diff --git a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java deleted file mode 100644 index 89078df2a1..0000000000 --- a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java +++ /dev/null @@ -1,240 +0,0 @@ -package ch.cyberduck.core.cryptomator; - -/* - * Copyright (c) 2002-2017 iterate GmbH. All rights reserved. - * https://cyberduck.io/ - * - * 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 3 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. - */ - -import ch.cyberduck.core.AlphanumericRandomStringService; -import ch.cyberduck.core.ConnectionCallback; -import ch.cyberduck.core.Credentials; -import ch.cyberduck.core.Host; -import ch.cyberduck.core.HostKeyCallback; -import ch.cyberduck.core.LoginCallback; -import ch.cyberduck.core.PasswordCallback; -import ch.cyberduck.core.Path; -import ch.cyberduck.core.cryptomator.features.CryptoReadFeature; -import ch.cyberduck.core.cryptomator.impl.v8.CryptomatorVault; -import ch.cyberduck.core.cryptomator.impl.v8.MasterkeyVaultMetadataProvider; -import ch.cyberduck.core.proxy.DisabledProxyFinder; -import ch.cyberduck.core.sftp.SFTPHomeDirectoryService; -import ch.cyberduck.core.sftp.SFTPProtocol; -import ch.cyberduck.core.sftp.SFTPReadFeature; -import ch.cyberduck.core.sftp.SFTPSession; -import ch.cyberduck.core.ssl.DefaultX509KeyManager; -import ch.cyberduck.core.ssl.DisabledX509TrustManager; -import ch.cyberduck.core.threading.CancelCallback; -import ch.cyberduck.core.transfer.TransferStatus; -import ch.cyberduck.core.vault.DefaultVaultRegistry; -import ch.cyberduck.core.vault.VaultCredentials; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.RandomUtils; -import org.apache.commons.text.RandomStringGenerator; -import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; -import org.apache.sshd.server.SshServer; -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; -import org.apache.sshd.sftp.server.SftpSubsystemFactory; -import org.cryptomator.cryptofs.CryptoFileSystem; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.Collections; -import java.util.EnumSet; -import java.util.concurrent.ThreadLocalRandom; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import static org.junit.Assert.assertArrayEquals; - -public class SFTPCryptomatorInteroperabilityTest { - - private final int PORT_NUMBER = ThreadLocalRandom.current().nextInt(2000, 3000); - - private static SshServer server; - private CryptoFileSystem cryptoFileSystem; - private java.nio.file.Path tempDir; - private String passphrase; - - @Before - public void startSerer() throws Exception { - server = SshServer.setUpDefaultServer(); - server.setPort(PORT_NUMBER); - server.setPasswordAuthenticator((username, password, session) -> true); - server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider()); - server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); - tempDir = Files.createTempDirectory(String.format("%s-", this.getClass().getName())); - - unzipTestVault("/testvault.zip", tempDir.toString()); - - //TODO switch back to cryptofs based testing as soon as cryptofs is based on new cryptolib - /* - final java.nio.file.Path vault = tempDir.resolve("vault"); - Files.createDirectory(vault); - passphrase = new AlphanumericRandomStringService().random(); - final SecureRandom csprng; - switch(Factory.Platform.getDefault()) { - case windows: - csprng = ReseedingSecureRandom.create(SecureRandom.getInstanceStrong()); - break; - default: - csprng = FastSecureRandomProvider.get().provide(); - } - final PerpetualMasterkey mk = Masterkey.generate(csprng); - final MasterkeyFileAccess mkAccess = new MasterkeyFileAccess(PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8), csprng); - final java.nio.file.Path mkPath = Paths.get(vault.toString(), DefaultVaultRegistry.DEFAULT_MASTERKEY_FILE_NAME); - mkAccess.persist(mk, mkPath, passphrase); - CryptoFileSystemProperties properties = cryptoFileSystemProperties().withKeyLoader(new MasterkeyLoader() { - @Override - public Masterkey loadKey(final URI keyId) throws MasterkeyLoadingFailedException { - return mkAccess.load(mkPath, passphrase); - } - }) - .withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC) - .build(); - CryptoFileSystemProvider.initialize(vault, properties, URI.create("test:key")); - cryptoFileSystem = CryptoFileSystemProvider.newFileSystem(vault, properties); - */ - server.setFileSystemFactory(new VirtualFileSystemFactory(tempDir.toAbsolutePath())); - server.start(); - } - - @After - public void stop() throws Exception { - server.stop(); - FileUtils.deleteDirectory(tempDir.toFile()); - /* - cryptoFileSystem.close(); - FileUtils.deleteDirectory(cryptoFileSystem.getPathToVault().getParent().toFile()); - */ - } - - private void unzipTestVault(final String zip, final String target) throws Exception { - try(InputStream is = this.getClass().getResourceAsStream(zip); - ZipInputStream zipIn = new ZipInputStream(is)) { - - java.nio.file.Path targetDir = Paths.get(target); - Files.createDirectories(targetDir); - - ZipEntry entry; - while((entry = zipIn.getNextEntry()) != null) { - java.nio.file.Path filePath = targetDir.resolve(entry.getName()); - System.out.println(filePath.toString()); - - if(entry.isDirectory()) { - Files.createDirectories(filePath); - } - else { - Files.createDirectories(filePath.getParent()); - Files.copy(zipIn, filePath, StandardCopyOption.REPLACE_EXISTING); - } - zipIn.closeEntry(); - } - } - } - - /** - * Create file/folder with Cryptomator, read with Cyberduck - */ - @Ignore(value = "Need a Cryptofs version that is based on the new Cryptolib") - @Test(expected = CryptoInvalidFilenameException.class) - public void testCryptomatorInteroperabilityLongFilename() throws Exception { - // create folder - final java.nio.file.Path targetFolder = cryptoFileSystem.getPath("/", new AlphanumericRandomStringService().random()); - Files.createDirectory(targetFolder); - // create file and write some random content - java.nio.file.Path targetFile = targetFolder.resolve(new RandomStringGenerator.Builder().build().generate(220)); - final byte[] content = RandomUtils.nextBytes(48768); - Files.write(targetFile, content); - - // read with Cyberduck and compare - final Host host = new Host(new SFTPProtocol(), "localhost", PORT_NUMBER, new Credentials("empty", "empty")); - final SFTPSession session = new SFTPSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager()); - session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop); - session.login(LoginCallback.noop, CancelCallback.noop); - final Path home = new SFTPHomeDirectoryService(session).find(); - final Path vaultPath = new Path(home, "vault", EnumSet.of(Path.Type.directory)); - final AbstractVault cryptomator = new CryptomatorVault(vaultPath); - cryptomator.load(session, new MasterkeyVaultMetadataProvider(new VaultCredentials("12341234"))); - session.withRegistry(new DefaultVaultRegistry(PasswordCallback.noop, cryptomator)); - Path p = new Path(new Path(vaultPath, targetFolder.getFileName().toString(), EnumSet.of(Path.Type.directory)), targetFile.getFileName().toString(), EnumSet.of(Path.Type.file)); - final InputStream read = new CryptoReadFeature(session, new SFTPReadFeature(session), cryptomator).read(p, new TransferStatus(), ConnectionCallback.noop); - final byte[] readContent = new byte[content.length]; - IOUtils.readFully(read, readContent); - assertArrayEquals(content, readContent); - } - - /** - * Read Cryptomator generated vault with long file and folder names - */ - @Test - public void testCryptomatorInteroperabilityLongFileAndFoldername() throws Exception { - - - // read with Cyberduck and compare - final Host host = new Host(new SFTPProtocol(), "localhost", PORT_NUMBER, new Credentials("empty", "empty")); - final SFTPSession session = new SFTPSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager()); - session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop); - session.login(LoginCallback.noop, CancelCallback.noop); - final Path vaultPath = new SFTPHomeDirectoryService(session).find(); - final AbstractVault cryptomator = new CryptomatorVault(vaultPath); - cryptomator.load(session, new MasterkeyVaultMetadataProvider(new VaultCredentials("12341234"))); - session.withRegistry(new DefaultVaultRegistry(PasswordCallback.noop, cryptomator)); - -/* - Path p = new Path(new Path(vaultPath, targetFolder.getFileName().toString(), EnumSet.of(Path.Type.directory)), targetFile.getFileName().toString(), EnumSet.of(Path.Type.file)); - final InputStream read = new CryptoReadFeature(session, new SFTPReadFeature(session), cryptomator).read(p, new TransferStatus(), ConnectionCallback.noop); - final byte[] readContent = new byte[content.length]; - IOUtils.readFully(read, readContent); - assertArrayEquals(content, readContent);*/ - } - - - /** - * Create long file/folder with Cryptomator, read with Cyberduck - */ - @Ignore(value = "Need a Cryptofs version that is based on the new Cryptolib") - @Test - public void testCryptomatorInteroperability() throws Exception { - // create folder - final java.nio.file.Path targetFolder = cryptoFileSystem.getPath("/", new AlphanumericRandomStringService().random()); - Files.createDirectory(targetFolder); - // create file and write some random content - java.nio.file.Path targetFile = targetFolder.resolve(new AlphanumericRandomStringService().random()); - final byte[] content = RandomUtils.nextBytes(20); - Files.write(targetFile, content); - - // read with Cyberduck and compare - final Host host = new Host(new SFTPProtocol(), "localhost", PORT_NUMBER, new Credentials("empty", "empty")); - final SFTPSession session = new SFTPSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager()); - session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop); - session.login(LoginCallback.noop, CancelCallback.noop); - final Path home = new SFTPHomeDirectoryService(session).find(); - final Path vaultPath = new Path(home, "vault", EnumSet.of(Path.Type.directory)); - final AbstractVault cryptomator = new CryptomatorVault(vaultPath); - cryptomator.load(session, new MasterkeyVaultMetadataProvider(new VaultCredentials("12341234"))); - session.withRegistry(new DefaultVaultRegistry(PasswordCallback.noop, cryptomator)); - Path p = new Path(new Path(vaultPath, targetFolder.getFileName().toString(), EnumSet.of(Path.Type.directory)), targetFile.getFileName().toString(), EnumSet.of(Path.Type.file)); - final InputStream read = new CryptoReadFeature(session, new SFTPReadFeature(session), cryptomator).read(p, new TransferStatus(), ConnectionCallback.noop); - final byte[] readContent = new byte[content.length]; - IOUtils.readFully(read, readContent); - assertArrayEquals(content, readContent); - } -} diff --git a/www/update/changelog.html b/www/update/changelog.html index 887b4b379f..87414d5e6b 100755 --- a/www/update/changelog.html +++ b/www/update/changelog.html @@ -134,6 +134,8 @@
  • Bugfix Support "Include" directive when reading from OpenSSH config (SFTP) (#10451)
  • +
  • Bugfix Exclude trashed folders in list by default (Backblaze B2) (##18101