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

Commit 4f78740

Browse files
committed
fixed metadata path in WebDAV provider, especially during directory listing
1 parent 2b5186b commit 4f78740

File tree

6 files changed

+106
-111
lines changed

6 files changed

+106
-111
lines changed
Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
package org.cryptomator.cloudaccess.webdav;
22

3-
import org.cryptomator.cloudaccess.api.CloudItemMetadata;
4-
import org.cryptomator.cloudaccess.api.CloudPath;
3+
import com.google.common.base.Splitter;
4+
import com.google.common.collect.Streams;
55

66
import java.net.URLDecoder;
77
import java.nio.charset.StandardCharsets;
88
import java.time.Instant;
99
import java.util.Optional;
1010
import java.util.regex.Pattern;
1111

12-
import static org.cryptomator.cloudaccess.api.CloudItemType.FILE;
13-
import static org.cryptomator.cloudaccess.api.CloudItemType.FOLDER;
14-
1512
class PropfindEntryData {
13+
1614
private static final Pattern URI_PATTERN = Pattern.compile("^[a-z]+://[^/]+/(.*)$");
1715

18-
private CloudPath path;
16+
private String path;
1917

20-
private boolean file = true;
18+
private boolean collection = true;
2119
private Optional<Instant> lastModified = Optional.empty();
2220
private Optional<Long> size = Optional.empty();
2321

@@ -32,52 +30,47 @@ private String extractPath(final String pathOrUri) {
3230
}
3331
}
3432

33+
private String urlDecode(final String value) {
34+
return URLDecoder.decode(value, StandardCharsets.UTF_8);
35+
}
36+
37+
public Optional<Instant> getLastModified() {
38+
return lastModified;
39+
}
40+
3541
void setLastModified(final Optional<Instant> lastModified) {
3642
this.lastModified = lastModified;
3743
}
3844

