diff --git a/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java b/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java index bac9e50abd..1d90a7a333 100644 --- a/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java +++ b/backblaze/src/main/java/ch/cyberduck/core/b2/B2WriteFeature.java @@ -78,7 +78,7 @@ public class B2WriteFeature extends AbstractHttpWriteFeature imp try { final Checksum checksum = status.getChecksum(); if(status.isSegment()) { - final B2GetUploadPartUrlResponse uploadUrl = session.getClient().getUploadPartUrl(status.getParameters().get("fileId")); + final B2GetUploadPartUrlResponse uploadUrl = session.getClient().getUploadPartUrl(status.getParameters().get("fileId").toString()); return session.getClient().uploadLargeFilePart(uploadUrl, status.getPart(), entity, checksum.hash); } else { diff --git a/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java b/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java index d939542098..6592c8be2f 100644 --- a/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java +++ b/box/src/main/java/ch/cyberduck/core/box/BoxWriteFeature.java @@ -185,8 +185,8 @@ public class BoxWriteFeature extends AbstractHttpWriteFeature { final HttpRange range = HttpRange.withStatus(new TransferStatus() .setLength(status.getLength()) .setOffset(status.getOffset())); - final String uploadSessionId = status.getParameters().get(BoxLargeUploadService.UPLOAD_SESSION_ID); - final String overall_length = status.getParameters().get(BoxLargeUploadService.OVERALL_LENGTH); + final String uploadSessionId = status.getParameters().get(BoxLargeUploadService.UPLOAD_SESSION_ID).toString(); + final String overall_length = status.getParameters().get(BoxLargeUploadService.OVERALL_LENGTH).toString(); log.debug("Send range {} for file {}", range, file); final HttpPut request = new HttpPut(String.format("%s/files/upload_sessions/%s", client.getBasePath(), uploadSessionId)); // Must not overlap with the range of a part already uploaded this session. diff --git a/core/src/main/java/ch/cyberduck/core/transfer/TransferStatus.java b/core/src/main/java/ch/cyberduck/core/transfer/TransferStatus.java index eca46f9e60..829a71913a 100644 --- a/core/src/main/java/ch/cyberduck/core/transfer/TransferStatus.java +++ b/core/src/main/java/ch/cyberduck/core/transfer/TransferStatus.java @@ -160,7 +160,7 @@ public class TransferStatus implements TransferResponse, StreamCancelation, Stre private Long modified; private Long created; - private Map parameters + private Map parameters = Collections.emptyMap(); private Map metadata @@ -522,11 +522,11 @@ public class TransferStatus implements TransferResponse, StreamCancelation, Stre return this; } - public Map getParameters() { + public Map getParameters() { return parameters; } - public TransferStatus setParameters(final Map parameters) { + public TransferStatus setParameters(final Map parameters) { this.parameters = parameters; return this; } diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraBulkFeature.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraBulkFeature.java index 47e8be80d8..aa66611c67 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraBulkFeature.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraBulkFeature.java @@ -1,7 +1,7 @@ package ch.cyberduck.core.ctera; /* - * Copyright (c) 2002-2025 iterate GmbH. All rights reserved. + * Copyright (c) 2002-2026 iterate GmbH. All rights reserved. * https://cyberduck.io/ * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.ctera.model.DirectIO; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.VersionIdProvider; +import ch.cyberduck.core.http.HttpExceptionMappingService; import ch.cyberduck.core.shared.DisabledBulkFeature; import ch.cyberduck.core.transfer.Transfer; import ch.cyberduck.core.transfer.TransferItem; @@ -33,8 +34,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.util.HashMap; -import java.util.List; +import java.util.Collections; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; @@ -42,6 +42,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class CteraBulkFeature extends DisabledBulkFeature { private static final Logger log = LogManager.getLogger(CteraBulkFeature.class); + public static final String DIRECTIO_PARAMETER = "directio"; + private final CteraSession session; private final VersionIdProvider versionid; @@ -57,46 +59,11 @@ public class CteraBulkFeature extends DisabledBulkFeature { break; case download: for(Map.Entry file : files.entrySet()) { - final DirectIO metadata; - try { - metadata = this.getMetadata(file.getKey().remote); - } - catch(IOException e) { - log.warn("Ignore DirectIO download failure {} for {}", e, file.getKey().remote); - continue; - } + final DirectIO metadata = this.getMetadata(file.getKey().remote); + log.debug("DirectIO metadata {} retrieved for {}", metadata, file.getKey().remote); final TransferStatus status = file.getValue(); - if(status.isSegmented()) { - final List segments = status.getSegments(); - if(segments.size() <= metadata.chunks.size()) { - for(int i = 0; i < segments.size(); i++) { - final TransferStatus segment = segments.get(i); - if(i == 0) { - if(segment.getOffset() > 0) { - log.warn("DirectIO download for {} with an initial offset is not supported", file.getKey().remote); - continue; - } - } - segment.setUrl(metadata.chunks.get(i).url); - final Map parameters = new HashMap<>(segment.getParameters()); - parameters.put(CteraDirectIOReadFeature.CTERA_WRAPPEDKEY, metadata.encrypt_info.wrapped_key); - segment.setParameters(parameters); - } - } - else { - log.error("Mismatch between number of segments ({}) and chunks ({}) for {}", - segments.size(), metadata.chunks.size(), file.getKey().remote); - } - } - else { - if(metadata.actual_blocks_range.file_size == 0) { - final Map parameters = new HashMap<>(status.getParameters()); - parameters.put(CteraDirectIOReadFeature.CTERA_WRAPPEDKEY, metadata.encrypt_info.wrapped_key); - status.setParameters(parameters); - } - else { - log.warn("DirectIO download for {} with an initial offset is not supported", file.getKey().remote); - } + for(TransferStatus segment : status.getSegments()) { + segment.setParameters(Collections.singletonMap(DIRECTIO_PARAMETER, metadata)); } } break; @@ -104,17 +71,20 @@ public class CteraBulkFeature extends DisabledBulkFeature { return files; } - private DirectIO getMetadata(final Path file) throws IOException, BackgroundException { + private DirectIO getMetadata(final Path file) throws BackgroundException { final HttpGet request = new HttpGet(String.format("%s%s%s", new HostUrlProvider().withUsername(false).withPath(false) .get(session.getHost()), CteraDirectIOInterceptor.DIRECTIO_PATH, versionid.getVersionId(file))); - final DirectIO metadata = session.getClient().getClient().execute(request, new AbstractResponseHandler() { - @Override - public DirectIO handleEntity(final HttpEntity entity) throws IOException { - final ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(entity.getContent(), DirectIO.class); - } - }); - log.debug("DirectIO metadata {} retrieved for {}", metadata, file); - return metadata; + try { + return session.getClient().getClient().execute(request, new AbstractResponseHandler() { + @Override + public DirectIO handleEntity(final HttpEntity entity) throws IOException { + final ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(entity.getContent(), DirectIO.class); + } + }); + } + catch(IOException e) { + throw new HttpExceptionMappingService().map("Download {0} failed", e, file); + } } } 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 75b95fccbf..8c10d55d0e 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDelegatingReadFeature.java @@ -19,9 +19,10 @@ 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; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,32 +33,50 @@ 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) { + public CteraDelegatingReadFeature(final CteraSession session, final VersionIdProvider versionid) { this.session = session; + this.versionid = versionid; + this.directio = HostPreferencesFactory.get(session.getHost()).getBoolean("ctera.download.directio.enable"); } @Override public InputStream read(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { - if(StringUtils.isNotBlank(status.getParameters().get(CteraDirectIOReadFeature.CTERA_WRAPPEDKEY))) { - return new CteraDirectIOReadFeature(session).read(file, status, callback); + if(directio) { + try { + return new CteraDirectIOReadFeature(session).read(file, status, callback); + } + catch(BackgroundException e) { + log.warn("Ignore DirectIO retrieval failure {} for {}", e, file); + } } - log.warn("No key material found in status {} for {}", status, file); return new CteraReadFeature(session).read(file, status, callback); } @Override public boolean offset(final Path file) throws BackgroundException { + if(directio) { + return new CteraDirectIOReadFeature(session).offset(file); + } return new CteraReadFeature(session).offset(file); } @Override public void preflight(final Path file) throws BackgroundException { + if(directio) { + new CteraDirectIOReadFeature(session).preflight(file); + return; + } new CteraReadFeature(session).preflight(file); } @Override public EnumSet features(final Path file) { + if(directio) { + return new CteraDirectIOReadFeature(session).features(file); + } return new CteraReadFeature(session).features(file); } } diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeature.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeature.java index 4e7b160e52..8b2cbd37fa 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeature.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeature.java @@ -18,7 +18,6 @@ package ch.cyberduck.core.ctera; import ch.cyberduck.core.ConnectionCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.ctera.directio.DirectIOInputStream; -import ch.cyberduck.core.ctera.directio.EncryptInfo; import ch.cyberduck.core.ctera.model.DirectIO; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Read; @@ -27,7 +26,6 @@ import ch.cyberduck.core.http.HttpMethodReleaseInputStream; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -35,15 +33,15 @@ import org.apache.logging.log4j.Logger; import java.io.IOException; import java.io.InputStream; import java.util.Collections; -import java.util.EnumSet; import java.util.Enumeration; import java.util.List; +import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.READPERMISSION; +import static ch.cyberduck.core.ctera.CteraAttributesFinderFeature.assumeRole; + public class CteraDirectIOReadFeature implements Read { private static final Logger log = LogManager.getLogger(CteraDirectIOReadFeature.class); - public static final String CTERA_WRAPPEDKEY = "wrapped_key"; - private final CteraSession session; public CteraDirectIOReadFeature(final CteraSession session) { @@ -53,15 +51,13 @@ public class CteraDirectIOReadFeature implements Read { @Override public InputStream read(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException { try { - final EncryptInfo key = new EncryptInfo(status.getParameters().get(CTERA_WRAPPEDKEY), session.getOrCreateAPIKeys().secretKey); + final DirectIO metadata = (DirectIO) status.getParameters().get(CteraBulkFeature.DIRECTIO_PARAMETER); + log.debug("DirectIO metadata {} retrieved for {}", metadata, file); + final String secretKey = session.getOrCreateAPIKeys().secretKey; if(status.getLength() == 0) { - return new ChunkSequenceInputStream(Collections.emptyList(), key); + return new ChunkSequenceInputStream(Collections.emptyList(), metadata.encrypt_info, secretKey, status.getOffset()); } - final DirectIO.Chunk chunk = new DirectIO.Chunk(); - chunk.url = status.getUrl(); - chunk.len = status.getLength(); - log.debug("Return chunk {} for file {}", chunk, file); - return new ChunkSequenceInputStream(Collections.singletonList(chunk), key); + return new ChunkSequenceInputStream(metadata.chunks, metadata.encrypt_info, secretKey, status.getOffset()); } catch(IOException e) { throw new HttpExceptionMappingService().map("Download {0} failed", e, file); @@ -69,43 +65,63 @@ public class CteraDirectIOReadFeature implements Read { } @Override - public EnumSet features(final Path file) { - return EnumSet.noneOf(Flags.class); + public void preflight(final Path file) throws BackgroundException { + assumeRole(file, READPERMISSION); } private final class ChunkSequenceInputStream extends InputStream { - private final Enumeration chunks; - private final EncryptInfo key; - private InputStream in; + private final DirectIO.EncryptInfo key; + private final String secretKey; + private final long offset; - public ChunkSequenceInputStream(final List chunks, final EncryptInfo key) throws IOException { + private InputStream in; + private long currentPosition = 0L; + + public ChunkSequenceInputStream(final List chunks, final DirectIO.EncryptInfo key, final String secretKey, final long offset) throws IOException { this.chunks = Collections.enumeration(chunks); this.key = key; - this.peekNextStream(); + this.secretKey = secretKey; + this.offset = offset; + this.peek(); } private void nextStream() throws IOException { if(in != null) { in.close(); } - this.peekNextStream(); + this.peek(); } - private void peekNextStream() throws IOException { - if(chunks.hasMoreElements()) { - in = getStream(chunks.nextElement()); + /** + * Peek at the next chunk in the sequence + */ + private void peek() throws IOException { + while(chunks.hasMoreElements()) { + final DirectIO.Chunk chunk = chunks.nextElement(); + final long chunkStart = currentPosition; + final long chunkEnd = currentPosition + chunk.len; + // Skip chunks that are entirely before the offset + if(chunkEnd <= offset) { + log.debug("Skipping chunk {} entirely before offset {}", chunk, offset); + currentPosition = chunkEnd; + continue; + } + log.debug("Request chunk {}", chunk); + // Open the stream for this chunk + in = new DirectIOInputStream(new HttpMethodReleaseInputStream( + session.getClient().getClient().execute(new HttpGet(chunk.url)), + new TransferStatus().setOffset(0L).setLength(chunk.len)), key, secretKey); + // If this chunk contains the offset, skip bytes before the offset + if(chunkStart < offset) { + final long bytesToSkip = offset - chunkStart; + log.debug("Skipping {} bytes in chunk {} to reach offset {}", bytesToSkip, chunk, offset); + IOUtils.skip(in, bytesToSkip); + } + currentPosition = chunkEnd; + return; } - else { - in = null; - } - } - - private InputStream getStream(final DirectIO.Chunk chunk) throws IOException { - log.debug("Request chunk {}", chunk); - final HttpGet chunkRequest = new HttpGet(chunk.url); - final HttpResponse chunkResponse = session.getClient().getClient().execute(chunkRequest); - return new DirectIOInputStream(new HttpMethodReleaseInputStream(chunkResponse, new TransferStatus()), key); + in = null; } @Override diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraProtocol.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraProtocol.java index 0dd0e4f139..eea093a65e 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraProtocol.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraProtocol.java @@ -26,9 +26,6 @@ import ch.cyberduck.core.synchronization.ComparisonService; import ch.cyberduck.core.synchronization.DefaultComparisonService; import ch.cyberduck.core.synchronization.ETagComparisonService; -import java.util.HashMap; -import java.util.Map; - import com.google.auto.service.AutoService; @AutoService(Protocol.class) @@ -36,7 +33,6 @@ public class CteraProtocol extends AbstractProtocol { public static final String CTERA_REDIRECT_URI = String.format("%s:websso", PreferencesFactory.get().getProperty("oauth.handler.scheme")); - private static final int DIRECTIO_CHUNKSIZE = 4194304; @Override public Type getType() { @@ -102,17 +98,6 @@ public class CteraProtocol extends AbstractProtocol { return "CTERA Token"; } - @Override - public Map getProperties() { - final Map properties = new HashMap<>(); - if(PreferencesFactory.get().getBoolean("ctera.download.directio.enable")) { - properties.put("queue.download.segments.size.dynamic", String.valueOf(false)); - properties.put("queue.download.segments.size", String.valueOf(DIRECTIO_CHUNKSIZE)); - properties.put("queue.download.segments.threshold", String.valueOf(0)); - } - return properties; - } - @Override @SuppressWarnings("unchecked") public T getFeature(final Class type) { 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 a917b1e536..4e38a7cb8a 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraSession.java @@ -181,10 +181,8 @@ public class CteraSession extends DAVSession { final HttpPost post = new HttpPost(API_PATH); try { final String userId = this.getPortalSession().getUserIdFromUserRef(); - post.setEntity( - new StringEntity(String.format("user-definedcreateApiKey%s", - userId), ContentType.TEXT_XML - ) + post.setEntity(new StringEntity(String.format("user-definedcreateApiKey%s", userId), + ContentType.TEXT_XML) ); final APICredentials credentials = this.getClient().execute(post, new AbstractResponseHandler() { @Override @@ -279,10 +277,7 @@ public class CteraSession extends DAVSession { return (T) new CteraListService(this); } if(type == Read.class) { - if(preferences.getBoolean("ctera.download.directio.enable")) { - return (T) new CteraDelegatingReadFeature(this); - } - return (T) new CteraReadFeature(this); + return (T) new CteraDelegatingReadFeature(this, versionid); } if(type == Write.class) { return (T) new CteraWriteFeature(this); diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/Decryptor.java b/ctera/src/main/java/ch/cyberduck/core/ctera/directio/Decryptor.java index 942cdbabbf..0447b67824 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/Decryptor.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/directio/Decryptor.java @@ -15,6 +15,8 @@ package ch.cyberduck.core.ctera.directio; * GNU General Public License for more details. */ +import ch.cyberduck.core.ctera.model.DirectIO; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; @@ -44,10 +46,10 @@ public class Decryptor { private static final byte[] GZIP_MAGIC = {0x1F, (byte) 0x8B}; private static final byte[] SNAPPY_MAGIC = {-126, 83, 78, 65, 80, 80, 89, 0}; - public InputStream decryptData(final InputStream blockData, final EncryptInfo encryptInfo) throws IOException { + public InputStream decryptData(final InputStream blockData, final DirectIO.EncryptInfo encryptInfo, final String secretKey) throws IOException { try { - final DecryptKey decryptKey = new DecryptKey(encryptInfo.getWrappedKey()); - decryptKey.decrypt(encryptInfo.getWrappingKey()); + final DecryptKey decryptKey = new DecryptKey(encryptInfo.wrapped_key); + decryptKey.decrypt(secretKey); final SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(decryptKey.getDecryptedKey()), ENCRYPTION_KEY_ALGORITHM); blockData.read(); final byte[] iv = new byte[16]; diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/DirectIOInputStream.java b/ctera/src/main/java/ch/cyberduck/core/ctera/directio/DirectIOInputStream.java index 8babc0270b..4ba785540c 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/DirectIOInputStream.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/directio/DirectIOInputStream.java @@ -15,6 +15,8 @@ package ch.cyberduck.core.ctera.directio; * GNU General Public License for more details. */ +import ch.cyberduck.core.ctera.model.DirectIO; + import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ProxyInputStream; @@ -23,14 +25,12 @@ import java.io.InputStream; public class DirectIOInputStream extends ProxyInputStream { - private InputStream decryptedInputStream; - private final Decryptor decryptor; - private final EncryptInfo encryptInfo; + private final InputStream decryptedInputStream; - public DirectIOInputStream(final InputStream proxy, final EncryptInfo encryptInfo) { + public DirectIOInputStream(final InputStream proxy, final DirectIO.EncryptInfo encryptInfo, final String secretKey) throws IOException { super(proxy); - this.decryptor = new Decryptor(); - this.encryptInfo = encryptInfo; + final Decryptor decryptor = new Decryptor(); + this.decryptedInputStream = decryptor.decryptData(in, encryptInfo, secretKey); } @Override @@ -46,23 +46,12 @@ public class DirectIOInputStream extends ProxyInputStream { @Override public int read(final byte[] dst, final int off, final int len) throws IOException { - this.initStream(); return decryptedInputStream.read(dst, off, len); } - private void initStream() throws IOException { - if(decryptedInputStream == null) { - this.readNextChunk(); - } - } - @Override public long skip(final long len) throws IOException { return IOUtils.skip(this, len); } - - private void readNextChunk() throws IOException { - decryptedInputStream = decryptor.decryptData(this.in, encryptInfo); - } } diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/EncryptInfo.java b/ctera/src/main/java/ch/cyberduck/core/ctera/directio/EncryptInfo.java deleted file mode 100644 index 56b1477083..0000000000 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/directio/EncryptInfo.java +++ /dev/null @@ -1,35 +0,0 @@ -package ch.cyberduck.core.ctera.directio; - -/* - * Copyright (c) 2002-2025 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. - */ - -public class EncryptInfo { - - public EncryptInfo(final String wrappedKey, final String wrappingKey) { - this.wrappedKey = wrappedKey; - this.wrappingKey = wrappingKey; - } - - private String wrappedKey; - private String wrappingKey; - - public String getWrappedKey() { - return wrappedKey; - } - - public String getWrappingKey() { - return wrappingKey; - } -} \ No newline at end of file diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/model/APICredentials.java b/ctera/src/main/java/ch/cyberduck/core/ctera/model/APICredentials.java index edd7522ab3..8ee6175443 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/model/APICredentials.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/model/APICredentials.java @@ -29,6 +29,5 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; public final class APICredentials { public String accessKey; - public String secretKey; } \ No newline at end of file diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/model/DirectIO.java b/ctera/src/main/java/ch/cyberduck/core/ctera/model/DirectIO.java index d9e2339477..6764fdafcf 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/model/DirectIO.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/model/DirectIO.java @@ -41,7 +41,6 @@ public final class DirectIO { } public static class EncryptInfo { - public String wrapped_key; public boolean data_encrypted; @@ -56,7 +55,6 @@ public final class DirectIO { } public static class ActualBlocksRange { - public long file_size; public String range; diff --git a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java b/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java index 6d71bd87cd..d6fa155210 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/AbstractCteraDirectIOTest.java @@ -21,7 +21,6 @@ import ch.cyberduck.core.HostKeyCallback; import ch.cyberduck.core.LoginCallback; import ch.cyberduck.core.LoginConnectionService; import ch.cyberduck.core.ProgressListener; -import ch.cyberduck.core.preferences.PreferencesFactory; import ch.cyberduck.core.proxy.DisabledProxyFinder; import ch.cyberduck.core.ssl.DefaultX509KeyManager; import ch.cyberduck.core.ssl.DisabledX509TrustManager; @@ -34,10 +33,12 @@ 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 @@ -52,10 +53,10 @@ public class AbstractCteraDirectIOTest extends VaultTest { } }; host.setDefaultPath("/ServicesPortal/webdav/My Files"); - PreferencesFactory.get().setDefault("ctera.download.directio.enable", String.valueOf(true)); - session = new CteraSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager(), new TestPasswordStore()); + keychain = new TestPasswordStore(); + session = new CteraSession(host, new DisabledX509TrustManager(), new DefaultX509KeyManager(), keychain); final LoginConnectionService connect = new LoginConnectionService(LoginCallback.noop, HostKeyCallback.noop, - new TestPasswordStore(), ProgressListener.noop, new DisabledProxyFinder()); + keychain, ProgressListener.noop, new DisabledProxyFinder()); connect.check(session, CancelCallback.noop); } } 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 3958017e75..4067eb5400 100644 --- a/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java +++ b/ctera/src/test/java/ch/cyberduck/core/ctera/CteraDirectIOReadFeatureTest.java @@ -36,6 +36,7 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.test.IntegrationTest; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.RandomUtils; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -52,7 +53,7 @@ import static org.junit.Assert.*; public class CteraDirectIOReadFeatureTest extends AbstractCteraDirectIOTest { @Test - public void testReadChunk() throws Exception { + public void testReadSingleChunk() throws Exception { final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); final byte[] content = RandomUtils.nextBytes(65536); @@ -67,10 +68,8 @@ public class CteraDirectIOReadFeatureTest extends AbstractCteraDirectIOTest { final TransferStatus status = new TransferStatus(); final TransferStatus segment = new TransferStatus().setSegment(true).setLength(content.length); status.setSegments(Collections.singletonList(segment)); - final CteraBulkFeature bulk = new CteraBulkFeature(session, new DefaultVersionIdProvider(session)); - bulk.pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test), status), ConnectionCallback.noop); - assertNotNull(segment.getUrl()); - assertNotNull(segment.getParameters()); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); assertNotNull(in); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); @@ -80,6 +79,174 @@ public class CteraDirectIOReadFeatureTest extends AbstractCteraDirectIOTest { new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); } + @Test + public void testReadSingleChunkWithOffset() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(65536); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setLength(content.length - 1).setOffset(1L); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(ArrayUtils.subarray(content, 1, content.length), buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + + @Test + public void testReadSingleChunkEqualDefaultChunksize() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(4194304); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setLength(content.length); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(content, buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + + @Test + public void testReadSingleChunkEqualDefaultChunksizeWithOffset() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(4194304); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setLength(1L).setOffset(content.length - 1L); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(ArrayUtils.subarray(content, content.length - 1, content.length), buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + + @Test + public void testReadMultipleChunkSizeAligned() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(4194304 * 2); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setLength(content.length); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(content, buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + + @Test + public void testReadMultipleChunkSize() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(8388609); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setLength(content.length); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(content, buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + + @Test + public void testReadMultipleChunkSizeWithOffset() throws Exception { + final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); + final Local local = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random()); + final byte[] content = RandomUtils.nextBytes(8388609); + final OutputStream out = local.getOutputStream(false); + assertNotNull(out); + IOUtils.write(content, out); + out.close(); + new DAVUploadFeature(session).upload( + new CteraWriteFeature(session), test, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), ProgressListener.noop, StreamListener.noop, + new TransferStatus().setLength(content.length), + ConnectionCallback.noop); + final TransferStatus status = new TransferStatus(); + final TransferStatus segment = new TransferStatus().setSegment(true).setOffset(4194304L).setLength(content.length - 4194304); + status.setSegments(Collections.singletonList(segment)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); + final InputStream in = new CteraDirectIOReadFeature(session).read(test, segment, ConnectionCallback.noop); + assertNotNull(in); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(content.length); + new StreamCopier(segment, segment).transfer(in, buffer); + in.close(); + assertArrayEquals(ArrayUtils.subarray(content, 4194304, content.length), buffer.toByteArray()); + new CteraDeleteFeature(session).delete(Collections.singletonList(test), LoginCallback.noop, new Delete.DisabledCallback()); + } + @Test public void testReadZeroByteFile() throws Exception { final Path test = new CteraTouchFeature(session).touch(new CteraWriteFeature(session), new Path(new DefaultHomeFinderService(session).find(), new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus()); @@ -95,10 +262,8 @@ public class CteraDirectIOReadFeatureTest extends AbstractCteraDirectIOTest { ConnectionCallback.noop); final TransferStatus status = new TransferStatus().setLength(content.length); status.setSegments(Collections.emptyList()); - final CteraBulkFeature bulk = new CteraBulkFeature(session, new DefaultVersionIdProvider(session)); - bulk.pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test), status), ConnectionCallback.noop); - assertNull(status.getUrl()); - assertNotNull(status.getParameters().get(CteraDirectIOReadFeature.CTERA_WRAPPEDKEY)); + final DefaultVersionIdProvider versionid = new DefaultVersionIdProvider(session); + new CteraBulkFeature(session, versionid).pre(Transfer.Type.download, Collections.singletonMap(new TransferItem(test, local), status), ConnectionCallback.noop); assertTrue(new DAVFindFeature(session).find(test)); final PathAttributes attributes = new CteraAttributesFinderFeature(session).find(test); assertEquals(content.length, attributes.getSize()); diff --git a/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java b/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java index 355a453132..90f86e4c69 100644 --- a/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java +++ b/eue/src/main/java/ch/cyberduck/core/eue/EueWriteFeature.java @@ -83,7 +83,7 @@ public class EueWriteFeature extends AbstractHttpWriteFeature stream = this.write(file, status, new DelayedHttpEntityCallable(file) { diff --git a/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java b/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java index 7161776fae..420041bdc5 100644 --- a/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java +++ b/s3/src/main/java/ch/cyberduck/core/s3/S3WriteFeature.java @@ -52,6 +52,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class S3WriteFeature extends AbstractHttpWriteFeature implements Write { private static final Logger log = LogManager.getLogger(S3WriteFeature.class); @@ -77,7 +78,9 @@ public class S3WriteFeature extends AbstractHttpWriteFeature impl final RequestEntityRestStorageService client = session.getClient(); final Path bucket = containerService.getContainer(file); client.putObjectWithRequestEntityImpl( - bucket.isRoot() ? StringUtils.EMPTY : bucket.getName(), object, entity, status.getParameters()); + bucket.isRoot() ? StringUtils.EMPTY : bucket.getName(), object, entity, + status.getParameters().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()))); log.debug("Saved object {} with checksum {}", file, object.getETag()); } catch(ServiceException e) { diff --git a/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraBulkService.java b/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraBulkService.java index 9ba684268e..d4390ac9c4 100644 --- a/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraBulkService.java +++ b/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraBulkService.java @@ -156,7 +156,7 @@ public class SpectraBulkService implements Bulk> { for(Map.Entry item : files.entrySet()) { if(container.getKey().equals(containerService.getContainer(item.getKey().remote))) { final TransferStatus status = item.getValue(); - final Map parameters = new HashMap<>(status.getParameters()); + final Map parameters = new HashMap<>(status.getParameters()); parameters.put(REQUEST_PARAMETER_JOBID_IDENTIFIER, master.getJobId().toString()); status.setParameters(parameters); status.setPart(counters.get(containerService.getKey(item.getKey().remote))); @@ -191,7 +191,7 @@ public class SpectraBulkService implements Bulk> { if(!status.getParameters().containsKey(REQUEST_PARAMETER_JOBID_IDENTIFIER)) { throw new NotfoundException(String.format("Missing job id parameter in status for %s", file.getName())); } - final String job = status.getParameters().get(REQUEST_PARAMETER_JOBID_IDENTIFIER); + final String job = status.getParameters().get(REQUEST_PARAMETER_JOBID_IDENTIFIER).toString(); log.debug("Cancel job {}", job); final Ds3Client client = new SpectraClientBuilder().wrap(session, session.getHost()); client.cancelJobSpectraS3(new CancelJobSpectraS3Request(job)); @@ -224,7 +224,7 @@ public class SpectraBulkService implements Bulk> { if(!status.getParameters().containsKey(REQUEST_PARAMETER_JOBID_IDENTIFIER)) { throw new NotfoundException(String.format("Missing job id parameter in status for %s", file.getName())); } - final String job = status.getParameters().get(REQUEST_PARAMETER_JOBID_IDENTIFIER); + final String job = status.getParameters().get(REQUEST_PARAMETER_JOBID_IDENTIFIER).toString(); log.debug("Query status for job {}", job); // Fetch current list from server final Ds3Client client = new SpectraClientBuilder().wrap(session, session.getHost()); @@ -305,7 +305,7 @@ public class SpectraBulkService implements Bulk> { chunk.setLength(object.getLength()); chunk.setOffset(object.getOffset()); // Job parameter already present from #pre - final Map parameters = new HashMap<>(chunk.getParameters()); + final Map parameters = new HashMap<>(chunk.getParameters()); // Set offset for chunk. parameters.put(REQUEST_PARAMETER_OFFSET, Long.toString(chunk.getOffset())); chunk.setParameters(parameters); diff --git a/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraReadFeature.java b/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraReadFeature.java index 6d190516b5..861861bd93 100644 --- a/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraReadFeature.java +++ b/spectra/src/main/java/ch/cyberduck/core/spectra/SpectraReadFeature.java @@ -34,6 +34,8 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class SpectraReadFeature implements Read { @@ -64,19 +66,20 @@ public class SpectraReadFeature implements Read { public InputStream open() throws IOException { try { return session.getClient().getObjectImpl( - false, - containerService.getContainer(file).getName(), - containerService.getKey(file), - null, - null, - null, - null, - null, - null, - file.attributes().getVersionId(), - new HashMap(), - chunk.getParameters()) - .getDataInputStream(); + false, + containerService.getContainer(file).getName(), + containerService.getKey(file), + null, + null, + null, + null, + null, + null, + file.attributes().getVersionId(), + new HashMap<>(), + chunk.getParameters().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()))) + .getDataInputStream(); } catch(ServiceException e) { throw new IOException(e.getMessage(), e); diff --git a/tus/src/main/java/ch/cyberduck/core/tus/TusWriteFeature.java b/tus/src/main/java/ch/cyberduck/core/tus/TusWriteFeature.java index c35f3ec9b1..c41962d3bc 100644 --- a/tus/src/main/java/ch/cyberduck/core/tus/TusWriteFeature.java +++ b/tus/src/main/java/ch/cyberduck/core/tus/TusWriteFeature.java @@ -64,7 +64,7 @@ public class TusWriteFeature extends AbstractHttpWriteFeature { final DelayedHttpEntityCallable command = new DelayedHttpEntityCallable(file) { @Override public Void call(final HttpEntity entity) throws BackgroundException { - final HttpPatch request = new HttpPatch(status.getParameters().get(TusUploadFeature.UPLOAD_URL)); + final HttpPatch request = new HttpPatch(status.getParameters().get(TusUploadFeature.UPLOAD_URL).toString()); request.setEntity(entity); request.setHeader(TUS_HEADER_RESUMABLE, TUS_VERSION); final Checksum checksum = status.getChecksum(); diff --git a/webdav/src/main/java/ch/cyberduck/core/dav/DAVReadFeature.java b/webdav/src/main/java/ch/cyberduck/core/dav/DAVReadFeature.java index db388a58a2..7b42f382d4 100644 --- a/webdav/src/main/java/ch/cyberduck/core/dav/DAVReadFeature.java +++ b/webdav/src/main/java/ch/cyberduck/core/dav/DAVReadFeature.java @@ -117,13 +117,13 @@ public class DAVReadFeature implements Read { if(!status.getParameters().isEmpty()) { resource.append("?"); } - for(Map.Entry parameter : status.getParameters().entrySet()) { + for(Map.Entry parameter : status.getParameters().entrySet()) { if(!resource.toString().endsWith("?")) { resource.append("&"); } resource.append(URIEncoder.encode(parameter.getKey())) .append("=") - .append(URIEncoder.encode(parameter.getValue())); + .append(URIEncoder.encode(parameter.getValue().toString())); } return new HttpGet(resource.toString());