mirror of
https://github.com/iterate-ch/cyberduck.git
synced 2026-05-26 19:10:49 +00:00
Merge remote-tracking branch 'origin/master' into feature/10-ikvmnet-cli
This commit is contained in:
@@ -32,7 +32,7 @@ jobs:
|
||||
if: runner.os == 'windows' || runner.os == 'macos'
|
||||
- run: msiexec /i setup\wix\Bonjour64.msi /Quiet /Passive /NoRestart
|
||||
if: runner.os == 'windows'
|
||||
- uses: microsoft/setup-msbuild@v2
|
||||
- uses: microsoft/setup-msbuild@v3
|
||||
if: runner.os == 'windows'
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
cache: maven
|
||||
- name: Add msbuild to PATH
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
- name: "NuGet"
|
||||
|
||||
@@ -78,7 +78,7 @@ public class B2WriteFeature extends AbstractHttpWriteFeature<BaseB2Response> 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 {
|
||||
|
||||
@@ -30,6 +30,7 @@ import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.ProviderHelpServiceFactory;
|
||||
import ch.cyberduck.core.local.BrowserLauncherFactory;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.rococoa.Foundation;
|
||||
@@ -86,17 +87,20 @@ public abstract class WindowController extends BundleController implements NSWin
|
||||
|
||||
public void setWindow(final NSWindow window) {
|
||||
this.window = window;
|
||||
this.window.setFrameAutosaveName(StringUtils.EMPTY);
|
||||
this.window.recalculateKeyViewLoop();
|
||||
this.window.setReleasedWhenClosed(true);
|
||||
this.window.setDelegate(this.id());
|
||||
this.window.setCollectionBehavior(window.collectionBehavior()
|
||||
| NSWindow.NSWindowCollectionBehavior.NSWindowCollectionBehaviorTransient);
|
||||
}
|
||||
|
||||
public NSWindow window() {
|
||||
return window;
|
||||
}
|
||||
|
||||
public String windowFrameName() {
|
||||
return window.title();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NSView view() {
|
||||
return window.contentView();
|
||||
@@ -106,16 +110,22 @@ public abstract class WindowController extends BundleController implements NSWin
|
||||
* Order front window
|
||||
*/
|
||||
public void display() {
|
||||
this.display(true);
|
||||
this.display(true, this.isVisible() ? null : this.windowFrameName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Order front window
|
||||
*
|
||||
* @param key Make key window
|
||||
* @param key Make key window
|
||||
* @param frameName Property name
|
||||
*/
|
||||
public void display(final boolean key) {
|
||||
public void display(final boolean key, final String frameName) {
|
||||
this.loadBundle();
|
||||
if(frameName != null) {
|
||||
if(window.setFrameUsingName(frameName)) {
|
||||
log.debug("Restored frame {} for window {}", window.frame(), frameName);
|
||||
}
|
||||
}
|
||||
if(key) {
|
||||
window.makeKeyAndOrderFront(null);
|
||||
}
|
||||
@@ -202,6 +212,14 @@ public abstract class WindowController extends BundleController implements NSWin
|
||||
@Delegate
|
||||
public void windowWillClose(final NSNotification notification) {
|
||||
window.endEditingFor(null);
|
||||
// Save frame rectangle
|
||||
final String frameName = this.windowFrameName();
|
||||
if(frameName != null) {
|
||||
log.debug("Save frame {} for window {}", window.frame(), frameName);
|
||||
window.saveFrameUsingName(frameName);
|
||||
}
|
||||
// Workaround for macOS 26.x bug where closed windows leave an invisible rect that intercepts mouse events
|
||||
window.setFrame_display_animate(new NSRect(new NSPoint(0, 0), new NSSize(0, 0)), false, false);
|
||||
log.debug("Window will close {}", notification);
|
||||
for(WindowListener listener : listeners.toArray(new WindowListener[listeners.size()])) {
|
||||
listener.windowWillClose();
|
||||
|
||||
@@ -935,8 +935,7 @@ public abstract class NSWindow extends NSResponder {
|
||||
public abstract boolean isDocumentEdited();
|
||||
|
||||
/**
|
||||
* Original signature : <code>BOOL isVisible()</code><br>
|
||||
* <i>native declaration : :361</i>
|
||||
* @return A Boolean value that indicates whether the window is visible onscreen (even when it’s obscured by other windows).
|
||||
*/
|
||||
public abstract boolean isVisible();
|
||||
|
||||
@@ -1335,21 +1334,18 @@ public abstract class NSWindow extends NSResponder {
|
||||
public abstract void setFrameFromString(String string);
|
||||
|
||||
/**
|
||||
* Original signature : <code>void saveFrameUsingName(NSString*)</code><br>
|
||||
* <i>native declaration : :467</i>
|
||||
* Saves the window’s frame rectangle in the user defaults system under a given name.
|
||||
* With the companion method setFrameUsingName(_:), you can save and reset an NSWindow object’s frame over various launches of an application.
|
||||
*
|
||||
* @param name The name under which the frame is to be saved.
|
||||
*/
|
||||
public abstract void saveFrameUsingName(String name);
|
||||
|
||||
/**
|
||||
* Set force=YES to use setFrameUsingName on a non-resizable window<br> Original signature : <code>BOOL
|
||||
* setFrameUsingName(NSString*, BOOL)</code><br>
|
||||
* <i>native declaration : :469</i>
|
||||
*/
|
||||
public abstract boolean setFrameUsingName_force(String name, boolean force);
|
||||
|
||||
/**
|
||||
* Original signature : <code>BOOL setFrameUsingName(NSString*)</code><br>
|
||||
* <i>native declaration : :470</i>
|
||||
* Sets the window’s frame rectangle by reading the rectangle data stored under a given name from the defaults system.
|
||||
* The frame is constrained according to the window’s minimum and maximum size settings. This method causes a windowWillResize(_:to:) message to be sent to the delegate
|
||||
*
|
||||
* @param name The name of the frame to read.
|
||||
*/
|
||||
public abstract boolean setFrameUsingName(String name);
|
||||
|
||||
@@ -1418,6 +1414,7 @@ public abstract class NSWindow extends NSResponder {
|
||||
|
||||
/**
|
||||
* Sets the size of the window’s content view to a given size, which is expressed in the window’s base coordinate system.
|
||||
*
|
||||
* @param size The new size of the window’s content view in the window’s base coordinate system.
|
||||
*/
|
||||
public abstract void setContentSize(NSSize size);
|
||||
|
||||
@@ -185,8 +185,8 @@ public class BoxWriteFeature extends AbstractHttpWriteFeature<File> {
|
||||
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.
|
||||
|
||||
@@ -18,7 +18,6 @@ package ch.cyberduck.cli;
|
||||
* feedback@cyberduck.io
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.DeserializerFactory;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.Profile;
|
||||
import ch.cyberduck.core.ProtocolFactory;
|
||||
@@ -50,7 +49,7 @@ public class CommandLinePathParserTest {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Arrays.asList(new FTPTLSProtocol(), new S3Protocol())));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/FTP.cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/FTPS.cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile")));
|
||||
|
||||
assertEquals(new Path("/", EnumSet.of(Path.Type.directory)),
|
||||
new CommandLinePathParser(input, factory).parse("ftps://u@test.cyberduck.ch/"));
|
||||
|
||||
@@ -51,7 +51,7 @@ public class CommandLineUriParserTest {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new LinkedHashSet<>(Arrays.asList(new FTPTLSProtocol(), new S3Protocol())));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/FTP.cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/FTPS.cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile")));
|
||||
factory.register(new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile")));
|
||||
assertEquals(0, new Host(new S3Protocol(), "s3.amazonaws.com", 443, "/cyberduck-test", new Credentials("AWS456", null))
|
||||
.compareTo(new CommandLineUriParser(input, factory).parse("s3:AWS456@cyberduck-test/key")));
|
||||
assertEquals(0, new Host(new S3Protocol(), "s3.amazonaws.com", 443, "/cyberduck-test", new Credentials("AWS456", null))
|
||||
|
||||
@@ -158,7 +158,7 @@ public class FinderLocal extends Local {
|
||||
}
|
||||
}
|
||||
if(null == bookmark) {
|
||||
log.warn("No security scoped bookmark for {}", path);
|
||||
log.debug("No security scoped bookmark for {}", path);
|
||||
return null;
|
||||
}
|
||||
log.debug("Lock with bookmark {}", bookmark);
|
||||
|
||||
@@ -15,11 +15,7 @@ package ch.cyberduck.core.exception;
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
public class SSLNegotiateException extends InteroperabilityException {
|
||||
public SSLNegotiateException(final String detail) {
|
||||
super(detail);
|
||||
}
|
||||
|
||||
public class SSLNegotiateException extends ConnectionRefusedException {
|
||||
public SSLNegotiateException(final String detail, final Throwable cause) {
|
||||
super(detail, cause);
|
||||
}
|
||||
|
||||
@@ -327,10 +327,18 @@ public abstract class Preferences implements Locales, PreferencesReader {
|
||||
this.configureLogging(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset logging configuration to default level from configuration
|
||||
*/
|
||||
public void resetLogging() {
|
||||
this.deleteProperty("logging");
|
||||
this.configureLogging(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconfigure logging configuration
|
||||
*
|
||||
* @param level Log level
|
||||
* @param level Log level or null to use default level from configuration
|
||||
*/
|
||||
protected void configureLogging(final String level) {
|
||||
// Call only once during initialization time of your application
|
||||
@@ -346,10 +354,13 @@ public abstract class Preferences implements Locales, PreferencesReader {
|
||||
}
|
||||
catch(IOException e) {
|
||||
log.error("Failure configuring log4j", e);
|
||||
Configurator.setRootLevel(Level.ERROR);
|
||||
}
|
||||
}
|
||||
// Allow to override default logging level
|
||||
Configurator.setRootLevel(Level.toLevel(level, Level.ERROR));
|
||||
if(StringUtils.isNotEmpty(level)) {
|
||||
// Allow to override default logging level
|
||||
Configurator.setRootLevel(Level.toLevel(level, Level.ERROR));
|
||||
}
|
||||
// Map logging level to pass through bridge
|
||||
final ImmutableMap<Level, java.util.logging.Level> map = new ImmutableMap.Builder<Level, java.util.logging.Level>()
|
||||
.put(Level.ALL, java.util.logging.Level.ALL)
|
||||
@@ -369,7 +380,7 @@ public abstract class Preferences implements Locales, PreferencesReader {
|
||||
java.util.logging.Logger.getLogger(loggerConfig.getName()).setLevel(map.get(loggerConfig.getLevel()));
|
||||
}
|
||||
}
|
||||
this.configureAppenders(level);
|
||||
this.configureAppenders(LogManager.getRootLogger().getLevel().name());
|
||||
}
|
||||
|
||||
private InputStream getLogConfiguration() {
|
||||
|
||||
@@ -22,7 +22,7 @@ import ch.cyberduck.core.AbstractExceptionMappingService;
|
||||
import ch.cyberduck.core.DefaultSocketExceptionMappingService;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.exception.ConnectionCanceledException;
|
||||
import ch.cyberduck.core.exception.InteroperabilityException;
|
||||
import ch.cyberduck.core.exception.ConnectionRefusedException;
|
||||
import ch.cyberduck.core.exception.SSLNegotiateException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -98,10 +98,10 @@ public class SSLExceptionMappingService extends AbstractExceptionMappingService<
|
||||
}
|
||||
if(ExceptionUtils.getRootCause(failure) instanceof GeneralSecurityException) {
|
||||
this.append(buffer, ExceptionUtils.getRootCause(failure).getMessage());
|
||||
return new InteroperabilityException(buffer.toString(), failure);
|
||||
return new ConnectionRefusedException(buffer.toString(), failure);
|
||||
}
|
||||
this.append(buffer, message);
|
||||
return new InteroperabilityException(buffer.toString(), failure);
|
||||
return new ConnectionRefusedException(buffer.toString(), failure);
|
||||
}
|
||||
|
||||
private enum Alert {
|
||||
|
||||
@@ -29,7 +29,6 @@ import ch.cyberduck.core.exception.LocalNotfoundException;
|
||||
import ch.cyberduck.core.exception.LoginFailureException;
|
||||
import ch.cyberduck.core.exception.QuotaException;
|
||||
import ch.cyberduck.core.exception.ResolveFailedException;
|
||||
import ch.cyberduck.core.exception.SSLNegotiateException;
|
||||
import ch.cyberduck.core.exception.TransferCanceledException;
|
||||
import ch.cyberduck.core.exception.UnsupportedException;
|
||||
import ch.cyberduck.core.io.IOResumeException;
|
||||
@@ -78,9 +77,6 @@ public final class DefaultFailureDiagnostics implements FailureDiagnostics<Backg
|
||||
if(cause instanceof ConnectionRefusedException) {
|
||||
return Type.network;
|
||||
}
|
||||
if(cause instanceof SSLNegotiateException) {
|
||||
return Type.application;
|
||||
}
|
||||
if(cause instanceof SSLHandshakeException) {
|
||||
return Type.application;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public class TransferStatus implements TransferResponse, StreamCancelation, Stre
|
||||
private Long modified;
|
||||
private Long created;
|
||||
|
||||
private Map<String, String> parameters
|
||||
private Map<String, ?> parameters
|
||||
= Collections.emptyMap();
|
||||
|
||||
private Map<String, String> metadata
|
||||
@@ -522,11 +522,11 @@ public class TransferStatus implements TransferResponse, StreamCancelation, Stre
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getParameters() {
|
||||
public Map<String, ?> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public TransferStatus setParameters(final Map<String, String> parameters) {
|
||||
public TransferStatus setParameters(final Map<String, ?> parameters) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ public class VaultRegistryCopyFeature implements Copy {
|
||||
private final Copy proxy;
|
||||
private final VaultRegistry registry;
|
||||
|
||||
|
||||
public VaultRegistryCopyFeature(final Session<?> session, final Copy proxy, final VaultRegistry registry) {
|
||||
this.session = session;
|
||||
this.destination = session;
|
||||
|
||||
@@ -31,7 +31,6 @@ public class VaultRegistryDirectoryFeature<Reply> implements Directory<Reply> {
|
||||
private final VaultRegistry registry;
|
||||
|
||||
public VaultRegistryDirectoryFeature(final Session<?> session, final Directory<Reply> proxy, final VaultRegistry registry) {
|
||||
|
||||
this.session = session;
|
||||
this.proxy = proxy;
|
||||
this.registry = registry;
|
||||
|
||||
@@ -19,23 +19,34 @@ package ch.cyberduck.core.ssl;
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.exception.ConnectionRefusedException;
|
||||
import ch.cyberduck.core.exception.SSLNegotiateException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.net.SocketException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class SSLExceptionMappingServiceTest {
|
||||
|
||||
@Test
|
||||
public void testMap() {
|
||||
public void testMapSocketException() {
|
||||
final BackgroundException f = new SSLExceptionMappingService().map(new SSLException(
|
||||
"Connection has been shutdown: javax.net.ssl.SSLException: java.net.SocketException: Broken pipe",
|
||||
new SSLException("javax.net.ssl.SSLException: java.net.SocketException: Broken pipe",
|
||||
new SocketException("Broken pipe"))));
|
||||
assertSame(ConnectionRefusedException.class, f.getClass());
|
||||
assertEquals("Connection failed", f.getMessage());
|
||||
assertEquals("Broken pipe. The connection attempt was rejected. The server may be down, or your network may not be properly configured.", f.getDetail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapSSLFailure() {
|
||||
final BackgroundException f = new SSLExceptionMappingService().map(new SSLHandshakeException("r"));
|
||||
assertSame(SSLNegotiateException.class, f.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TransferItem, TransferStatus> 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<TransferStatus> 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<String, String> 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<String, String> 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<DirectIO>() {
|
||||
@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<DirectIO>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Flags> features(final Path file) {
|
||||
if(directio) {
|
||||
return new CteraDirectIOReadFeature(session).features(file);
|
||||
}
|
||||
return new CteraReadFeature(session).features(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Flags> 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<DirectIO.Chunk> 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<DirectIO.Chunk> chunks, final EncryptInfo key) throws IOException {
|
||||
private InputStream in;
|
||||
private long currentPosition = 0L;
|
||||
|
||||
public ChunkSequenceInputStream(final List<DirectIO.Chunk> 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
|
||||
|
||||
@@ -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<String, String> getProperties() {
|
||||
final Map<String, String> 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> T getFeature(final Class<T> type) {
|
||||
|
||||
@@ -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("<obj><att id=\"type\"><val>user-defined</val></att><att id=\"name\"><val>createApiKey</val></att><att id=\"param\"><val>%s</val></att></obj>",
|
||||
userId), ContentType.TEXT_XML
|
||||
)
|
||||
post.setEntity(new StringEntity(String.format("<obj><att id=\"type\"><val>user-defined</val></att><att id=\"name\"><val>createApiKey</val></att><att id=\"param\"><val>%s</val></att></obj>", userId),
|
||||
ContentType.TEXT_XML)
|
||||
);
|
||||
final APICredentials credentials = this.getClient().execute(post, new AbstractResponseHandler<APICredentials>() {
|
||||
@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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,5 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
public final class APICredentials {
|
||||
|
||||
public String accessKey;
|
||||
|
||||
public String secretKey;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -272,9 +272,6 @@ ftp.command.lista=true
|
||||
ftp.command.stat=true
|
||||
ftp.command.mlsd=true
|
||||
|
||||
# Fallback to active or passive mode respectively
|
||||
ftp.connectmode.fallback=false
|
||||
|
||||
# Protect the data channel by default. For TLS, the data connection can have one of two security levels.
|
||||
# 1) Clear (requested by 'PROT C')
|
||||
# 2) Private (requested by 'PROT P')
|
||||
|
||||
@@ -83,7 +83,7 @@ public class EueWriteFeature extends AbstractHttpWriteFeature<EueWriteFeature.Ch
|
||||
}
|
||||
else {
|
||||
uploadUri = status.getUrl();
|
||||
resourceId = status.getParameters().get(RESOURCE_ID);
|
||||
resourceId = status.getParameters().get(RESOURCE_ID).toString();
|
||||
}
|
||||
final HttpResponseOutputStream<Chunk> stream = this.write(file, status,
|
||||
new DelayedHttpEntityCallable<Chunk>(file) {
|
||||
|
||||
@@ -19,10 +19,10 @@ package ch.cyberduck.core.ftp;
|
||||
|
||||
import ch.cyberduck.core.exception.AccessDeniedException;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.exception.ConnectionRefusedException;
|
||||
import ch.cyberduck.core.exception.ConnectionTimeoutException;
|
||||
import ch.cyberduck.core.exception.InteroperabilityException;
|
||||
import ch.cyberduck.core.exception.NotfoundException;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -33,22 +33,16 @@ public class DataConnectionActionExecutor {
|
||||
private static final Logger log = LogManager.getLogger(DataConnectionActionExecutor.class);
|
||||
|
||||
private final FTPSession session;
|
||||
private final boolean enabled;
|
||||
|
||||
public DataConnectionActionExecutor(final FTPSession session) {
|
||||
this(session, PreferencesFactory.get().getBoolean("ftp.connectmode.fallback"));
|
||||
}
|
||||
|
||||
public DataConnectionActionExecutor(final FTPSession session, final boolean enabled) {
|
||||
this.session = session;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param action Action that needs to open a data connection
|
||||
* @return True if action was successful
|
||||
*/
|
||||
public <T> T data(final DataConnectionAction<T> action) throws IOException, BackgroundException {
|
||||
public <T> T open(final DataConnectionAction<T> action) throws IOException, BackgroundException {
|
||||
try {
|
||||
// Make sure to always configure data mode because connect event sets defaults.
|
||||
final FTPConnectMode mode = session.getConnectMode();
|
||||
@@ -62,52 +56,15 @@ public class DataConnectionActionExecutor {
|
||||
}
|
||||
return action.execute();
|
||||
}
|
||||
catch(ConnectionTimeoutException failure) {
|
||||
log.warn("Timeout opening data socket {}", failure.getMessage());
|
||||
// Expect 421 response
|
||||
catch(ConnectionRefusedException | ConnectionTimeoutException failure) {
|
||||
log.warn("I/O error opening data socket {}", failure.getMessage());
|
||||
// Drain any pending reply on the control channel to resynchronize after failed data connection
|
||||
session.getClient().completePendingCommand();
|
||||
// Fallback handling
|
||||
if(enabled) {
|
||||
try {
|
||||
return this.fallback(action);
|
||||
}
|
||||
catch(BackgroundException e) {
|
||||
log.warn("Connect mode fallback failed with {}", e.getMessage());
|
||||
// Throw original error message
|
||||
}
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
catch(InteroperabilityException | NotfoundException | AccessDeniedException failure) {
|
||||
log.warn("Server denied data socket operation with {}", failure.getMessage());
|
||||
// Fallback handling
|
||||
if(enabled) {
|
||||
try {
|
||||
return this.fallback(action);
|
||||
}
|
||||
catch(BackgroundException e) {
|
||||
log.warn("Connect mode fallback failed with {}", e.getMessage());
|
||||
// Throw original error message
|
||||
}
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param action Action that needs to open a data connection
|
||||
* @return True if action was successful
|
||||
*/
|
||||
protected <T> T fallback(final DataConnectionAction<T> action) throws BackgroundException {
|
||||
// Fallback to other connect mode
|
||||
if(session.getClient().getDataConnectionMode() == FTPClient.PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
|
||||
log.warn("Fallback to active data connection");
|
||||
session.getClient().enterLocalActiveMode();
|
||||
}
|
||||
else if(session.getClient().getDataConnectionMode() == FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE) {
|
||||
log.warn("Fallback to passive data connection");
|
||||
session.getClient().enterLocalPassiveMode();
|
||||
}
|
||||
return action.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class FTPReadFeature implements Read {
|
||||
if(status.isAppend()) {
|
||||
session.getClient().setRestartOffset(status.getOffset());
|
||||
}
|
||||
final InputStream in = new DataConnectionActionExecutor(session).data(new DataConnectionAction<InputStream>() {
|
||||
final InputStream in = new DataConnectionActionExecutor(session).open(new DataConnectionAction<InputStream>() {
|
||||
@Override
|
||||
public InputStream execute() throws BackgroundException {
|
||||
try {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class FTPWriteFeature implements Write<Void> {
|
||||
if(!session.getClient().setFileType(FTPClient.BINARY_FILE_TYPE)) {
|
||||
throw new FTPException(session.getClient().getReplyCode(), session.getClient().getReplyString());
|
||||
}
|
||||
final OutputStream out = new DataConnectionActionExecutor(session).data(new DataConnectionAction<OutputStream>() {
|
||||
final OutputStream out = new DataConnectionActionExecutor(session).open(new DataConnectionAction<OutputStream>() {
|
||||
@Override
|
||||
public OutputStream execute() throws BackgroundException {
|
||||
try {
|
||||
|
||||
@@ -55,7 +55,7 @@ public class FTPDefaultListService implements ListService {
|
||||
// data connection in type ASCII or type EBCDIC.
|
||||
throw new FTPException(session.getClient().getReplyCode(), session.getClient().getReplyString());
|
||||
}
|
||||
final List<String> list = new DataConnectionActionExecutor(session).data(new DataConnectionAction<List<String>>() {
|
||||
final List<String> list = new DataConnectionActionExecutor(session).open(new DataConnectionAction<List<String>>() {
|
||||
@Override
|
||||
public List<String> execute() throws BackgroundException {
|
||||
try {
|
||||
|
||||
@@ -53,7 +53,7 @@ public class FTPMlsdListService implements ListService {
|
||||
// data connection in type ASCII or type EBCDIC.
|
||||
throw new FTPException(session.getClient().getReplyCode(), session.getClient().getReplyString());
|
||||
}
|
||||
final List<String> list = new DataConnectionActionExecutor(session).data(new DataConnectionAction<List<String>>() {
|
||||
final List<String> list = new DataConnectionActionExecutor(session).open(new DataConnectionAction<List<String>>() {
|
||||
@Override
|
||||
public List<String> execute() throws BackgroundException {
|
||||
try {
|
||||
|
||||
@@ -17,97 +17,27 @@ package ch.cyberduck.core.ftp;
|
||||
* Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.Credentials;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.HostKeyCallback;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.proxy.DisabledProxyFinder;
|
||||
import ch.cyberduck.core.threading.CancelCallback;
|
||||
import ch.cyberduck.core.transfer.TransferStatus;
|
||||
import ch.cyberduck.core.exception.InteroperabilityException;
|
||||
import ch.cyberduck.test.IntegrationTest;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@Category(IntegrationTest.class)
|
||||
public class DataConnectionActionExecutorTest extends AbstractFTPTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testFallbackDataConnectionSocketTimeout() throws Exception {
|
||||
final Host host = new Host(new FTPProtocol(), "mirror.switch.ch", new Credentials(
|
||||
PreferencesFactory.get().getProperty("connection.login.anon.name"), null
|
||||
));
|
||||
host.setFTPConnectMode(FTPConnectMode.active);
|
||||
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
final FTPSession session = new FTPSession(host);
|
||||
session.open(new DisabledProxyFinder(), HostKeyCallback.noop, LoginCallback.noop, CancelCallback.noop);
|
||||
session.getClient().setDefaultTimeout(2000);
|
||||
session.getClient().setConnectTimeout(2000);
|
||||
session.login(LoginCallback.noop, CancelCallback.noop);
|
||||
final Path file = new Path("/pub/debian/README.html", EnumSet.of(Path.Type.file));
|
||||
final TransferStatus status = new TransferStatus();
|
||||
final DataConnectionAction<InputStream> action = new DataConnectionAction<InputStream>() {
|
||||
@Override
|
||||
public InputStream execute() throws BackgroundException {
|
||||
try {
|
||||
final InputStream in = session.getClient().retrieveFileStream(file.getAbsolute());
|
||||
if(count.get() == 0) {
|
||||
throw new FTPExceptionMappingService().map(new SocketTimeoutException());
|
||||
}
|
||||
return in;
|
||||
}
|
||||
catch(IOException e) {
|
||||
throw new FTPExceptionMappingService().map(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
final DataConnectionActionExecutor f = new DataConnectionActionExecutor(session, true) {
|
||||
@Override
|
||||
protected <T> T fallback(final DataConnectionAction<T> action) throws BackgroundException {
|
||||
count.incrementAndGet();
|
||||
return super.fallback(action);
|
||||
}
|
||||
};
|
||||
f.data(action);
|
||||
assertEquals(1, count.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFallbackDataConnection500Error() throws Exception {
|
||||
session.getHost().setFTPConnectMode(FTPConnectMode.active);
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
public void testServerError() throws Exception {
|
||||
final DataConnectionAction<Void> action = new DataConnectionAction<Void>() {
|
||||
@Override
|
||||
public Void execute() throws BackgroundException {
|
||||
if(count.get() == 0) {
|
||||
throw new FTPExceptionMappingService().map(new FTPException(500, "m"));
|
||||
}
|
||||
return null;
|
||||
throw new FTPExceptionMappingService().map(new FTPException(500, "m"));
|
||||
}
|
||||
};
|
||||
final DataConnectionActionExecutor f = new DataConnectionActionExecutor(session, true) {
|
||||
@Override
|
||||
protected <T> T fallback(final DataConnectionAction<T> action) throws BackgroundException {
|
||||
count.incrementAndGet();
|
||||
return super.fallback(action);
|
||||
}
|
||||
};
|
||||
f.data(action);
|
||||
assertEquals(1, count.get());
|
||||
final DataConnectionActionExecutor f = new DataConnectionActionExecutor(session);
|
||||
assertThrows(InteroperabilityException.class, () -> f.open(action));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -19,6 +19,7 @@ import ch.cyberduck.core.AttributedList;
|
||||
import ch.cyberduck.core.DefaultPathAttributes;
|
||||
import ch.cyberduck.core.DefaultPathContainerService;
|
||||
import ch.cyberduck.core.DescriptiveUrl;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.ListProgressListener;
|
||||
import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.Path;
|
||||
@@ -66,19 +67,19 @@ public class DriveAttributesFinderFeature implements AttributesFinder, Attribute
|
||||
if(new DefaultPathContainerService().isContainer(file)) {
|
||||
return PathAttributes.EMPTY;
|
||||
}
|
||||
final Path query;
|
||||
if(file.isPlaceholder()) {
|
||||
query = new Path(file.getParent(), FilenameUtils.removeExtension(file.getName()), file.getType(), file.attributes());
|
||||
}
|
||||
else {
|
||||
query = file;
|
||||
}
|
||||
final AttributedList<Path> list;
|
||||
if(new SimplePathPredicate(DriveHomeFinderService.SHARED_DRIVES_NAME).test(file.getParent())) {
|
||||
list = new DriveTeamDrivesListService(session, fileid).list(file.getParent(), listener);
|
||||
}
|
||||
else {
|
||||
list = new FileidDriveListService(session, fileid, query).list(file.getParent(), listener);
|
||||
final Path query;
|
||||
if(file.isPlaceholder()) {
|
||||
query = new Path(file.getParent(), FilenameUtils.removeExtension(file.getName()), file.getType(), file.attributes());
|
||||
}
|
||||
else {
|
||||
query = file;
|
||||
}
|
||||
list = new FileidDriveListService(session, fileid, query).list(file.getParent(), new DisabledListProgressListener());
|
||||
}
|
||||
final Path found = list.find(new ListFilteringFeature.ListFilteringPredicate(session.getCaseSensitivity(), file));
|
||||
if(null == found) {
|
||||
|
||||
+7
-1
@@ -23,6 +23,7 @@ import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.SimplePathPredicate;
|
||||
import ch.cyberduck.core.exception.ListCanceledException;
|
||||
import ch.cyberduck.core.exception.NotfoundException;
|
||||
import ch.cyberduck.core.features.Delete;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
@@ -89,7 +90,12 @@ public class DriveAttributesFinderFeatureTest extends AbstractDriveTest {
|
||||
final DriveFileIdProvider fileid = new DriveFileIdProvider(session);
|
||||
new DriveTouchFeature(session, fileid).touch(new DriveWriteFeature(session, fileid), test, new TransferStatus());
|
||||
final DriveAttributesFinderFeature f = new DriveAttributesFinderFeature(session, fileid);
|
||||
final PathAttributes attributes = f.find(test);
|
||||
final PathAttributes attributes = f.find(test, new DisabledListProgressListener() {
|
||||
@Override
|
||||
public void chunk(final Path directory, final AttributedList<Path> list) throws ListCanceledException {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
assertEquals(0L, attributes.getSize());
|
||||
assertNotNull(attributes.getFileId());
|
||||
assertNull(attributes.getVersionId());
|
||||
|
||||
@@ -34,6 +34,7 @@ import ch.cyberduck.core.shared.DefaultFindFeature;
|
||||
import ch.cyberduck.core.transfer.TransferStatus;
|
||||
import ch.cyberduck.test.IntegrationTest;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
@@ -46,6 +47,7 @@ import static org.junit.Assert.*;
|
||||
public class DeleteWorkerTest extends AbstractDriveTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testDelete() throws Exception {
|
||||
final Path home = DriveHomeFinderService.MYDRIVE_FOLDER;
|
||||
final DriveFileIdProvider fileid = new DriveFileIdProvider(session);
|
||||
|
||||
+5
-4
@@ -25,6 +25,7 @@ import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.LoginOptions;
|
||||
import ch.cyberduck.core.NullFilter;
|
||||
import ch.cyberduck.core.PasswordCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.ProgressListener;
|
||||
import ch.cyberduck.core.TestProtocol;
|
||||
@@ -43,7 +44,6 @@ import ch.cyberduck.core.nio.LocalProtocol;
|
||||
import ch.cyberduck.core.nio.LocalReadFeature;
|
||||
import ch.cyberduck.core.nio.LocalSession;
|
||||
import ch.cyberduck.core.notification.DisabledNotificationService;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.proxy.DisabledProxyFinder;
|
||||
import ch.cyberduck.core.threading.CancelCallback;
|
||||
import ch.cyberduck.core.transfer.DisabledTransferErrorCallback;
|
||||
@@ -115,13 +115,14 @@ public class CryptoLocalSingleTransferWorkerTest {
|
||||
out2.close();
|
||||
final CryptoVault cryptomator = new CryptoVault(vault);
|
||||
cryptomator.create(session, new VaultCredentials("test"), vaultVersion);
|
||||
session.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
final DefaultVaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
@Override
|
||||
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
|
||||
return new VaultCredentials("test");
|
||||
}
|
||||
}));
|
||||
PreferencesFactory.get().setProperty("factory.vault.class", CryptoVault.class.getName());
|
||||
});
|
||||
vaults.add(cryptomator.load(session, PasswordCallback.noop));
|
||||
session.withRegistry(vaults);
|
||||
final Transfer t = new UploadTransfer(new Host(new TestProtocol()), Collections.singletonList(new TransferItem(dir1, localDirectory1)), new NullFilter<>());
|
||||
assertTrue(new SingleTransferWorker(session, session, t, new TransferOptions(), new TransferSpeedometer(t), new DisabledTransferPrompt() {
|
||||
@Override
|
||||
|
||||
@@ -41,6 +41,7 @@ public class OAuthExceptionMappingService extends AbstractExceptionMappingServic
|
||||
switch(details.getError()) {
|
||||
// Error code "invalid_request", "invalid_client", "invalid_grant", "unauthorized_client", "unsupported_grant_type", "invalid_scope"
|
||||
case "invalid_client":
|
||||
case "invalid_request":
|
||||
case "unauthorized_client":
|
||||
case "unsupported_grant_type":
|
||||
case "invalid_scope":
|
||||
|
||||
+5
-3
@@ -24,9 +24,11 @@ import ch.cyberduck.core.onedrive.features.GraphFileIdProvider;
|
||||
import ch.cyberduck.core.onedrive.features.sharepoint.SiteDrivesListService;
|
||||
import ch.cyberduck.core.onedrive.features.sharepoint.SitesListService;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static ch.cyberduck.core.onedrive.SharepointListService.*;
|
||||
import static ch.cyberduck.core.onedrive.SharepointListService.DRIVES_CONTAINER;
|
||||
import static ch.cyberduck.core.onedrive.SharepointListService.SITES_CONTAINER;
|
||||
|
||||
public abstract class AbstractSharepointListService implements ListService {
|
||||
|
||||
@@ -72,8 +74,8 @@ public abstract class AbstractSharepointListService implements ListService {
|
||||
|
||||
protected AttributedList<Path> addSiteItems(final Path directory, final ListProgressListener listener) throws BackgroundException {
|
||||
final AttributedList<Path> list = new AttributedList<>();
|
||||
list.add(new Path(directory, DRIVES_NAME.getName(), DRIVES_NAME.getType(), DRIVES_NAME.attributes()));
|
||||
list.add(new Path(directory, SITES_NAME.getName(), SITES_NAME.getType(), SITES_NAME.attributes()));
|
||||
list.add(new Path(directory, DRIVES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory, Path.Type.volume)));
|
||||
list.add(new Path(directory, SITES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory, Path.Type.volume)));
|
||||
listener.chunk(directory, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -16,36 +16,27 @@ package ch.cyberduck.core.onedrive;
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.AttributedList;
|
||||
import ch.cyberduck.core.DefaultPathAttributes;
|
||||
import ch.cyberduck.core.DefaultIOExceptionMappingService;
|
||||
import ch.cyberduck.core.ListProgressListener;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.onedrive.features.GraphFileIdProvider;
|
||||
import ch.cyberduck.core.onedrive.features.sharepoint.GroupDrivesListService;
|
||||
import ch.cyberduck.core.onedrive.features.sharepoint.GroupListService;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.nuxeo.onedrive.client.OneDriveAPIException;
|
||||
import org.nuxeo.onedrive.client.OneDriveRuntimeException;
|
||||
import org.nuxeo.onedrive.client.types.Site;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SharepointListService extends AbstractSharepointListService {
|
||||
static final Logger log = LogManager.getLogger(SharepointListService.class);
|
||||
|
||||
public static final String DEFAULT_SITE = "Default";
|
||||
public static final String DRIVES_CONTAINER = "Drives";
|
||||
public static final String GROUPS_CONTAINER = "Groups";
|
||||
public static final String SITES_CONTAINER = "Sites";
|
||||
|
||||
public static final Path DEFAULT_NAME = new Path(DEFAULT_SITE, EnumSet.of(Path.Type.volume, Path.Type.placeholder, Path.Type.directory, Path.Type.symboliclink));
|
||||
public static final Path DRIVES_NAME = new Path(DRIVES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory));
|
||||
public static final Path GROUPS_NAME = new Path(GROUPS_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory));
|
||||
public static final Path SITES_NAME = new Path(SITES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory));
|
||||
|
||||
private final SharepointSession session;
|
||||
private final GraphFileIdProvider fileid;
|
||||
|
||||
@@ -55,48 +46,21 @@ public class SharepointListService extends AbstractSharepointListService {
|
||||
this.fileid = fileid;
|
||||
}
|
||||
|
||||
private Optional<Path> getDefault(final Path directory) {
|
||||
try {
|
||||
final Site site = Site.byId(session.getClient(), "root");
|
||||
final Site.Metadata metadata = site.getMetadata(null); // query: null: Default return set.
|
||||
final EnumSet<Path.Type> type = EnumSet.copyOf(DEFAULT_NAME.getType());
|
||||
final Path path = new Path(directory, DEFAULT_NAME.getName(), type, new DefaultPathAttributes().setFileId(metadata.getId()));
|
||||
path.setSymlinkTarget(
|
||||
new Path(SITES_NAME, metadata.getSiteCollection().getHostname(), SITES_NAME.getType(),
|
||||
new DefaultPathAttributes().setFileId(metadata.getId())));
|
||||
return Optional.of(path);
|
||||
}
|
||||
catch(IOException ex) {
|
||||
log.error("Cannot get default site. Skipping.", ex);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AttributedList<Path> getRoot(final Path directory, final ListProgressListener listener) throws BackgroundException {
|
||||
final AttributedList<Path> list = new AttributedList<>();
|
||||
getDefault(directory).ifPresent(list::add);
|
||||
addDefaultItems(list);
|
||||
list.add(new Path(directory, GROUPS_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory, Path.Type.volume)));
|
||||
list.add(new Path(directory, SITES_CONTAINER, EnumSet.of(Path.Type.placeholder, Path.Type.directory, Path.Type.volume)));
|
||||
listener.chunk(directory, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
static void addDefaultItems(final AttributedList<Path> list) {
|
||||
list.add(GROUPS_NAME);
|
||||
list.add(SITES_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AttributedList<Path> processList(Path directory, final ListProgressListener listener) throws BackgroundException {
|
||||
final GraphSession.ContainerItem container = session.getContainer(directory);
|
||||
if(container.isDrive()) {
|
||||
return AttributedList.emptyList();
|
||||
}
|
||||
|
||||
// Default?
|
||||
if(!container.isDefined() && container.getContainerPath().map(p -> DEFAULT_SITE.equals(p.getName())).orElse(false)) {
|
||||
return addSiteItems(directory, listener);
|
||||
}
|
||||
if(container.getCollectionPath().map(p -> GROUPS_CONTAINER.equals(p.getName())).orElse(false)) {
|
||||
if(!container.isDefined()) {
|
||||
return new GroupListService(session, fileid).list(directory, listener);
|
||||
@@ -107,4 +71,21 @@ public class SharepointListService extends AbstractSharepointListService {
|
||||
}
|
||||
return AttributedList.emptyList();
|
||||
}
|
||||
|
||||
public Path getDefaultSite() throws BackgroundException {
|
||||
try {
|
||||
final Site.Metadata metadata = Site.byId(session.getClient(), "root").getMetadata(null);
|
||||
return this.list(new Path(SharepointListService.SITES_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory, Path.Type.volume))).find(path -> metadata.getId().equals(path.attributes().getFileId()));
|
||||
}
|
||||
catch(OneDriveRuntimeException e) {
|
||||
throw new GraphExceptionMappingService(fileid).map(e.getCause());
|
||||
}
|
||||
catch(OneDriveAPIException e) {
|
||||
throw new GraphExceptionMappingService(fileid).map(e);
|
||||
}
|
||||
catch(IOException e) {
|
||||
throw new DefaultIOExceptionMappingService().map(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,21 +46,13 @@ public class SharepointSession extends AbstractSharepointSession {
|
||||
if(parentContainer.getCollectionPath().map(p -> SharepointListService.GROUPS_CONTAINER.equals(p.getName())).orElse(false)) {
|
||||
return new Drive(getGroup(parentContainer.getContainerPath().get()), driveId);
|
||||
}
|
||||
else if(parentContainer.getContainerPath().map(p -> SharepointListService.DEFAULT_SITE.equals(p.getName())).orElse(false)) {
|
||||
// Handles /Default-case, which is a site.
|
||||
return new Drive(getSite(parentContainer.getContainerPath().get()), driveId);
|
||||
}
|
||||
else {
|
||||
// finds:
|
||||
// Sites/<site name>
|
||||
final GraphSession.ContainerItem containerItem = getContainer(parentContainer.getContainerPath().get());
|
||||
if(containerItem.getCollectionPath().map(p -> SharepointListService.SITES_CONTAINER.equals(p.getName())).orElse(false)) {
|
||||
return new Drive(getSite(containerItem.getContainerPath().get()), driveId);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
// finds:
|
||||
// Sites/<site name>
|
||||
final GraphSession.ContainerItem containerItem = getContainer(parentContainer.getContainerPath().get());
|
||||
if(containerItem.getCollectionPath().map(p -> SharepointListService.SITES_CONTAINER.equals(p.getName())).orElse(false)) {
|
||||
return new Drive(getSite(containerItem.getContainerPath().get()), driveId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+11
-8
@@ -21,7 +21,6 @@ import org.nuxeo.onedrive.client.types.SharePointIds;
|
||||
import org.nuxeo.onedrive.client.types.Site;
|
||||
import org.nuxeo.onedrive.client.types.Site.Metadata;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -137,23 +136,27 @@ public class SitesListService extends AbstractListService<Site.Metadata> {
|
||||
}
|
||||
});
|
||||
if(!result.isEmpty()) {
|
||||
final Set<Integer> set = duplicates.getOrDefault(file.getName(), new HashSet<>());
|
||||
set.add(i);
|
||||
duplicates.put(file.getName(), set);
|
||||
duplicates.computeIfAbsent(file.getName(), key -> new HashSet<>())
|
||||
.add(i);
|
||||
}
|
||||
}
|
||||
for(Set<Integer> set : duplicates.values()) {
|
||||
for(Integer i : set) {
|
||||
final Path file = list.get(i);
|
||||
final URI webLink = URI.create(file.attributes().getLink().getUrl());
|
||||
final String[] path = webLink.getPath().split(String.valueOf(Path.DELIMITER));
|
||||
final String suffix = path[path.length - 2];
|
||||
final Path rename = new Path(file.getParent(), String.format("%s (%s)", file.getName(), suffix), file.getType(), file.attributes());
|
||||
final String siteIdUnique = getSiteId(file.attributes().getFileId());
|
||||
final Path rename = new Path(file.getParent(), String.format("%s (%s)", file.getName(), siteIdUnique), file.getType(), file.attributes());
|
||||
list.set(i, rename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSiteId(final String fileId) {
|
||||
// caller ensures that fileId is valid ("tenant,siteId,webId")
|
||||
final int siteIdStart = fileId.indexOf(',');
|
||||
final int siteIdEnd = fileId.indexOf(',', siteIdStart + 1);
|
||||
return fileId.substring(siteIdStart + 1, siteIdEnd);
|
||||
}
|
||||
|
||||
enum SharepointID {
|
||||
Invalid(0, 0);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package ch.cyberduck.core.onedrive;
|
||||
import ch.cyberduck.core.AlphanumericRandomStringService;
|
||||
import ch.cyberduck.core.AttributedList;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
@@ -46,8 +45,8 @@ public class GraphLockFeatureTest extends AbstractSharepointTest {
|
||||
|
||||
@Test
|
||||
public void testLock() throws Exception {
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(list.getDefaultSite(), DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path drive = drives.get(0);
|
||||
final Path file = new GraphTouchFeature(session, fileid).touch(new GraphWriteFeature(session, fileid), new Path(drive,
|
||||
new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
package ch.cyberduck.core.onedrive;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2002-2018 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.AttributedList;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.exception.NotfoundException;
|
||||
import ch.cyberduck.core.features.Home;
|
||||
import ch.cyberduck.core.onedrive.features.GraphAttributesFinderFeature;
|
||||
import ch.cyberduck.core.shared.DefaultHomeFinderService;
|
||||
import ch.cyberduck.core.shared.PathAttributesHomeFeature;
|
||||
import ch.cyberduck.core.shared.RootPathContainerService;
|
||||
import ch.cyberduck.test.IntegrationTest;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -33,9 +15,7 @@ import org.junit.experimental.categories.Category;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import static ch.cyberduck.core.onedrive.SharepointListService.DRIVES_CONTAINER;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@Category(IntegrationTest.class)
|
||||
public class SharepointListServiceTest extends AbstractSharepointTest {
|
||||
@@ -46,29 +26,54 @@ public class SharepointListServiceTest extends AbstractSharepointTest {
|
||||
new SharepointListService(session, fileid).list(directory, new DisabledListProgressListener());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListRoot() throws Exception {
|
||||
final AttributedList<Path> list = new SharepointListService(session, fileid).list(Home.root(), new DisabledListProgressListener());
|
||||
assertFalse(list.isEmpty());
|
||||
assertEquals(3, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListDefault() throws Exception {
|
||||
new SharepointListService(session, fileid).list(SharepointListService.DEFAULT_NAME, new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final Path defaultSite = new Path(list.getDefaultSite()).withAttributes(PathAttributes.EMPTY);
|
||||
final AttributedList<Path> containers = list.list(defaultSite);
|
||||
assertEquals(2, containers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListDefaultDriveOverwrite() throws Exception {
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path drive = drives.get(0);
|
||||
new PathAttributesHomeFeature(session, () -> drive, new GraphAttributesFinderFeature(session, fileid), new RootPathContainerService()).find();
|
||||
list.list(drive, new DisabledListProgressListener());
|
||||
public void testListRoot() throws Exception {
|
||||
final Path root = Home.root();
|
||||
final AttributedList<Path> list = new SharepointListService(session, fileid).list(root, new DisabledListProgressListener());
|
||||
assertFalse(list.isEmpty());
|
||||
assertEquals(2, list.size());
|
||||
for(Path d : list) {
|
||||
assertSame(root, d.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListGroups() throws Exception {
|
||||
new SharepointListService(session, fileid).list(SharepointListService.GROUPS_NAME, new DisabledListProgressListener());
|
||||
final Path container = new Path(SharepointListService.GROUPS_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory));
|
||||
final AttributedList<Path> list = new SharepointListService(session, fileid).list(container, new DisabledListProgressListener());
|
||||
for(Path group : list) {
|
||||
assertSame(container, group.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListSites() throws Exception {
|
||||
final Path container = new Path(SharepointListService.SITES_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory));
|
||||
final AttributedList<Path> list = new SharepointListService(session, fileid).list(container, new DisabledListProgressListener());
|
||||
for(Path site : list) {
|
||||
assertSame(container, site.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListDrives() throws Exception {
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final Path siteWithoutId = new Path(list.getDefaultSite()).withAttributes(PathAttributes.EMPTY);
|
||||
final Path directory = new Path(siteWithoutId, SharepointListService.DRIVES_CONTAINER, EnumSet.of(Path.Type.directory));
|
||||
final AttributedList<Path> drives = list.list(directory);
|
||||
assertFalse(drives.isEmpty());
|
||||
for(Path d : drives) {
|
||||
assertSame(directory, d.getParent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import ch.cyberduck.core.AbstractPath;
|
||||
import ch.cyberduck.core.DefaultPathAttributes;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.features.Home;
|
||||
import ch.cyberduck.core.ssl.DefaultX509KeyManager;
|
||||
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
|
||||
@@ -46,29 +45,21 @@ public class SharepointSessionTest {
|
||||
@Test
|
||||
public void isAccessible() {
|
||||
assertFalse(session.isAccessible(Home.root()));
|
||||
assertFalse(session.isAccessible(SharepointListService.DEFAULT_NAME));
|
||||
assertFalse(session.isAccessible(SharepointListService.DEFAULT_NAME, false));
|
||||
final Path defaultSiteDrive =
|
||||
new Path(
|
||||
new Path(
|
||||
SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(AbstractPath.Type.directory)),
|
||||
"Drive-Id", EnumSet.of(Path.Type.directory));
|
||||
assertTrue(session.isAccessible(defaultSiteDrive));
|
||||
assertFalse(session.isAccessible(defaultSiteDrive, false));
|
||||
|
||||
assertFalse(session.isAccessible(SharepointListService.SITES_NAME));
|
||||
assertFalse(session.isAccessible(SharepointListService.SITES_NAME, false));
|
||||
assertFalse(session.isAccessible(SharepointListService.GROUPS_NAME));
|
||||
assertFalse(session.isAccessible(SharepointListService.GROUPS_NAME, false));
|
||||
final Path siteDrive =
|
||||
new Path(
|
||||
new Path(
|
||||
new Path(SharepointListService.SITES_NAME, "Site", EnumSet.of(AbstractPath.Type.directory)),
|
||||
DRIVES_CONTAINER, EnumSet.of(AbstractPath.Type.directory)),
|
||||
"Drive-Id", EnumSet.of(Path.Type.directory));
|
||||
assertFalse(session.isAccessible(new Path(SharepointListService.SITES_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory))));
|
||||
assertFalse(session.isAccessible(new Path(SharepointListService.SITES_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory)), false));
|
||||
assertFalse(session.isAccessible(new Path(SharepointListService.GROUPS_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory))));
|
||||
assertFalse(session.isAccessible(new Path(SharepointListService.GROUPS_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory)), false));
|
||||
final Path siteDrive = new Path(new Path(new Path(new Path(SharepointListService.SITES_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory)), "Site", EnumSet.of(AbstractPath.Type.directory)),
|
||||
DRIVES_CONTAINER, EnumSet.of(AbstractPath.Type.directory)), "Drive-Id", EnumSet.of(Path.Type.directory));
|
||||
assertTrue(session.isAccessible(siteDrive));
|
||||
assertFalse(session.isAccessible(siteDrive, false));
|
||||
final Path group = new Path(SharepointListService.GROUPS_NAME, "Group Name", EnumSet.of(Path.Type.directory));
|
||||
final Path group = new Path(new Path(SharepointListService.GROUPS_CONTAINER,
|
||||
EnumSet.of(Path.Type.placeholder, Path.Type.directory)), "Group Name", EnumSet.of(Path.Type.directory));
|
||||
assertFalse(session.isAccessible(group));
|
||||
assertFalse(session.isAccessible(group, false));
|
||||
assertTrue(session.isAccessible(new Path(group, "Drive-Id", EnumSet.of(Path.Type.directory))));
|
||||
|
||||
@@ -34,7 +34,6 @@ public class SharepointSiteSessionTest {
|
||||
|
||||
@Test
|
||||
public void testHome() {
|
||||
Assert.assertFalse(session.isHome(SharepointListService.DEFAULT_NAME));
|
||||
Assert.assertTrue(session.isHome(Home.root()));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -18,7 +18,6 @@ package ch.cyberduck.core.onedrive;
|
||||
import ch.cyberduck.core.AlphanumericRandomStringService;
|
||||
import ch.cyberduck.core.AttributedList;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
@@ -50,8 +49,8 @@ public class SharepointTimestampFeatureTest extends AbstractSharepointTest {
|
||||
|
||||
@Test
|
||||
public void testSetTimestamp() throws Exception {
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(list.getDefaultSite(), DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path drive = drives.get(0);
|
||||
final Path file = new Path(drive, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
|
||||
new GraphTouchFeature(session, fileid).touch(new GraphWriteFeature(session, fileid), file, new TransferStatus().setMime("x-application/cyberduck"));
|
||||
@@ -69,8 +68,8 @@ public class SharepointTimestampFeatureTest extends AbstractSharepointTest {
|
||||
|
||||
@Test
|
||||
public void testSetTimestampDirectory() throws Exception {
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(list.getDefaultSite(), DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path drive = drives.get(0);
|
||||
final Path test = new Path(drive, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
|
||||
new GraphDirectoryFeature(session, fileid).mkdir(new GraphWriteFeature(session, fileid), test, null);
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@ import ch.cyberduck.core.ConnectionCallback;
|
||||
import ch.cyberduck.core.DefaultPathAttributes;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.DisabledPasswordCallback;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.exception.NotfoundException;
|
||||
@@ -56,8 +55,8 @@ public class SharepointVersioningFeatureTest extends AbstractSharepointTest {
|
||||
@Test
|
||||
public void testList() throws Exception {
|
||||
final GraphFileIdProvider fileid = new GraphFileIdProvider(session);
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(list.getDefaultSite(), DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path drive = drives.get(0);
|
||||
final Path test = new GraphTouchFeature(session, fileid).touch(new GraphWriteFeature(session, fileid), new Path(drive, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
|
||||
assertNotNull(test.attributes().getVersionId());
|
||||
|
||||
@@ -19,7 +19,6 @@ import ch.cyberduck.core.AlphanumericRandomStringService;
|
||||
import ch.cyberduck.core.AttributedList;
|
||||
import ch.cyberduck.core.ConnectionCallback;
|
||||
import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
@@ -57,8 +56,8 @@ public class SharepointWriteFeatureTest extends AbstractSharepointTest {
|
||||
@Test
|
||||
public void testWrite() throws Exception {
|
||||
final GraphWriteFeature feature = new GraphWriteFeature(session, fileid);
|
||||
final ListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(SharepointListService.DEFAULT_NAME, DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final SharepointListService list = new SharepointListService(session, fileid);
|
||||
final AttributedList<Path> drives = list.list(new Path(list.getDefaultSite(), DRIVES_CONTAINER, EnumSet.of(Path.Type.directory)), new DisabledListProgressListener());
|
||||
final Path container = drives.get(0);
|
||||
final Path folder = new GraphDirectoryFeature(session, fileid).mkdir(new GraphWriteFeature(session, fileid), new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory)), new TransferStatus());
|
||||
final PathAttributes folderAttributes = new GraphAttributesFinderFeature(session, fileid).find(folder);
|
||||
|
||||
@@ -30,7 +30,6 @@ import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LocalFactory;
|
||||
import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.LocationCallback;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.LoginOptions;
|
||||
import ch.cyberduck.core.exception.ConnectionCanceledException;
|
||||
@@ -157,12 +156,4 @@ public class PromptLoginCallback extends PromptPasswordCallback implements Login
|
||||
}
|
||||
throw new LoginCanceledException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getFeature(final Class<T> type) {
|
||||
if(type == LocationCallback.class) {
|
||||
return type.cast(new PromptLocationCallback(controller));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import ch.cyberduck.core.PasswordCallback;
|
||||
import ch.cyberduck.core.exception.LoginCanceledException;
|
||||
import ch.cyberduck.ui.cocoa.controller.PasswordController;
|
||||
|
||||
public class PromptPasswordCallback implements PasswordCallback {
|
||||
public class PromptPasswordCallback extends PromptLocationCallback implements PasswordCallback {
|
||||
|
||||
private final ProxyController controller;
|
||||
|
||||
@@ -32,6 +32,7 @@ public class PromptPasswordCallback implements PasswordCallback {
|
||||
private boolean suppressed;
|
||||
|
||||
public PromptPasswordCallback(final ProxyController controller) {
|
||||
super(controller);
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
|
||||
@@ -502,8 +502,8 @@ public class BookmarkController extends SheetController implements CollectionLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(final boolean key) {
|
||||
super.display(key);
|
||||
public void display(final boolean key, final String frameName) {
|
||||
super.display(key, frameName);
|
||||
cascade = this.cascade(cascade);
|
||||
}
|
||||
|
||||
|
||||
@@ -636,9 +636,7 @@ public class BrowserController extends WindowController implements NSToolbar.Del
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindow(NSWindow window) {
|
||||
// Save frame rectangle
|
||||
window.setFrameAutosaveName("Browser");
|
||||
public void setWindow(final NSWindow window) {
|
||||
if(window.respondsToSelector(Foundation.selector("setSubtitle:"))) {
|
||||
window.setSubtitle(StringUtils.EMPTY);
|
||||
}
|
||||
@@ -662,16 +660,24 @@ public class BrowserController extends WindowController implements NSToolbar.Del
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(final boolean key) {
|
||||
super.display(key);
|
||||
public String windowFrameName() {
|
||||
if(pool != SessionPool.DISCONNECTED) {
|
||||
return pool.getHost().getUuid();
|
||||
}
|
||||
return "Browser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(final boolean key, final String frameName) {
|
||||
super.display(key, frameName);
|
||||
cascade = this.cascade(cascade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowWillClose(final NSNotification notification) {
|
||||
// Convert from lower left to top left coordinates
|
||||
cascade = new NSPoint(this.window().frame().origin.x.doubleValue(),
|
||||
this.window().frame().origin.y.doubleValue() + this.window().frame().size.height.doubleValue());
|
||||
cascade = new NSPoint(window.frame().origin.x.doubleValue(),
|
||||
window.frame().origin.y.doubleValue() + window.frame().size.height.doubleValue());
|
||||
super.windowWillClose(notification);
|
||||
}
|
||||
|
||||
@@ -1788,7 +1794,7 @@ public class BrowserController extends WindowController implements NSToolbar.Del
|
||||
|
||||
@Action
|
||||
public void searchButtonClicked(final ID sender) {
|
||||
this.window().makeFirstResponder(searchField);
|
||||
window.makeFirstResponder(searchField);
|
||||
}
|
||||
|
||||
@Action
|
||||
@@ -3346,6 +3352,12 @@ public class BrowserController extends WindowController implements NSToolbar.Del
|
||||
scheduler.shutdown(false);
|
||||
}
|
||||
pool.shutdown();
|
||||
window.setTitle(StringUtils.EMPTY);
|
||||
if(window.respondsToSelector(Foundation.selector("setSubtitle:"))) {
|
||||
window.setSubtitle(StringUtils.EMPTY);
|
||||
}
|
||||
window.setRepresentedFilename(StringUtils.EMPTY);
|
||||
window.saveFrameUsingName(pool.getHost().getUuid());
|
||||
pool = SessionPool.DISCONNECTED;
|
||||
setWorkdir(null);
|
||||
cache.clear();
|
||||
@@ -3353,11 +3365,6 @@ public class BrowserController extends WindowController implements NSToolbar.Del
|
||||
editor.close();
|
||||
}
|
||||
editors.clear();
|
||||
window.setTitle(StringUtils.EMPTY);
|
||||
if(window.respondsToSelector(Foundation.selector("setSubtitle:"))) {
|
||||
window.setSubtitle(StringUtils.EMPTY);
|
||||
}
|
||||
window.setRepresentedFilename(StringUtils.EMPTY);
|
||||
navigation.clear();
|
||||
disconnected.run();
|
||||
}
|
||||
|
||||
@@ -328,7 +328,6 @@ public class InfoController extends ToolbarWindowController {
|
||||
|
||||
@Override
|
||||
public void setWindow(final NSWindow window) {
|
||||
window.setFrameAutosaveName("Info");
|
||||
window.setHidesOnDeactivate(false);
|
||||
window.setShowsResizeIndicator(true);
|
||||
window.setContentMinSize(window.frame().size);
|
||||
@@ -340,14 +339,19 @@ public class InfoController extends ToolbarWindowController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(final boolean key) {
|
||||
super.display(key);
|
||||
public String windowFrameName() {
|
||||
return "Info";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(final boolean key, final String frameName) {
|
||||
super.display(key, frameName);
|
||||
cascade = this.cascade(cascade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowWillClose(final NSNotification notification) {
|
||||
cascade = new NSPoint(this.window().frame().origin.x.doubleValue(), this.window().frame().origin.y.doubleValue() + this.window().frame().size.height.doubleValue());
|
||||
cascade = new NSPoint(window.frame().origin.x.doubleValue(), window.frame().origin.y.doubleValue() + window.frame().size.height.doubleValue());
|
||||
super.windowWillClose(notification);
|
||||
}
|
||||
|
||||
@@ -1852,7 +1856,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if progress animation has started and settings are toggled
|
||||
*/
|
||||
protected boolean toggleS3Settings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final Credentials credentials = session.getHost().getCredentials();
|
||||
boolean enable = session.getHost().getProtocol().getType() == Protocol.Type.s3
|
||||
|| session.getHost().getProtocol().getType() == Protocol.Type.b2
|
||||
@@ -2083,7 +2087,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if progress animation has started and settings are toggled
|
||||
*/
|
||||
protected boolean toggleAclSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final boolean enabled = this.validateAclActions(stop);
|
||||
if(stop) {
|
||||
aclProgress.stopAnimation(null);
|
||||
@@ -2114,7 +2118,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if progress animation has started and settings are toggled
|
||||
*/
|
||||
protected boolean toggleMetadataSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final boolean feature = this.validateMetadataActions(stop);
|
||||
if(stop) {
|
||||
metadataProgress.stopAnimation(null);
|
||||
@@ -2166,7 +2170,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if progress animation has started and settings are toggled
|
||||
*/
|
||||
protected boolean toggleVersionsSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final boolean enabled = this.validateVersionsActions(stop);
|
||||
if(stop) {
|
||||
versionsProgress.stopAnimation(null);
|
||||
@@ -2354,7 +2358,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if controls are enabled for the given protocol in idle state
|
||||
*/
|
||||
protected boolean togglePermissionSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final Credentials credentials = session.getHost().getCredentials();
|
||||
boolean enable = !credentials.isAnonymousLogin() && session.getFeature(UnixPermission.class) != null;
|
||||
recursiveButton.setEnabled(stop && enable);
|
||||
@@ -2390,7 +2394,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if controls are enabled for the given protocol in idle state
|
||||
*/
|
||||
protected boolean toggleDistributionSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
final Credentials credentials = session.getHost().getCredentials();
|
||||
final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class);
|
||||
boolean enable = !credentials.isAnonymousLogin() && cdn != null;
|
||||
@@ -2597,7 +2601,7 @@ public class InfoController extends ToolbarWindowController {
|
||||
* @return True if progress animation has started and settings are toggled
|
||||
*/
|
||||
protected boolean toggleSizeSettings(final boolean stop) {
|
||||
this.window().endEditingFor(null);
|
||||
window.endEditingFor(null);
|
||||
sizeButton.setEnabled(false);
|
||||
for(Path next : files) {
|
||||
if(next.isDirectory()) {
|
||||
|
||||
@@ -194,9 +194,9 @@ public class MainController extends BundleController implements NSApplication.De
|
||||
private BookmarkMenuDelegate bookmarkMenuDelegate;
|
||||
|
||||
/**
|
||||
* @param frame Frame autosave name
|
||||
* @param frameName Frame autosave name
|
||||
*/
|
||||
public static BrowserController newDocument(final boolean force, final String frame) {
|
||||
public static BrowserController newDocument(final boolean force, final String frameName) {
|
||||
final List<BrowserController> browsers = getBrowsers();
|
||||
if(!force) {
|
||||
for(BrowserController controller : browsers) {
|
||||
@@ -213,10 +213,7 @@ public class MainController extends BundleController implements NSApplication.De
|
||||
browsers.remove(controller);
|
||||
}
|
||||
});
|
||||
controller.display();
|
||||
if(StringUtils.isNotBlank(frame)) {
|
||||
controller.window().setFrameUsingName(frame);
|
||||
}
|
||||
controller.display(true, null == frameName ? controller.windowFrameName() : frameName);
|
||||
browsers.add(controller);
|
||||
return controller;
|
||||
}
|
||||
@@ -1087,7 +1084,6 @@ public class MainController extends BundleController implements NSApplication.De
|
||||
= new HostDictionary<>().deserialize(browser.getSession().getHost().serialize(SerializerFactory.get()));
|
||||
serialized.setWorkdir(browser.workdir());
|
||||
sessions.add(serialized);
|
||||
browser.window().saveFrameUsingName(serialized.getUuid());
|
||||
}
|
||||
}
|
||||
if(browser.isConnected()) {
|
||||
@@ -1278,7 +1274,7 @@ public class MainController extends BundleController implements NSApplication.De
|
||||
if(browser.isMounted()) {
|
||||
if(new HostUrlProvider().get(browser.getSession().getHost()).equals(new HostUrlProvider().get(h))) {
|
||||
// Handle browser window already connected to the same host. #4215
|
||||
browser.display();
|
||||
browser.display(true, null);
|
||||
if(Path.Type.directory == detector.detect(h.getDefaultPath())) {
|
||||
browser.setWorkdir(new Path(PathNormalizer.normalize(h.getDefaultPath()), EnumSet.of(Path.Type.directory)));
|
||||
}
|
||||
|
||||
@@ -332,9 +332,8 @@ public class PreferencesController extends ToolbarWindowController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindow(NSWindow window) {
|
||||
public void setWindow(final NSWindow window) {
|
||||
window.setExcludedFromWindowsMenu(true);
|
||||
window.setFrameAutosaveName("Preferences");
|
||||
if(window.respondsToSelector(Foundation.selector("setToolbarStyle:"))) {
|
||||
window.setToolbarStyle(NSWindow.NSWindowToolbarStyle.NSWindowToolbarStylePreference);
|
||||
}
|
||||
@@ -342,6 +341,11 @@ public class PreferencesController extends ToolbarWindowController {
|
||||
super.setWindow(window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String windowFrameName() {
|
||||
return "Preferences";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void awakeFromNib() {
|
||||
this.window.center();
|
||||
@@ -2466,7 +2470,7 @@ public class PreferencesController extends ToolbarWindowController {
|
||||
preferences.setLogging(Level.DEBUG.toString());
|
||||
break;
|
||||
default:
|
||||
preferences.setLogging(Level.ERROR.toString());
|
||||
preferences.resetLogging();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,8 +195,7 @@ public class TransferController extends WindowController implements TransferList
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindow(NSWindow window) {
|
||||
window.setFrameAutosaveName("Transfers");
|
||||
public void setWindow(final NSWindow window) {
|
||||
window.setContentMinSize(new NSSize(400d, 150d));
|
||||
window.setMovableByWindowBackground(true);
|
||||
window.setTitle(LocaleFactory.localizedString("Transfers"));
|
||||
@@ -206,6 +205,11 @@ public class TransferController extends WindowController implements TransferList
|
||||
super.setWindow(window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String windowFrameName() {
|
||||
return "Transfers";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDidBecomeKey(NSNotification notification) {
|
||||
this.updateHighlight();
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ProtocolFactoryTest {
|
||||
factory.register(ftp);
|
||||
final Profile ftps = new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/FTPS.cyberduckprofile"));
|
||||
factory.register(ftps);
|
||||
final Profile s3 = new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
final Profile s3 = new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
factory.register(s3);
|
||||
assertSame(ftp, factory.forName(ftp.getIdentifier()));
|
||||
assertSame(ftp, factory.forName(ftp.getIdentifier(), ftp.getProvider()));
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@
|
||||
<key>Bundled</key>
|
||||
<true/>
|
||||
<key>Description</key>
|
||||
<string>AWS S3 (IAM Identity Center)</string>
|
||||
<string>Amazon S3 (IAM Identity Center)</string>
|
||||
<key>Default Nickname</key>
|
||||
<string>AWS S3 (IAM Identity Center)</string>
|
||||
<string>Amazon S3 (IAM Identity Center)</string>
|
||||
<key>Hostname Configurable</key>
|
||||
<false/>
|
||||
<key>Scopes</key>
|
||||
@@ -62,7 +62,9 @@ public class S3BucketListService implements RootListService {
|
||||
// Null if the owner is not available
|
||||
attr.setOwner(b.getOwner().getId());
|
||||
}
|
||||
attr.setCreationDate(b.getCreationDate().getTime());
|
||||
if(b.getCreationDate() != null) {
|
||||
attr.setCreationDate(b.getCreationDate().getTime());
|
||||
}
|
||||
if(b.isLocationKnown()) {
|
||||
attr.setRegion(b.getLocation());
|
||||
}
|
||||
|
||||
@@ -46,14 +46,9 @@ import java.util.function.Predicate;
|
||||
import com.amazonaws.auth.profile.internal.AbstractProfilesConfigFileScanner;
|
||||
import com.amazonaws.auth.profile.internal.AllProfiles;
|
||||
import com.amazonaws.auth.profile.internal.BasicProfile;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
|
||||
/**
|
||||
* Configure credentials from AWS CLI configuration and SSO cache
|
||||
@@ -98,11 +93,7 @@ public class S3CredentialsConfigurator implements CredentialsConfigurator {
|
||||
if(profile.isProcessBasedProfile()) {
|
||||
// Uses external process to retrieve temporary credentials
|
||||
final String command = profile.getCredentialProcess();
|
||||
final ObjectMapper mapper = JsonMapper.builder()
|
||||
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY).build();
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
List<String> cmd = new ArrayList<>();
|
||||
switch(Factory.Platform.getDefault()) {
|
||||
case windows:
|
||||
@@ -192,7 +183,7 @@ public class S3CredentialsConfigurator implements CredentialsConfigurator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialsConfigurator reload() throws LoginCanceledException {
|
||||
public S3CredentialsConfigurator reload() throws LoginCanceledException {
|
||||
// See https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html for configuration behavior
|
||||
final Local configFile = LocalFactory.get(directory, "config");
|
||||
final Local credentialsFile = LocalFactory.get(directory, "credentials");
|
||||
@@ -246,6 +237,7 @@ public class S3CredentialsConfigurator implements CredentialsConfigurator {
|
||||
|| e.getProperties().containsKey("sso_session");
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private static class CachedCredential {
|
||||
@JsonProperty("AccessKeyId")
|
||||
private String accessKey;
|
||||
|
||||
@@ -24,6 +24,7 @@ import ch.cyberduck.core.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.HostKeyCallback;
|
||||
import ch.cyberduck.core.ListService;
|
||||
import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathContainerService;
|
||||
@@ -37,6 +38,7 @@ import ch.cyberduck.core.cdn.DistributionConfiguration;
|
||||
import ch.cyberduck.core.cloudfront.CloudFrontDistributionConfigurationPreloader;
|
||||
import ch.cyberduck.core.cloudfront.WebsiteCloudFrontDistributionConfiguration;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.exception.ConnectionCanceledException;
|
||||
import ch.cyberduck.core.features.*;
|
||||
import ch.cyberduck.core.http.CustomServiceUnavailableRetryStrategy;
|
||||
import ch.cyberduck.core.http.HttpSession;
|
||||
@@ -52,6 +54,7 @@ import ch.cyberduck.core.ssl.DefaultX509KeyManager;
|
||||
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
|
||||
import ch.cyberduck.core.ssl.X509KeyManager;
|
||||
import ch.cyberduck.core.ssl.X509TrustManager;
|
||||
import ch.cyberduck.core.sso.IdentityCenterAuthorizationService;
|
||||
import ch.cyberduck.core.sso.IdentityCenterCredentialsStrategy;
|
||||
import ch.cyberduck.core.sso.RegisterClientOAuth2RequestInterceptor;
|
||||
import ch.cyberduck.core.sts.STSAssumeRoleCredentialsStrategy;
|
||||
@@ -83,7 +86,11 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.amazonaws.auth.profile.internal.BasicProfile;
|
||||
|
||||
import static ch.cyberduck.core.s3.S3CredentialsConfigurator.toSsoPredicate;
|
||||
import static com.amazonaws.services.s3.Headers.REQUESTER_PAYS_HEADER;
|
||||
import static com.amazonaws.services.s3.Headers.S3_ALTERNATE_DATE;
|
||||
|
||||
@@ -260,8 +267,28 @@ public class S3Session extends HttpSession<RequestEntityRestStorageService> {
|
||||
protected S3CredentialsStrategy configureCredentialsStrategy(final HttpClientBuilder configuration,
|
||||
final LoginCallback prompt) throws BackgroundException {
|
||||
if(host.getProtocol().isOAuthConfigurable()) {
|
||||
if(host.getProtocol().getOAuthScopes().contains("sso:account:access")) {
|
||||
if(host.getProtocol().getOAuthScopes().contains(IdentityCenterCredentialsStrategy.SSO_ACCOUNT_ACCESS_SCOPE)) {
|
||||
log.debug("Configure SSO");
|
||||
final S3CredentialsConfigurator configurator = new S3CredentialsConfigurator().reload();
|
||||
final Set<BasicProfile> profiles = configurator.getProfiles().values().stream().filter(toSsoPredicate()).collect(Collectors.toSet());
|
||||
if(!profiles.isEmpty()) {
|
||||
if(StringUtils.isBlank(host.getCredentials().getUsername())) {
|
||||
try {
|
||||
final String profile = IdentityCenterAuthorizationService.prompt(host, prompt,
|
||||
profiles.stream().map(p -> new Location.Name(p.getProfileName())).collect(Collectors.toSet()), null,
|
||||
LocaleFactory.localizedString("Select AWS CLI Profile Name", "Credentials"), null).getIdentifier();
|
||||
log.debug("Configuring credentials from profile {}", profile);
|
||||
host.setCredentials(configurator.configure(host.setCredentials(new Credentials(profile))));
|
||||
}
|
||||
catch(ConnectionCanceledException e) {
|
||||
// Continue with manual configuration
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Copy properties from AWS CLI profile
|
||||
host.setCredentials(configurator.configure(host));
|
||||
}
|
||||
}
|
||||
final OAuth2RequestInterceptor oauth = new RegisterClientOAuth2RequestInterceptor(configuration.build(), host, trust, key, prompt)
|
||||
.setFlowType(OAuth2AuthorizationService.FlowType.AuthorizationCode);
|
||||
log.debug("Add interceptor {}", oauth);
|
||||
|
||||
@@ -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<StorageObject> implements Write<StorageObject> {
|
||||
private static final Logger log = LogManager.getLogger(S3WriteFeature.class);
|
||||
@@ -77,7 +78,9 @@ public class S3WriteFeature extends AbstractHttpWriteFeature<StorageObject> 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) {
|
||||
|
||||
@@ -76,8 +76,8 @@ public class IdentityCenterAuthorizationService {
|
||||
* @param roleName The friendly name of the role that is assigned to the user.
|
||||
* @return Short-lived access tokens
|
||||
*/
|
||||
public RoleCredentials getRoleCredentials(final OAuthTokens tokens, final String region,
|
||||
@Nullable String accountId, @Nullable String roleName) throws BackgroundException {
|
||||
public IdentityCenterRoleCredentials getRoleCredentials(final OAuthTokens tokens, final String region,
|
||||
@Nullable String accountId, @Nullable String roleName) throws BackgroundException {
|
||||
final AWSSSO client = AWSSSOClient.builder()
|
||||
.withRegion(region)
|
||||
.withClientConfiguration(new CustomClientConfiguration(host,
|
||||
@@ -91,11 +91,13 @@ public class IdentityCenterAuthorizationService {
|
||||
.withNextToken(nextToken)
|
||||
.withAccessToken(tokens.getAccessToken()));
|
||||
list.addAll(result.getAccountList());
|
||||
log.debug("Retrieved account list {}", list);
|
||||
nextToken = result.getNextToken();
|
||||
}
|
||||
while(null != nextToken);
|
||||
if(list.size() == 1) {
|
||||
accountId = list.get(0).getAccountId();
|
||||
log.debug("Using default account ID {}", accountId);
|
||||
}
|
||||
else {
|
||||
accountId = prompt(host, prompt, list.stream().map(info -> new Location.Name(info.getAccountId()) {
|
||||
@@ -116,11 +118,13 @@ public class IdentityCenterAuthorizationService {
|
||||
.withAccountId(accountId)
|
||||
.withAccessToken(tokens.getAccessToken()));
|
||||
list.addAll(result.getRoleList());
|
||||
log.debug("Retrieved role list {}", list);
|
||||
nextToken = result.getNextToken();
|
||||
}
|
||||
while(null != nextToken);
|
||||
if(list.size() == 1) {
|
||||
roleName = list.get(0).getRoleName();
|
||||
log.debug("Using default role name {}", roleName);
|
||||
}
|
||||
else {
|
||||
roleName = prompt(host, prompt, list.stream().map(info -> new Location.Name(info.getRoleName())).collect(Collectors.toSet()),
|
||||
@@ -131,10 +135,12 @@ public class IdentityCenterAuthorizationService {
|
||||
log.debug("Getting role credentials for account {} and role {} with access token {}",
|
||||
accountId, roleName, tokens);
|
||||
// Gets STS role credentials using the SSO access token for a given role name that is assigned to the user.
|
||||
return client.getRoleCredentials(new GetRoleCredentialsRequest()
|
||||
final RoleCredentials result = client.getRoleCredentials(new GetRoleCredentialsRequest()
|
||||
.withAccountId(accountId)
|
||||
.withRoleName(roleName)
|
||||
.withAccessToken(tokens.getAccessToken())).getRoleCredentials();
|
||||
return new IdentityCenterRoleCredentials(accountId, roleName,
|
||||
result.getAccessKeyId(), result.getSecretAccessKey(), result.getSessionToken(), result.getExpiration());
|
||||
}
|
||||
catch(AmazonClientException e) {
|
||||
throw new AmazonServiceExceptionMappingService().map(e);
|
||||
@@ -156,4 +162,46 @@ public class IdentityCenterAuthorizationService {
|
||||
}
|
||||
return new Location.Name(value);
|
||||
}
|
||||
|
||||
public static final class IdentityCenterRoleCredentials {
|
||||
private final String accountId;
|
||||
private final String roleName;
|
||||
private final String accessKeyId;
|
||||
private final String secretAccessKey;
|
||||
private final String sessionToken;
|
||||
private final Long expiration;
|
||||
|
||||
public IdentityCenterRoleCredentials(final String accountId, final String roleName, final String accessKeyId, final String secretAccessKey, final String sessionToken, final Long expiration) {
|
||||
this.accountId = accountId;
|
||||
this.roleName = roleName;
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.secretAccessKey = secretAccessKey;
|
||||
this.sessionToken = sessionToken;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public String getAccessKeyId() {
|
||||
return accessKeyId;
|
||||
}
|
||||
|
||||
public String getSecretAccessKey() {
|
||||
return secretAccessKey;
|
||||
}
|
||||
|
||||
public String getSessionToken() {
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
public Long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,12 @@ import ch.cyberduck.core.s3.S3CredentialsStrategy;
|
||||
import ch.cyberduck.core.ssl.X509KeyManager;
|
||||
import ch.cyberduck.core.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.amazonaws.services.sso.model.RoleCredentials;
|
||||
|
||||
public class IdentityCenterCredentialsStrategy extends IdentityCenterAuthorizationService implements S3CredentialsStrategy {
|
||||
private static final Logger log = LogManager.getLogger(IdentityCenterCredentialsStrategy.class);
|
||||
|
||||
@@ -49,6 +48,11 @@ public class IdentityCenterCredentialsStrategy extends IdentityCenterAuthorizati
|
||||
private final String accountId;
|
||||
private final String roleName;
|
||||
|
||||
/**
|
||||
* A minimum scope of sso:account:access must be granted to get a refresh token back from the IAM Identity Center service.
|
||||
*/
|
||||
public static final String SSO_ACCOUNT_ACCESS_SCOPE = "sso:account:access";
|
||||
|
||||
public IdentityCenterCredentialsStrategy(final OAuth2RequestInterceptor oauth, final Host host,
|
||||
final X509TrustManager trust, final X509KeyManager key, final LoginCallback prompt) throws ConnectionCanceledException {
|
||||
super(host, trust, key, prompt);
|
||||
@@ -57,18 +61,12 @@ public class IdentityCenterCredentialsStrategy extends IdentityCenterAuthorizati
|
||||
this.region = prompt(host, prompt, host.getProtocol().getRegions(), Profile.SSO_REGION_KEY,
|
||||
LocaleFactory.localizedString(String.format("SSO Region (%s)", Profile.SSO_REGION_KEY), "Credentials"),
|
||||
host.getProperty(Profile.SSO_REGION_KEY)).getIdentifier();
|
||||
// Lookup using SSO API if explicit configuration option is missing
|
||||
this.accountId = host.getProperty(Profile.SSO_ACCOUNT_ID_KEY);
|
||||
// Lookup using SSO API if explicit configuration option is missing
|
||||
this.roleName = host.getProperty(Profile.SSO_ROLE_NAME_KEY);
|
||||
}
|
||||
|
||||
public TemporaryAccessTokens refresh(final Credentials credentials) throws BackgroundException {
|
||||
final RoleCredentials roleCredentials = this.getRoleCredentials(
|
||||
oauth.save(oauth.validate(credentials.getOauth())), region, accountId, roleName);
|
||||
log.debug("Received temporary access tokens {}", roleCredentials);
|
||||
return new TemporaryAccessTokens(roleCredentials.getAccessKeyId(),
|
||||
roleCredentials.getSecretAccessKey(), roleCredentials.getSessionToken(), roleCredentials.getExpiration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Credentials get() throws BackgroundException {
|
||||
lock.lock();
|
||||
@@ -78,7 +76,14 @@ public class IdentityCenterCredentialsStrategy extends IdentityCenterAuthorizati
|
||||
// Get temporary credentials from Identity Center
|
||||
if(tokens.isExpired()) {
|
||||
log.debug("Refresh expired tokens {} for {}", tokens, host);
|
||||
credentials.setTokens(this.refresh(credentials));
|
||||
final IdentityCenterRoleCredentials role = this.getRoleCredentials(oauth.save(oauth.validate(credentials.getOauth())),
|
||||
region, accountId, roleName);
|
||||
log.debug("Received temporary access tokens {}", role);
|
||||
credentials.setTokens(new TemporaryAccessTokens(role.getAccessKeyId(),
|
||||
role.getSecretAccessKey(), role.getSessionToken(), role.getExpiration()));
|
||||
if(StringUtils.isBlank(credentials.getUsername())) {
|
||||
credentials.setUsername(String.format("%s-%s", role.getRoleName(), role.getAccountId()));
|
||||
}
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@@ -15,27 +15,23 @@ package ch.cyberduck.core.sso;
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.Credentials;
|
||||
import ch.cyberduck.core.DefaultIOExceptionMappingService;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.OAuthTokens;
|
||||
import ch.cyberduck.core.PreferencesUseragentProvider;
|
||||
import ch.cyberduck.core.Profile;
|
||||
import ch.cyberduck.core.aws.AmazonSSOOIDCExceptionMappingService;
|
||||
import ch.cyberduck.core.aws.AmazonServiceExceptionMappingService;
|
||||
import ch.cyberduck.core.aws.CustomClientConfiguration;
|
||||
import ch.cyberduck.core.exception.BackgroundException;
|
||||
import ch.cyberduck.core.exception.ConnectionCanceledException;
|
||||
import ch.cyberduck.core.features.Location;
|
||||
import ch.cyberduck.core.oauth.OAuth2RequestInterceptor;
|
||||
import ch.cyberduck.core.s3.S3CredentialsConfigurator;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.ssl.ThreadLocalHostnameDelegatingTrustManager;
|
||||
import ch.cyberduck.core.ssl.X509KeyManager;
|
||||
import ch.cyberduck.core.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -45,11 +41,8 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.amazonaws.AmazonClientException;
|
||||
import com.amazonaws.auth.profile.internal.BasicProfile;
|
||||
import com.amazonaws.services.ssooidc.AWSSSOOIDC;
|
||||
import com.amazonaws.services.ssooidc.AWSSSOOIDCClientBuilder;
|
||||
import com.amazonaws.services.ssooidc.model.AWSSSOOIDCException;
|
||||
@@ -60,8 +53,6 @@ import com.amazonaws.services.ssooidc.model.RegisterClientResult;
|
||||
import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
|
||||
import com.google.api.client.auth.openidconnect.IdTokenResponse;
|
||||
|
||||
import static ch.cyberduck.core.s3.S3CredentialsConfigurator.toSsoPredicate;
|
||||
|
||||
public class RegisterClientOAuth2RequestInterceptor extends OAuth2RequestInterceptor {
|
||||
private static final Logger log = LogManager.getLogger(RegisterClientOAuth2RequestInterceptor.class);
|
||||
|
||||
@@ -84,23 +75,6 @@ public class RegisterClientOAuth2RequestInterceptor extends OAuth2RequestInterce
|
||||
final X509TrustManager trust, final X509KeyManager key, final LoginCallback prompt) throws ConnectionCanceledException {
|
||||
super(client, host, null, null, null, null, host.getProtocol().getOAuthScopes(), true, prompt);
|
||||
this.host = host;
|
||||
if(StringUtils.isBlank(host.getCredentials().getUsername())) {
|
||||
final S3CredentialsConfigurator configurator = new S3CredentialsConfigurator();
|
||||
configurator.reload();
|
||||
final Set<BasicProfile> profiles = configurator.getProfiles().values().stream().filter(toSsoPredicate()).collect(Collectors.toSet());
|
||||
if(!profiles.isEmpty()) {
|
||||
try {
|
||||
final String profile = IdentityCenterAuthorizationService.prompt(host, prompt,
|
||||
profiles.stream().map(p -> new Location.Name(p.getProfileName())).collect(Collectors.toSet()), null,
|
||||
LocaleFactory.localizedString("Select AWS CLI Profile Name", "Credentials"), null).getIdentifier();
|
||||
log.debug("Configuring credentials from profile {}", profile);
|
||||
host.setCredentials(configurator.configure(host.setCredentials(new Credentials(profile))));
|
||||
}
|
||||
catch(ConnectionCanceledException e) {
|
||||
// Continue with manual configuration
|
||||
}
|
||||
}
|
||||
}
|
||||
this.trust = trust;
|
||||
this.key = key;
|
||||
this.region = IdentityCenterAuthorizationService.prompt(host, prompt, host.getProtocol().getRegions(), Profile.SSO_REGION_KEY,
|
||||
@@ -131,7 +105,7 @@ public class RegisterClientOAuth2RequestInterceptor extends OAuth2RequestInterce
|
||||
Inet4Address.getLoopbackAddress().getHostAddress(), temp.getLocalPort());
|
||||
final RegisterClientResult registration = client.registerClient(new RegisterClientRequest()
|
||||
// The friendly name of the client.
|
||||
.withClientName(new PreferencesUseragentProvider().get())
|
||||
.withClientName(PreferencesFactory.get().getProperty("application.name"))
|
||||
// The service supports only public as a client type.
|
||||
.withClientType("public")
|
||||
.withIssuerUrl(issuerUrl)
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class AbstractS3Test extends VaultTest {
|
||||
public void setupDefault() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, profile.getDefaultHostname(), new Credentials(
|
||||
PROPERTIES.get("s3.key")
|
||||
));
|
||||
@@ -94,7 +94,7 @@ public abstract class AbstractS3Test extends VaultTest {
|
||||
public void setupVirtualHost() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, "test-eu-central-1-cyberduck.s3.amazonaws.com", new Credentials(
|
||||
PROPERTIES.get("s3.key")
|
||||
));
|
||||
@@ -114,7 +114,7 @@ public abstract class AbstractS3Test extends VaultTest {
|
||||
public void setupCloudFront() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, "d4fobtprygi46.cloudfront.net", new Credentials("anonymous")).setRegion("eu-central-1");
|
||||
cloudfront = new S3Session(host, new DefaultX509TrustManager(), new DefaultX509KeyManager());
|
||||
final LoginConnectionService login = new LoginConnectionService(new DisabledLoginCallback() {
|
||||
|
||||
@@ -126,7 +126,7 @@ public class S3ExceptionMappingServiceTest {
|
||||
|
||||
@Test
|
||||
public void testAlgorithmFailure() {
|
||||
assertEquals("EC AlgorithmParameters not available. Please contact your web hosting service provider for assistance.",
|
||||
assertEquals("EC AlgorithmParameters not available. The connection attempt was rejected. The server may be down, or your network may not be properly configured.",
|
||||
new S3ExceptionMappingService().map(new S3ServiceException(
|
||||
new SSLException(
|
||||
new RuntimeException(
|
||||
|
||||
@@ -77,7 +77,7 @@ public class S3ProtocolTest {
|
||||
@Test
|
||||
public void testDefaultProfile() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
assertTrue(profile.isHostnameConfigurable());
|
||||
assertFalse(profile.isPortConfigurable());
|
||||
assertTrue(profile.isUsernameConfigurable());
|
||||
|
||||
@@ -130,7 +130,7 @@ public class S3SessionTest extends AbstractS3Test {
|
||||
public void testConnectDefaultPath() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, profile.getDefaultHostname(), new Credentials(
|
||||
PROPERTIES.get("s3.key"), PROPERTIES.get("s3.secret")
|
||||
));
|
||||
@@ -145,7 +145,7 @@ public class S3SessionTest extends AbstractS3Test {
|
||||
public void testCustomHostnameUnknown() throws Exception {
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, "testu.cyberduck.ch", new Credentials(
|
||||
PROPERTIES.get("s3.key"), "s"
|
||||
));
|
||||
|
||||
@@ -122,7 +122,7 @@ public class S3SingleTransferWorkerTest extends AbstractS3Test {
|
||||
out.close();
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, profile.getDefaultHostname(), new Credentials(
|
||||
PROPERTIES.get("s3.key"), PROPERTIES.get("s3.secret")
|
||||
)) {
|
||||
@@ -206,7 +206,7 @@ public class S3SingleTransferWorkerTest extends AbstractS3Test {
|
||||
out.close();
|
||||
final ProtocolFactory factory = new ProtocolFactory(new HashSet<>(Collections.singleton(new S3Protocol())));
|
||||
final Profile profile = new ProfilePlistReader(factory).read(
|
||||
this.getClass().getResourceAsStream("/S3 (HTTPS).cyberduckprofile"));
|
||||
this.getClass().getResourceAsStream("/Amazon S3.cyberduckprofile"));
|
||||
final Host host = new Host(profile, profile.getDefaultHostname(), new Credentials(
|
||||
PROPERTIES.get("s3.key"), PROPERTIES.get("s3.secret")
|
||||
)) {
|
||||
|
||||
@@ -156,7 +156,7 @@ public class SpectraBulkService implements Bulk<Set<UUID>> {
|
||||
for(Map.Entry<TransferItem, TransferStatus> item : files.entrySet()) {
|
||||
if(container.getKey().equals(containerService.getContainer(item.getKey().remote))) {
|
||||
final TransferStatus status = item.getValue();
|
||||
final Map<String, String> parameters = new HashMap<>(status.getParameters());
|
||||
final Map<String, Object> 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<Set<UUID>> {
|
||||
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<Set<UUID>> {
|
||||
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<Set<UUID>> {
|
||||
chunk.setLength(object.getLength());
|
||||
chunk.setOffset(object.getOffset());
|
||||
// Job parameter already present from #pre
|
||||
final Map<String, String> parameters = new HashMap<>(chunk.getParameters());
|
||||
final Map<String, Object> parameters = new HashMap<>(chunk.getParameters());
|
||||
// Set offset for chunk.
|
||||
parameters.put(REQUEST_PARAMETER_OFFSET, Long.toString(chunk.getOffset()));
|
||||
chunk.setParameters(parameters);
|
||||
|
||||
@@ -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<String, Object>(),
|
||||
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);
|
||||
|
||||
+5
-18
@@ -15,20 +15,7 @@ package ch.cyberduck.core.cryptomator;
|
||||
* 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.DisabledListProgressListener;
|
||||
import ch.cyberduck.core.DisabledPasswordCallback;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.LoginOptions;
|
||||
import ch.cyberduck.core.NullFilter;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.PathAttributes;
|
||||
import ch.cyberduck.core.ProgressListener;
|
||||
import ch.cyberduck.core.TestProtocol;
|
||||
import ch.cyberduck.core.*;
|
||||
import ch.cyberduck.core.cryptomator.features.CryptoListService;
|
||||
import ch.cyberduck.core.cryptomator.features.CryptoReadFeature;
|
||||
import ch.cyberduck.core.features.AttributesFinder;
|
||||
@@ -38,7 +25,6 @@ import ch.cyberduck.core.io.StreamCopier;
|
||||
import ch.cyberduck.core.io.StreamListener;
|
||||
import ch.cyberduck.core.local.DefaultLocalDirectoryFeature;
|
||||
import ch.cyberduck.core.notification.DisabledNotificationService;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.sftp.AbstractSFTPTest;
|
||||
import ch.cyberduck.core.sftp.SFTPAttributesFinderFeature;
|
||||
import ch.cyberduck.core.sftp.SFTPDeleteFeature;
|
||||
@@ -106,13 +92,14 @@ public class CryptoSFTPSingleTransferWorkerTest extends AbstractSFTPTest {
|
||||
out2.close();
|
||||
final CryptoVault cryptomator = new CryptoVault(vault);
|
||||
cryptomator.create(session, new VaultCredentials("test"), vaultVersion);
|
||||
session.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
final DefaultVaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
@Override
|
||||
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
|
||||
return new VaultCredentials("test");
|
||||
}
|
||||
}));
|
||||
PreferencesFactory.get().setProperty("factory.vault.class", CryptoVault.class.getName());
|
||||
});
|
||||
vaults.add(cryptomator.load(session, PasswordCallback.noop));
|
||||
session.withRegistry(vaults);
|
||||
final Host host = new Host(new TestProtocol());
|
||||
final Transfer t = new UploadTransfer(host, Collections.singletonList(new TransferItem(dir1, localDirectory1)), new NullFilter<>())
|
||||
.withOptions(new UploadFilterOptions(host).withTimestamp(true));
|
||||
|
||||
@@ -64,7 +64,7 @@ public class TusWriteFeature extends AbstractHttpWriteFeature<Void> {
|
||||
final DelayedHttpEntityCallable<Void> command = new DelayedHttpEntityCallable<Void>(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();
|
||||
|
||||
@@ -117,13 +117,13 @@ public class DAVReadFeature implements Read {
|
||||
if(!status.getParameters().isEmpty()) {
|
||||
resource.append("?");
|
||||
}
|
||||
for(Map.Entry<String, String> parameter : status.getParameters().entrySet()) {
|
||||
for(Map.Entry<String, ?> 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());
|
||||
|
||||
+9
-8
@@ -24,6 +24,7 @@ import ch.cyberduck.core.Local;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.LoginOptions;
|
||||
import ch.cyberduck.core.NullFilter;
|
||||
import ch.cyberduck.core.PasswordCallback;
|
||||
import ch.cyberduck.core.Path;
|
||||
import ch.cyberduck.core.ProgressListener;
|
||||
import ch.cyberduck.core.TestProtocol;
|
||||
@@ -47,7 +48,6 @@ import ch.cyberduck.core.io.StreamCopier;
|
||||
import ch.cyberduck.core.io.StreamListener;
|
||||
import ch.cyberduck.core.local.DefaultLocalDirectoryFeature;
|
||||
import ch.cyberduck.core.notification.DisabledNotificationService;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
import ch.cyberduck.core.shared.DefaultHomeFinderService;
|
||||
import ch.cyberduck.core.transfer.DisabledTransferErrorCallback;
|
||||
import ch.cyberduck.core.transfer.DisabledTransferPrompt;
|
||||
@@ -105,13 +105,14 @@ public class CryptoDAVSingleTransferWorkerTest extends AbstractDAVTest {
|
||||
out2.close();
|
||||
final CryptoVault cryptomator = new CryptoVault(vault);
|
||||
cryptomator.create(session, new VaultCredentials("test"), vaultVersion);
|
||||
session.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
final DefaultVaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
@Override
|
||||
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
|
||||
return new VaultCredentials("test");
|
||||
}
|
||||
}));
|
||||
PreferencesFactory.get().setProperty("factory.vault.class", CryptoVault.class.getName());
|
||||
});
|
||||
vaults.add(cryptomator.load(session, PasswordCallback.noop));
|
||||
session.withRegistry(vaults);
|
||||
final Transfer t = new UploadTransfer(new Host(new TestProtocol()), Collections.singletonList(new TransferItem(dir1, localDirectory1)), new NullFilter<>());
|
||||
assertTrue(new SingleTransferWorker(session, session, t, new TransferOptions(), new TransferSpeedometer(t), new DisabledTransferPrompt() {
|
||||
@Override
|
||||
@@ -145,18 +146,18 @@ public class CryptoDAVSingleTransferWorkerTest extends AbstractDAVTest {
|
||||
|
||||
@Test
|
||||
public void testDownload() throws Exception {
|
||||
PreferencesFactory.get().setProperty("factory.vault.class", CryptoVault.class.getName());
|
||||
|
||||
final Path home = new DefaultHomeFinderService(session).find();
|
||||
final Path vault = new Path(home, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory));
|
||||
final CryptoVault cryptomator = new CryptoVault(vault);
|
||||
cryptomator.create(session, new VaultCredentials("test"), vaultVersion);
|
||||
session.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
final DefaultVaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback() {
|
||||
@Override
|
||||
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
|
||||
return new VaultCredentials("test");
|
||||
}
|
||||
}));
|
||||
});
|
||||
vaults.add(cryptomator.load(session, PasswordCallback.noop));
|
||||
session.withRegistry(vaults);
|
||||
final Path dir1 = cryptomator.getFeature(session, Directory.class, new DAVDirectoryFeature(session)).mkdir(
|
||||
cryptomator.getFeature(session, Write.class, new DAVWriteFeature(session)), new Path(vault, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory)), new TransferStatus());
|
||||
final Local localDirectory1 = new Local(System.getProperty("java.io.tmpdir"), new AlphanumericRandomStringService().random());
|
||||
|
||||
@@ -193,7 +193,14 @@ namespace Ch.Cyberduck.Ui.Controller
|
||||
|
||||
private void View_DebugLogChangedEvent()
|
||||
{
|
||||
PreferencesFactory.get().setLogging(View.DebugLog ? Level.DEBUG.toString() : Level.ERROR.toString());
|
||||
if (View.DebugLog)
|
||||
{
|
||||
PreferencesFactory.get().setLogging(Level.DEBUG.name());
|
||||
}
|
||||
else
|
||||
{
|
||||
PreferencesFactory.get().resetLogging();
|
||||
}
|
||||
}
|
||||
|
||||
private void View_SegmentedDownloadsChangedEvent()
|
||||
|
||||
Reference in New Issue
Block a user