diff --git a/defaults/src/main/resources/default.properties b/defaults/src/main/resources/default.properties index 11d429dea67..bf6bbc0a3cc 100644 --- a/defaults/src/main/resources/default.properties +++ b/defaults/src/main/resources/default.properties @@ -535,8 +535,9 @@ webdav.microsoftiis.header.translate=true webdav.list.handler.sax=true webdav.lock.enable=true webdav.listing.chunksize=20 -nextcloud.root.default=/remote.php/dav -owncloud.root.default=/remote.php/dav +nextcloud.root.webdav.default=remote.php/webdav +nextcloud.root.webdav.user=remote.php/dav/{0}/{1} +nextcloud.root.ocs=ocs/v1.php smb.domain.default=WORKGROUP # Enable distributed filesystem path resolver @@ -644,7 +645,6 @@ sftp.read.maxunconfirmed=64 sftp.write.maxunconfirmed=64 sftp.write.chunksize=32768 sftp.permissions.server.blacklist=OpenSSH_for_Windows -sftp.listing.chunksize=20 archive.default=tar.gz diff --git a/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeature.java b/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeature.java index 954d3ea8354..1b16c24c1aa 100644 --- a/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeature.java +++ b/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeature.java @@ -17,41 +17,29 @@ import ch.cyberduck.core.Host; import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathNormalizer; +import ch.cyberduck.core.PathRelativizer; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Home; import ch.cyberduck.core.preferences.HostPreferences; import ch.cyberduck.core.shared.AbstractHomeFeature; -import ch.cyberduck.core.shared.DefaultPathHomeFeature; import ch.cyberduck.core.shared.DelegatingHomeFeature; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.text.MessageFormat; +import java.util.Arrays; import java.util.EnumSet; public class NextcloudHomeFeature extends AbstractHomeFeature { private static final Logger log = LogManager.getLogger(NextcloudHomeFeature.class); - private final Home delegate; private final Host bookmark; - private final String root; public NextcloudHomeFeature(final Host bookmark) { - this(new DefaultPathHomeFeature(bookmark), bookmark); - } - - public NextcloudHomeFeature(final Home delegate, final Host bookmark) { - this(delegate, bookmark, new HostPreferences(bookmark).getProperty("nextcloud.root.default")); - } - - /** - * @param root WebDAV root - */ - public NextcloudHomeFeature(final Home delegate, final Host bookmark, final String root) { - this.delegate = delegate; this.bookmark = bookmark; - this.root = root; } @Override @@ -59,34 +47,131 @@ public Path find() throws BackgroundException { return this.find(Context.files); } - public Path find(final Context files) throws BackgroundException { - final String username = bookmark.getCredentials().getUsername(); - if(StringUtils.isBlank(username)) { - if(log.isWarnEnabled()) { - log.warn(String.format("Missing username for %s", bookmark)); + public Path find(final Context context) throws BackgroundException { + return context.workdir(bookmark).find(); + } + + public enum Context { + ocs { + @Override + public Home home(final Host bookmark) { + return () -> new Path(new HostPreferences(bookmark).getProperty("nextcloud.root.ocs"), EnumSet.of(Path.Type.directory)); + } + }, + files { + @Override + public Home home(final Host bookmark) throws BackgroundException { + return new DelegatingHomeFeature(new UserDavRoot(bookmark, this), new DefaultDavRoot(bookmark)); + } + + @Override + public Home workdir(final Host bookmark) throws BackgroundException { + final Path workdir = super.workdir(bookmark).find(); + return new DelegatingHomeFeature(new DefaultPathSuffix(bookmark, workdir), () -> workdir); } - return delegate.find(); + }, + versions, + meta; + + public Home home(final Host bookmark) throws BackgroundException { + return new UserDavRoot(bookmark, this); } - // Custom path setting - final Path workdir; - final Path defaultpath = new DelegatingHomeFeature(delegate).find(); - if(!defaultpath.isRoot() && StringUtils.isNotBlank(StringUtils.removeStart(defaultpath.getAbsolute(), root))) { - workdir = new Path(new Path(String.format("%s/%s/%s", root, files.name(), username), EnumSet.of(Path.Type.directory)), - StringUtils.removeStart(defaultpath.getAbsolute(), root), EnumSet.of(Path.Type.directory)); + + public Home workdir(final Host bookmark) throws BackgroundException { + final Path home = this.home(bookmark).find(); + return new DelegatingHomeFeature(new WebDavRootPrefix(bookmark, home), () -> home); } - else { - workdir = new Path(new Path(String.format("%s/%s", root, files.name()), EnumSet.of(Path.Type.directory)), - username, EnumSet.of(Path.Type.directory)); + } + + private static final class UserDavRoot implements Home { + private final Host bookmark; + private final Context context; + + public UserDavRoot(final Host bookmark, final Context context) { + this.bookmark = bookmark; + this.context = context; } - if(log.isDebugEnabled()) { - log.debug(String.format("Use home directory %s", workdir)); + + @Override + public Path find() { + final String username = bookmark.getCredentials().getUsername(); + if(StringUtils.isBlank(username)) { + return null; + } + return new Path(MessageFormat.format( + new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.user"), context.name(), username), EnumSet.of(Path.Type.directory)); } - return workdir; } - public enum Context { - files, - versions, - meta + private static final class DefaultDavRoot implements Home { + private final Host bookmark; + + public DefaultDavRoot(final Host bookmark) { + this.bookmark = bookmark; + } + + @Override + public Path find() { + return new Path(new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.default"), EnumSet.of(Path.Type.directory)); + } + } + + private static final class WebDavRootPrefix implements Home { + private final Host bookmark; + private final Path home; + + public WebDavRootPrefix(final Host bookmark, final Path home) { + this.bookmark = bookmark; + this.home = home; + } + + @Override + public Path find() { + if(StringUtils.isNotBlank(bookmark.getDefaultPath())) { + for(String s : Arrays.asList(home.getAbsolute(), new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.default"), + MessageFormat.format(new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.user"), Context.files.name(), + bookmark.getCredentials().getUsername()))) { + if(StringUtils.contains(bookmark.getDefaultPath(), PathNormalizer.normalize(s))) { + final String prefix = StringUtils.substringBefore(bookmark.getDefaultPath(), PathNormalizer.normalize(s)); + if(StringUtils.isBlank((prefix))) { + return null; + } + return PathNormalizer.compose(new Path(prefix, EnumSet.of(Path.Type.directory)), + PathRelativizer.relativize(String.valueOf(Path.DELIMITER), home.getAbsolute())); + } + } + } + return null; + } + } + + private static final class DefaultPathSuffix implements Home { + private final Host bookmark; + private final Path home; + + public DefaultPathSuffix(final Host bookmark, final Path home) { + this.bookmark = bookmark; + this.home = home; + } + + @Override + public Path find() { + if(StringUtils.isNotBlank(bookmark.getDefaultPath())) { + for(String s : Arrays.asList(home.getAbsolute(), new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.default"), + MessageFormat.format(new HostPreferences(bookmark).getProperty("nextcloud.root.webdav.user"), Context.files.name(), + bookmark.getCredentials().getUsername()))) { + if(StringUtils.contains(bookmark.getDefaultPath(), s)) { + final String suffix = StringUtils.substringAfter(bookmark.getDefaultPath(), s); + if(StringUtils.isBlank((suffix))) { + return null; + } + return PathNormalizer.compose(home, PathRelativizer.relativize(String.valueOf(Path.DELIMITER), suffix)); + } + } + return PathNormalizer.compose(home, PathRelativizer.relativize(String.valueOf(Path.DELIMITER), + PathNormalizer.normalize(bookmark.getDefaultPath()))); + } + return null; + } } } diff --git a/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudShareFeature.java b/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudShareFeature.java index ebd2ca513ca..81c452832c4 100644 --- a/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudShareFeature.java +++ b/nextcloud/src/main/java/ch/cyberduck/core/nextcloud/NextcloudShareFeature.java @@ -90,8 +90,8 @@ public Set getSharees(final Type type) throws BackgroundException { return Collections.singleton(Sharee.world); default: final Host bookmark = session.getHost(); - final StringBuilder request = new StringBuilder(String.format("https://%s/ocs/v1.php/apps/files_sharing/api/v1/sharees?lookup=true&shareType=%d&itemType=file", - bookmark.getHostname(), + final StringBuilder request = new StringBuilder(String.format("https://%s%s/apps/files_sharing/api/v1/sharees?lookup=true&shareType=%d&itemType=file", + bookmark.getHostname(), new NextcloudHomeFeature(bookmark).find(NextcloudHomeFeature.Context.ocs).getAbsolute(), SHARE_TYPE_USER // User )); final HttpGet resource = new HttpGet(request.toString()); @@ -120,10 +120,9 @@ public Set getSharees(final Type type) throws BackgroundException { @Override public DescriptiveUrl toDownloadUrl(final Path file, final Sharee sharee, final Object options, final PasswordCallback callback) throws BackgroundException { final Host bookmark = session.getHost(); - final StringBuilder request = new StringBuilder(String.format("https://%s/ocs/v1.php/apps/files_sharing/api/v1/shares?path=%s&shareType=%d&shareWith=%s", - bookmark.getHostname(), - URIEncoder.encode(PathRelativizer.relativize(bookmark.getProtocol().getDefaultPath(), - PathRelativizer.relativize(new NextcloudHomeFeature(bookmark).find().getAbsolute(), file.getAbsolute()))), + final StringBuilder request = new StringBuilder(String.format("https://%s%s/apps/files_sharing/api/v1/shares?path=%s&shareType=%d&shareWith=%s", + bookmark.getHostname(), new NextcloudHomeFeature(bookmark).find(NextcloudHomeFeature.Context.ocs).getAbsolute(), + URIEncoder.encode(PathRelativizer.relativize(NextcloudHomeFeature.Context.files.home(bookmark).find().getAbsolute(), file.getAbsolute())), Sharee.world.equals(sharee) ? SHARE_TYPE_PUBLIC_LINK : SHARE_TYPE_USER, Sharee.world.equals(sharee) ? StringUtils.EMPTY : sharee.getIdentifier() )); @@ -152,10 +151,9 @@ public DescriptiveUrl toDownloadUrl(final Path file, final Sharee sharee, final @Override public DescriptiveUrl toUploadUrl(final Path file, final Sharee sharee, final Object options, final PasswordCallback callback) throws BackgroundException { final Host bookmark = session.getHost(); - final StringBuilder request = new StringBuilder(String.format("https://%s/ocs/v1.php/apps/files_sharing/api/v1/shares?path=%s&shareType=%d&permissions=%d", - bookmark.getHostname(), - URIEncoder.encode(PathRelativizer.relativize(bookmark.getProtocol().getDefaultPath(), - PathRelativizer.relativize(new NextcloudHomeFeature(bookmark).find().getAbsolute(), file.getAbsolute()))), + final StringBuilder request = new StringBuilder(String.format("https://%s%s/apps/files_sharing/api/v1/shares?path=%s&shareType=%d&permissions=%d", + bookmark.getHostname(), new NextcloudHomeFeature(bookmark).find(NextcloudHomeFeature.Context.ocs).getAbsolute(), + URIEncoder.encode(PathRelativizer.relativize(NextcloudHomeFeature.Context.files.home(bookmark).find().getAbsolute(), file.getAbsolute())), Sharee.world.equals(sharee) ? SHARE_TYPE_PUBLIC_LINK : SHARE_TYPE_USER, SHARE_PERMISSIONS_CREATE )); diff --git a/nextcloud/src/main/java/ch/cyberduck/core/ocs/OcsCapabilitiesRequest.java b/nextcloud/src/main/java/ch/cyberduck/core/ocs/OcsCapabilitiesRequest.java index 0dadb966d2b..13bd3fb4940 100644 --- a/nextcloud/src/main/java/ch/cyberduck/core/ocs/OcsCapabilitiesRequest.java +++ b/nextcloud/src/main/java/ch/cyberduck/core/ocs/OcsCapabilitiesRequest.java @@ -16,6 +16,8 @@ */ import ch.cyberduck.core.Host; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.nextcloud.NextcloudHomeFeature; import org.apache.http.HttpHeaders; import org.apache.http.client.methods.HttpGet; @@ -23,9 +25,9 @@ public class OcsCapabilitiesRequest extends HttpGet { - public OcsCapabilitiesRequest(final Host host) { - super(new StringBuilder(String.format("https://%s/ocs/v1.php/cloud/capabilities", - host.getHostname() + public OcsCapabilitiesRequest(final Host host) throws BackgroundException { + super(new StringBuilder(String.format("https://%s%s/cloud/capabilities", + host.getHostname(), new NextcloudHomeFeature(host).find(NextcloudHomeFeature.Context.ocs).getAbsolute() )).toString()); this.setHeader("OCS-APIRequest", "true"); this.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_XML.getMimeType()); diff --git a/nextcloud/src/test/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeatureTest.java b/nextcloud/src/test/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeatureTest.java index 36248f59d13..2482bb4d7e6 100644 --- a/nextcloud/src/test/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeatureTest.java +++ b/nextcloud/src/test/java/ch/cyberduck/core/nextcloud/NextcloudHomeFeatureTest.java @@ -24,28 +24,83 @@ import java.util.EnumSet; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; public class NextcloudHomeFeatureTest { @Test - public void testFind() throws Exception { - final Host bookmark = new Host(new NextcloudProtocol()); + public void testFindNoUsername() throws Exception { + final NextcloudHomeFeature feature = new NextcloudHomeFeature(new Host(new NextcloudProtocol())); + { + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/webdav", EnumSet.of(Path.Type.directory)), feature.find()); + assertEquals(new Path("/remote.php/webdav", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + assertEquals(new Path("/", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.meta)); + assertEquals(new Path("/", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.versions)); + } + } + + @Test + public void testFindWithUsername() throws Exception { + final NextcloudHomeFeature feature = new NextcloudHomeFeature(new Host(new NextcloudProtocol(), new Credentials("u"))); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + assertEquals(new Path("/remote.php/dav/meta/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.meta)); + assertEquals(new Path("/remote.php/dav/versions/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.versions)); + } + + @Test + public void testFindWithDefaultPath() throws Exception { + final Host bookmark = new Host(new NextcloudProtocol(), new Credentials("u")); final NextcloudHomeFeature feature = new NextcloudHomeFeature(bookmark); - assertNull(feature.find()); - bookmark.setCredentials(new Credentials("u")); - assertEquals(new Path("/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/remote.php/dav/"); - assertEquals(new Path("/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/remote.php/dav"); - assertEquals(new Path("/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/remote.php/dav/d"); - assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/remote.php/dav/d/"); - assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/d"); - assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find()); - bookmark.setDefaultPath("/d/"); - assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find()); + for(String s : variants("remote.php/webdav")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + } + for(String s : variants("remote.php/webdav/d")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + assertEquals(new Path("/remote.php/dav/meta/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.meta)); + assertEquals(new Path("/remote.php/dav/versions/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.versions)); + } + for(String s : variants("remote.php/dav/files/u")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u/", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + assertEquals(new Path("/remote.php/dav/meta/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.meta)); + assertEquals(new Path("/remote.php/dav/versions/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.versions)); + } + for(String s : variants("remote.php/dav/files/u/d")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + assertEquals(new Path("/remote.php/dav/meta/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.meta)); + assertEquals(new Path("/remote.php/dav/versions/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.versions)); + } + for(String s : variants("d")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + } + for(String s : variants("w/remote.php/webdav")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/w/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/w/remote.php/dav/files/u", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + } + for(String s : variants("w/remote.php/webdav/d")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/w/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/w/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + } + for(String s : variants("w/remote.php/dav/files/u/d")) { + bookmark.setDefaultPath(s); + assertEquals(new Path("/w/ocs/v1.php", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.ocs)); + assertEquals(new Path("/w/remote.php/dav/files/u/d", EnumSet.of(Path.Type.directory)), feature.find(NextcloudHomeFeature.Context.files)); + } + } + + static String[] variants(final String test) { + return new String[]{test, test + "/", "/" + test, "/" + test + "/"}; } } \ No newline at end of file diff --git a/owncloud/src/main/java/ch/cyberduck/core/owncloud/OwncloudHomeFeature.java b/owncloud/src/main/java/ch/cyberduck/core/owncloud/OwncloudHomeFeature.java index 1e60822e811..dd8bde435f4 100644 --- a/owncloud/src/main/java/ch/cyberduck/core/owncloud/OwncloudHomeFeature.java +++ b/owncloud/src/main/java/ch/cyberduck/core/owncloud/OwncloudHomeFeature.java @@ -16,12 +16,7 @@ */ import ch.cyberduck.core.Host; -import ch.cyberduck.core.Path; -import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.features.Home; import ch.cyberduck.core.nextcloud.NextcloudHomeFeature; -import ch.cyberduck.core.preferences.HostPreferences; -import ch.cyberduck.core.shared.DefaultPathHomeFeature; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,22 +25,6 @@ public class OwncloudHomeFeature extends NextcloudHomeFeature { private static final Logger log = LogManager.getLogger(OwncloudHomeFeature.class); public OwncloudHomeFeature(final Host bookmark) { - this(new DefaultPathHomeFeature(bookmark), bookmark); - } - - public OwncloudHomeFeature(final Home delegate, final Host bookmark) { - this(delegate, bookmark, new HostPreferences(bookmark).getProperty("owncloud.root.default")); - } - - public OwncloudHomeFeature(final Home delegate, final Host bookmark, final String root) { - super(delegate, bookmark, root); - } - - public Path find(final Context context) throws BackgroundException { - switch(context) { - case versions: - return super.find(Context.meta); - } - return super.find(context); + super(bookmark); } }