39-
public CloudPath getPath() {
45+
public String getPath() {
4046
return path;
4147
}
4248

43-
public void setPath(final String pathOrUri) {
44-
this.path = CloudPath.of(extractPath(pathOrUri));
49+
void setPath(final String pathOrUri) {
50+
this.path = extractPath(pathOrUri);
4551
}
4652

4753
public Optional<Long> getSize() {
4854
return size;
4955
}
5056

51-
public void setSize(final Optional<Long> size) {
57+
void setSize(final Optional<Long> size) {
5258
this.size = size;
5359
}
5460

55-
private boolean isFile() {
56-
return file;
61+
public boolean isCollection() {
62+
return collection;
5763
}
5864

59-
public void setFile(final boolean file) {
60-
this.file = file;
65+
void setCollection(final boolean collection) {
66+
this.collection = collection;
6167
}
6268

63-
public CloudItemMetadata toCloudItem() {
64-
if (isFile()) {
65-
return new CloudItemMetadata(getName(), path, FILE, lastModified, size);
66-
} else {
67-
return new CloudItemMetadata(getName(), path, FOLDER);
68-
}
69-
}
70-
71-
private String urlDecode(final String value) {
72-
return URLDecoder.decode(value, StandardCharsets.UTF_8);
73-
}
74-
75-
int getDepth() {
76-
return path.getNameCount();
69+
public long getDepth() {
70+
return Splitter.on("/").omitEmptyStrings().splitToStream(path).count();
7771
}
7872

79-
private String getName() {
80-
return path.getFileName().toString();
73+
public String getName() {
74+
return Streams.findLast(Splitter.on("/").omitEmptyStrings().splitToStream(path)).orElse("");
8175
}
82-
8376
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void assembleEntry() {
134134
entry.setLastModified(parseDate(lastModified));
135135
entry.setSize(parseLong(contentLength));
136136
entry.setPath(href);
137-
entry.setFile(!isCollection);
137+
entry.setCollection(isCollection);
138138

139139
entries.add(entry);
140140
}

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

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
import java.util.List;
2626
import java.util.stream.IntStream;
2727

28+
import static org.cryptomator.cloudaccess.api.CloudItemType.FILE;
29+
import static org.cryptomator.cloudaccess.api.CloudItemType.FOLDER;
30+
2831
public class WebDavClient {
2932

30-
private static final Comparator<PropfindEntryData> ASCENDING_BY_DEPTH = Comparator.comparingInt(PropfindEntryData::getDepth);
33+
private static final Comparator<PropfindEntryData> ASCENDING_BY_DEPTH = Comparator.comparingLong(PropfindEntryData::getDepth);
3134

3235
private final WebDavCompatibleHttpClient httpClient;
3336
private final URL baseUrl;
@@ -39,30 +42,30 @@ public class WebDavClient {
3942
}
4043

4144
CloudItemList list(final CloudPath folder) throws CloudProviderException {
42-
try (final var response = executePropfindRequest(folder, PROPFIND_DEPTH.ONE)) {
45+
try (final var response = executePropfindRequest(folder, PropfindDepth.ONE)) {
4346
checkExecutionSucceeded(response.code());
4447

4548
final var nodes = getEntriesFromResponse(response);
4649

47-
return processDirList(nodes);
50+
return processDirList(nodes, folder);
4851
} catch (IOException | SAXException e) {
4952
throw new CloudProviderException(e);
5053
}
5154
}
5255

5356
CloudItemMetadata itemMetadata(final CloudPath path) throws CloudProviderException {
54-
try (final var response = executePropfindRequest(path, PROPFIND_DEPTH.ZERO)) {
57+
try (final var response = executePropfindRequest(path, PropfindDepth.ZERO)) {
5558
checkExecutionSucceeded(response.code());
5659

5760
final var nodes = getEntriesFromResponse(response);
5861

59-
return processGet(nodes);
62+
return processGet(nodes, path);
6063
} catch (IOException | SAXException e) {
6164
throw new CloudProviderException(e);
6265
}
6366
}
6467

65-
private Response executePropfindRequest(final CloudPath path, final PROPFIND_DEPTH propfind_depth) throws IOException {
68+
private Response executePropfindRequest(final CloudPath path, final PropfindDepth propfindDepth) throws IOException {
6669
final var body = "<d:propfind xmlns:d=\"DAV:\">\n" //
6770
+ "<d:prop>\n" //
6871
+ "<d:resourcetype />\n" //
@@ -74,7 +77,7 @@ private Response executePropfindRequest(final CloudPath path, final PROPFIND_DEP
7477
final var builder = new Request.Builder() //
7578
.method("PROPFIND", RequestBody.create(body, MediaType.parse(body))) //
7679
.url(absoluteURLFrom(path)) //
77-
.header("DEPTH", propfind_depth.value) //
80+
.header("Depth", propfindDepth.value) //
7881
.header("Content-Type", "text/xml");
7982

8083
return httpClient.execute(builder);
@@ -86,12 +89,12 @@ private List<PropfindEntryData> getEntriesFromResponse(final Response response)
8689
}
8790
}
8891

89-
private CloudItemMetadata processGet(final List<PropfindEntryData> entryData) {
92+
private CloudItemMetadata processGet(final List<PropfindEntryData> entryData, final CloudPath path) {
9093
entryData.sort(ASCENDING_BY_DEPTH);
91-
return entryData.size() >= 1 ? entryData.get(0).toCloudItem() : null;
94+
return entryData.size() >= 1 ? toCloudItem(entryData.get(0), path) : null;
9295
}
9396

94-
private CloudItemList processDirList(final List<PropfindEntryData> entryData) {
97+
private CloudItemList processDirList(final List<PropfindEntryData> entryData, final CloudPath folder) {
9598
var result = new CloudItemList(new ArrayList<>());
9699

97100
if (entryData.isEmpty()) {
@@ -103,11 +106,19 @@ private CloudItemList processDirList(final List<PropfindEntryData> entryData) {
103106
// because it's depth is 1 smaller than the depth
104107
// ot the other entries, thus we skip the first entry
105108
for (PropfindEntryData childEntry : entryData.subList(1, entryData.size())) {
106-
result = result.add(List.of(childEntry.toCloudItem()));
109+
result = result.add(List.of(toCloudItem(childEntry, folder.resolve(childEntry.getName()))));
107110
}
108111
return result;
109112
}
110113

114+
private CloudItemMetadata toCloudItem(final PropfindEntryData data, final CloudPath path) {
115+
if (data.isCollection()) {
116+
return new CloudItemMetadata(data.getName(), path, FOLDER);
117+
} else {
118+
return new CloudItemMetadata(data.getName(), path, FILE, data.getLastModified(), data.getSize());
119+
}
120+
}
121+
111122
CloudPath move(final CloudPath from, final CloudPath to, boolean replace) throws CloudProviderException {
112123
final var builder = new Request.Builder() //
113124
.method("MOVE", null) //
@@ -142,7 +153,7 @@ InputStream read(final CloudPath path, final ProgressListener progressListener)
142153

143154
InputStream read(final CloudPath path, final long offset, final long count, final ProgressListener progressListener) throws CloudProviderException {
144155
final var getRequest = new Request.Builder() //
145-
.header("Range", String.format("bytes=%d-%d", offset, offset + count - 1))
156+
.header("Range", String.format("bytes=%d-%d", offset, offset + count - 1)) //
146157
.get() //
147158
.url(absoluteURLFrom(path));
148159
return read(getRequest, progressListener);
@@ -156,7 +167,7 @@ private InputStream read(final Request.Builder getRequest, final ProgressListene
156167
final var countingBody = new ProgressResponseWrapper(response.body(), progressListener);
157168

158169
final int UNSATISFIABLE_RANGE = 416;
159-
if(response.code() == UNSATISFIABLE_RANGE) {
170+
if (response.code() == UNSATISFIABLE_RANGE) {
160171
return new ByteArrayInputStream(new byte[0]);
161172
}
162173

@@ -178,8 +189,8 @@ CloudItemMetadata write(final CloudPath file, final boolean replace, final Input
178189
}
179190

180191
final var countingBody = new ProgressRequestWrapper(InputStreamRequestBody.from(data, size), progressListener);
181-
final var requestBuilder = new Request.Builder()
182-
.url(absoluteURLFrom(file))
192+
final var requestBuilder = new Request.Builder() //
193+
.url(absoluteURLFrom(file)) //
183194
.put(countingBody);
184195

185196
try (final var response = httpClient.execute(requestBuilder)) {
@@ -199,7 +210,7 @@ private boolean exists(CloudPath path) throws CloudProviderException {
199210
}
200211

201212
CloudPath createFolder(final CloudPath path) throws CloudProviderException {
202-
if(exists(path)) {
213+
if (exists(path)) {
203214
throw new AlreadyExistsException(String.format("Folder %s already exists", path.toString()));
204215
}
205216

@@ -228,8 +239,8 @@ void delete(final CloudPath path) throws CloudProviderException {
228239
}
229240

230241
void checkServerCompatibility() throws ServerNotWebdavCompatibleException {
231-
final var optionsRequest = new Request.Builder()
232-
.method("OPTIONS", null)
242+
final var optionsRequest = new Request.Builder() //
243+
.method("OPTIONS", null) //
233244
.url(baseUrl);
234245

235246
try (final var response = httpClient.execute(optionsRequest)) {
@@ -282,19 +293,20 @@ URL absoluteURLFrom(final CloudPath relativePath) {
282293
}
283294
}
284295

285-
private enum PROPFIND_DEPTH {
286-
ZERO("0"),
287-
ONE("1"),
296+
private enum PropfindDepth {
297+
ZERO("0"), //
298+
ONE("1"), //
288299
INFINITY("infinity");
289300

290301
private final String value;
291302

292-
PROPFIND_DEPTH(final String value) {
303+
PropfindDepth(final String value) {
293304
this.value = value;
294305
}
295306
}
296307

297308
static class WebDavAuthenticator {
309+
298310
static WebDavClient createAuthenticatedWebDavClient(final WebDavCredential webDavCredential) throws ServerNotWebdavCompatibleException, UnauthorizedException {
299311
final var webDavClient = new WebDavClient(new WebDavCompatibleHttpClient(webDavCredential), webDavCredential);
300312

0 commit comments

Comments
 (0)