Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

Commit ef05825

Browse files
Merge branch 'develop' into release/0.1.0
2 parents b15fe9c + f228b53 commit ef05825

File tree

5 files changed

+78
-31
lines changed

5 files changed

+78
-31
lines changed

src/main/java/org/cryptomator/cloudaccess/CloudAccess.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
package org.cryptomator.cloudaccess;
22

3+
import java.net.URL;
34
import java.nio.file.Path;
45

56
import org.cryptomator.cloudaccess.api.CloudProvider;
67
import org.cryptomator.cloudaccess.localfs.LocalFsCloudProvider;
8+
import org.cryptomator.cloudaccess.webdav.WebDavCloudProvider;
9+
import org.cryptomator.cloudaccess.webdav.WebDavCredential;
710

811
public class CloudAccess {
912

1013
private CloudAccess() {
1114
}
1215

16+
/**
17+
* Creates a new CloudProvider which provides access to the given URL via WebDAV.
18+
*
19+
* @param url Base URL leading to the root resource
20+
* @param username Username used during basic or digest auth challenges
21+
* @param password Password used during basic or digest auth challenges
22+
* @return A cloud access provider that provides access to the given WebDAV URL.
23+
*/
24+
public static CloudProvider toWebDAV(URL url, String username, CharSequence password) {
25+
// TODO can we pass though CharSequence to the auth mechanism?
26+
return WebDavCloudProvider.from(WebDavCredential.from(url, username, password.toString()));
27+
}
28+
1329
/**
1430
* Creates a new CloudProvider which provides access to the given <code>folder</code>. Mainly for test purposes.
1531
*

src/main/java/org/cryptomator/cloudaccess/webdav/WebDavClient.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
import java.io.IOException;
1717
import java.io.InputStream;
1818
import java.net.HttpURLConnection;
19+
import java.net.MalformedURLException;
20+
import java.net.URL;
1921
import java.nio.file.Path;
2022
import java.util.ArrayList;
2123
import java.util.Comparator;
2224
import java.util.List;
25+
import java.util.stream.IntStream;
26+
import java.util.stream.Stream;
2327

2428
public class WebDavClient {
2529

2630
private final WebDavCompatibleHttpClient httpClient;
27-
private final Path baseUrl;
31+
private final URL baseUrl;
2832
private final int HTTP_INSUFFICIENT_STORAGE = 507;
2933

3034
private enum PROPFIND_DEPTH {
@@ -98,7 +102,7 @@ private Response executePropfindRequest(final Path path, final PROPFIND_DEPTH pr
98102

99103
final var builder = new Request.Builder() //
100104
.method("PROPFIND", RequestBody.create(body, MediaType.parse(body))) //
101-
.url(absolutePathFrom(path)) //
105+
.url(absoluteURLFrom(path)) //
102106
.header("DEPTH", propfind_depth.value) //
103107
.header("Content-Type", "text/xml");
104108

@@ -138,8 +142,8 @@ private CloudItemList processDirList(final List<PropfindEntryData> entryData) {
138142
Path move(final Path from, final Path to, boolean replace) throws CloudProviderException {
139143
final var builder = new Request.Builder() //
140144
.method("MOVE", null) //
141-
.url(absolutePathFrom(from)) //
142-
.header("Destination", absolutePathFrom(to)) //
145+
.url(absoluteURLFrom(from)) //
146+
.header("Destination", absoluteURLFrom(to).toExternalForm()) //
143147
.header("Content-Type", "text/xml") //
144148
.header("Depth", "infinity");
145149

@@ -149,7 +153,7 @@ Path move(final Path from, final Path to, boolean replace) throws CloudProviderE
149153

150154
try (final var response = httpClient.execute(builder)) {
151155
if (response.code() == HttpURLConnection.HTTP_PRECON_FAILED) {
152-
throw new AlreadyExistsException(absolutePathFrom(to));
156+
throw new AlreadyExistsException(absoluteURLFrom(to).toExternalForm());
153157
}
154158

155159
checkExecutionSucceeded(response.code());
@@ -163,15 +167,15 @@ Path move(final Path from, final Path to, boolean replace) throws CloudProviderE
163167
InputStream read(final Path path, final ProgressListener progressListener) throws CloudProviderException {
164168
final var getRequest = new Request.Builder() //
165169
.get() //
166-
.url(absolutePathFrom(path));
170+
.url(absoluteURLFrom(path));
167171
return read(getRequest, progressListener);
168172
}
169173

170174
InputStream read(final Path path, final long offset, final long count, final ProgressListener progressListener) throws CloudProviderException {
171175
final var getRequest = new Request.Builder() //
172176
.header("Range", String.format("bytes=%d-%d", offset, offset + count - 1))
173177
.get() //
174-
.url(absolutePathFrom(path));
178+
.url(absoluteURLFrom(path));
175179
return read(getRequest, progressListener);
176180
}
177181

@@ -193,7 +197,7 @@ CloudItemMetadata write(final Path file, final boolean replace, final InputStrea
193197

194198
final var countingBody = new ProgressRequestWrapper(InputStreamRequestBody.from(data), progressListener);
195199
final var requestBuilder = new Request.Builder()
196-
.url(absolutePathFrom(file))
200+
.url(absoluteURLFrom(file))
197201
.put(countingBody);
198202

199203
try (final var response = httpClient.execute(requestBuilder)) {
@@ -215,7 +219,7 @@ private boolean exists(Path path) throws CloudProviderException {
215219
Path createFolder(final Path path) throws CloudProviderException {
216220
final var builder = new Request.Builder() //
217221
.method("MKCOL", null) //
218-
.url(absolutePathFrom(path));
222+
.url(absoluteURLFrom(path));
219223

220224
try (final var response = httpClient.execute(builder)) {
221225
checkExecutionSucceeded(response.code());
@@ -228,7 +232,7 @@ Path createFolder(final Path path) throws CloudProviderException {
228232
void delete(final Path path) throws CloudProviderException {
229233
final var builder = new Request.Builder() //
230234
.delete() //
231-
.url(absolutePathFrom(path));
235+
.url(absoluteURLFrom(path));
232236

233237
try (final var response = httpClient.execute(builder)) {
234238
checkExecutionSucceeded(response.code());
@@ -240,7 +244,7 @@ void delete(final Path path) throws CloudProviderException {
240244
void checkServerCompatibility() throws ServerNotWebdavCompatibleException {
241245
final var optionsRequest = new Request.Builder()
242246
.method("OPTIONS", null)
243-
.url(baseUrl.toString());
247+
.url(baseUrl);
244248

245249
try (final var response = httpClient.execute(optionsRequest)) {
246250
checkExecutionSucceeded(response.code());
@@ -281,9 +285,15 @@ private void checkExecutionSucceeded(final int status) throws CloudProviderExcep
281285
}
282286
}
283287

284-
private String absolutePathFrom(final Path relativePath) {
285-
// TODO improve path appending
286-
return baseUrl.toString() + relativePath.toString();
288+
// visible for testing
289+
URL absoluteURLFrom(final Path relativePath) {
290+
var basePath = Path.of(baseUrl.getPath());
291+
var fullPath = IntStream.range(0, relativePath.getNameCount()).mapToObj(i -> relativePath.getName(i)).reduce(basePath, Path::resolve);
292+
try {
293+
return new URL(baseUrl, fullPath.toString());
294+
} catch (MalformedURLException e) {
295+
throw new IllegalArgumentException("The relative path contains invalid URL elements.");
296+
}
287297
}
288298

289299
}

src/main/java/org/cryptomator/cloudaccess/webdav/WebDavCredential.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package org.cryptomator.cloudaccess.webdav;
22

3-
import java.nio.file.Path;
3+
import java.net.URL;
44

55
public class WebDavCredential {
66

7-
private final Path baseUrl;
7+
private final URL baseUrl;
88
private final String username;
99
private final String password;
1010

11-
public static WebDavCredential from(final Path baseUrl, final String username, final String password) {
11+
public static WebDavCredential from(final URL baseUrl, final String username, final String password) {
1212
return new WebDavCredential(baseUrl, username, password);
1313
}
1414

15-
private WebDavCredential(final Path baseUrl, final String username, final String password) {
15+
private WebDavCredential(final URL baseUrl, final String username, final String password) {
1616
this.baseUrl = baseUrl;
1717
this.username = username;
1818
this.password = password;
1919
}
2020

21-
public Path getBaseUrl() {
21+
public URL getBaseUrl() {
2222
return baseUrl;
2323
}
2424

src/test/java/org/cryptomator/cloudaccess/webdav/WebDavClientTest.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
import org.junit.jupiter.api.BeforeEach;
1414
import org.junit.jupiter.api.DisplayName;
1515
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.CsvSource;
1618
import org.mockito.ArgumentMatchers;
1719
import org.mockito.Mockito;
1820

1921
import java.io.BufferedReader;
2022
import java.io.IOException;
2123
import java.io.InputStream;
2224
import java.io.InputStreamReader;
25+
import java.net.MalformedURLException;
26+
import java.net.URL;
2327
import java.nio.charset.StandardCharsets;
2428
import java.nio.file.Path;
2529
import java.util.List;
@@ -31,7 +35,7 @@ public class WebDavClientTest {
3135
private final WebDavCompatibleHttpClient webDavCompatibleHttpClient = Mockito.mock(WebDavCompatibleHttpClient.class);
3236
private WebDavClient webDavClient;
3337

34-
private final Path baseUrl = Path.of("https://www.nextcloud.com/cloud/remote.php/webdav");
38+
private URL baseUrl;
3539

3640
private final CloudItemMetadata testFolderDocuments = new CloudItemMetadata("Documents", Path.of("/cloud/remote.php/webdav/Documents"), CloudItemType.FOLDER, Optional.empty(), Optional.empty());
3741
private final CloudItemMetadata testFileManual = new CloudItemMetadata("Nextcloud Manual.pdf", Path.of("/cloud/remote.php/webdav/Nextcloud Manual.pdf"), CloudItemType.FILE, Optional.of(TestUtil.toInstant("Thu, 19 Feb 2020 10:24:12 GMT")), Optional.of(6837751L));
@@ -40,11 +44,27 @@ public class WebDavClientTest {
4044
private final CloudItemMetadata testFolderPhotos = new CloudItemMetadata("Photos", Path.of("/cloud/remote.php/webdav/Photos"), CloudItemType.FOLDER, Optional.empty(), Optional.empty());
4145

4246
@BeforeEach
43-
public void setup() {
47+
public void setup() throws MalformedURLException {
48+
baseUrl = new URL("https://www.nextcloud.com/cloud/remote.php/webdav");
4449
final var webDavCredential = WebDavCredential.from(baseUrl, "foo", "bar");
4550
webDavClient = new WebDavClient(webDavCompatibleHttpClient, webDavCredential);
4651
}
4752

53+
@ParameterizedTest(name = "absoluteURLFrom(\"{0}\") == {1}")
54+
@DisplayName("absoluteURLFrom(...)")
55+
@CsvSource(value = {
56+
"'',/cloud/remote.php/webdav",
57+
"/,/cloud/remote.php/webdav",
58+
"/foo,/cloud/remote.php/webdav/foo",
59+
"foo/bar,/cloud/remote.php/webdav/foo/bar",
60+
"//foo///bar/baz,/cloud/remote.php/webdav/foo/bar/baz",
61+
})
62+
public void testAbsoluteURLFrom(String absPath, String expectedResult) {
63+
var result = webDavClient.absoluteURLFrom(Path.of(absPath));
64+
65+
Assertions.assertEquals(expectedResult, result.getPath());
66+
}
67+
4868
@Test
4969
@DisplayName("get metadata of /Nextcloud Manual.pdf")
5070
public void testItemMetadata() throws IOException {
@@ -268,18 +288,18 @@ public void testTryAuthenticatedRequest() throws IOException {
268288
Assertions.assertThrows(UnauthorizedException.class, () -> webDavClient.tryAuthenticatedRequest());
269289
}
270290

271-
private Response getInterceptedResponse(final Path path, final String testResource) {
272-
return getInterceptedResponse(path, 200, load(testResource));
291+
private Response getInterceptedResponse(final URL url, final String testResource) {
292+
return getInterceptedResponse(url, 200, load(testResource));
273293
}
274294

275-
private Response getInterceptedResponse(final Path path) {
276-
return getInterceptedResponse(path, 201, "");
295+
private Response getInterceptedResponse(final URL url) {
296+
return getInterceptedResponse(url, 201, "");
277297
}
278298

279-
private Response getInterceptedResponse(final Path path, int httpCode, final String body) {
299+
private Response getInterceptedResponse(final URL url, int httpCode, final String body) {
280300
return new Response.Builder()
281301
.request(new Request.Builder()
282-
.url(path.toString())
302+
.url(url)
283303
.build())
284304
.protocol(Protocol.HTTP_1_1)
285305
.code(httpCode)

src/test/java/org/cryptomator/cloudaccess/webdav/WebDavCloudProviderTestIT.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.io.BufferedReader;
1818
import java.io.IOException;
1919
import java.io.InputStreamReader;
20+
import java.net.URL;
2021
import java.nio.charset.StandardCharsets;
2122
import java.nio.file.Path;
2223
import java.util.List;
@@ -28,7 +29,7 @@ public class WebDavCloudProviderTestIT {
2829

2930
private final MockWebServer server;
3031
private final CloudProvider provider;
31-
private final Path baseUrl;
32+
private final URL baseUrl;
3233

3334
private final CloudItemMetadata testFolderDocuments = new CloudItemMetadata("Documents", Path.of("/cloud/remote.php/webdav/Documents"), CloudItemType.FOLDER, Optional.empty(), Optional.empty());
3435
private final CloudItemMetadata testFileManual = new CloudItemMetadata("Nextcloud Manual.pdf", Path.of("/cloud/remote.php/webdav/Nextcloud Manual.pdf"), CloudItemType.FILE, Optional.of(TestUtil.toInstant("Thu, 19 Feb 2020 10:24:12 GMT")), Optional.of(6837751L));
@@ -42,7 +43,7 @@ public WebDavCloudProviderTestIT() throws IOException, InterruptedException {
4243
server = new MockWebServer();
4344
server.start();
4445

45-
baseUrl = Path.of(String.format("http://%s:%s/cloud/remote.php/webdav", server.getHostName(), server.getPort()));
46+
baseUrl = new URL("http", server.getHostName(), server.getPort(), "/cloud/remote.php/webdav");
4647

4748
final var response = getInterceptedResponse("item-meta-data-response.xml");
4849
server.enqueue(response);
@@ -88,7 +89,7 @@ public void testList() throws InterruptedException {
8889
RecordedRequest rq = server.takeRequest();
8990
Assertions.assertEquals("PROPFIND", rq.getMethod());
9091
Assertions.assertEquals("1", rq.getHeader("DEPTH"));
91-
Assertions.assertEquals("/cloud/remote.php/webdav/", rq.getPath());
92+
Assertions.assertEquals("/cloud/remote.php/webdav", rq.getPath());
9293
Assertions.assertEquals(webDavRequestBody, rq.getBody().readUtf8());
9394
}
9495

@@ -115,7 +116,7 @@ public void testListExhaustively() throws InterruptedException {
115116
RecordedRequest rq = server.takeRequest();
116117
Assertions.assertEquals("PROPFIND", rq.getMethod());
117118
Assertions.assertEquals("infinity", rq.getHeader("DEPTH"));
118-
Assertions.assertEquals("/cloud/remote.php/webdav/", rq.getPath());
119+
Assertions.assertEquals("/cloud/remote.php/webdav", rq.getPath());
119120
Assertions.assertEquals(webDavRequestBody, rq.getBody().readUtf8());
120121
}
121122

0 commit comments

Comments
 (0)