Skip to content

Commit eba5f29

Browse files
committed
Remove conflicting c9u files on directory listing
1 parent ebe8c6e commit eba5f29

File tree

7 files changed

+147
-4
lines changed

7 files changed

+147
-4
lines changed

src/main/java/org/cryptomator/cryptofs/common/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*******************************************************************************/
99
package org.cryptomator.cryptofs.common;
1010

11+
import java.util.regex.Pattern;
12+
1113
public final class Constants {
1214

1315
private Constants() {
@@ -38,4 +40,6 @@ private Constants() {
3840
public static final String RECOVERY_DIR_NAME = "LOST+FOUND";
3941
public static final int INUSE_DELAY_MILLIS = 5000;
4042
public static final int INUSE_CLEARTEXT_SIZE = 1000; //calculation: Create inUse properties with owner consisting of \u2741.repeat(100) and encode it. Plus an additional buffer for future entries.
43+
44+
public static final Pattern BASE64_PATTERN = Pattern.compile("[a-zA-Z0-9-_]{20}(?:[a-zA-Z0-9-_]{4})*(?:[a-zA-Z0-9-_]{4}|[a-zA-Z0-9-_]{3}=|[a-zA-Z0-9-_]{2}==)");
4145
}

src/main/java/org/cryptomator/cryptofs/dir/C9rDecryptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
import java.util.regex.Pattern;
1717
import java.util.stream.Stream;
1818

19+
import static org.cryptomator.cryptofs.common.Constants.BASE64_PATTERN;
20+
1921
@DirectoryStreamScoped
2022
class C9rDecryptor {
2123

2224
// visible for testing:
23-
static final Pattern BASE64_PATTERN = Pattern.compile("[a-zA-Z0-9-_]{20}(?:[a-zA-Z0-9-_]{4})*(?:[a-zA-Z0-9-_]{4}|[a-zA-Z0-9-_]{3}=|[a-zA-Z0-9-_]{2}==)");
2425
private static final CharMatcher DELIM_MATCHER = CharMatcher.anyOf("_-");
2526

2627
private final Cryptor cryptor;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.cryptomator.cryptofs.dir;
2+
3+
import jakarta.inject.Inject;
4+
import org.cryptomator.cryptofs.common.Constants;
5+
import org.cryptomator.cryptofs.common.StringUtils;
6+
import org.cryptomator.cryptofs.inuse.InUseManager;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import java.io.IOException;
11+
import java.nio.file.Files;
12+
import java.util.regex.Matcher;
13+
import java.util.stream.Stream;
14+
15+
import static org.cryptomator.cryptofs.common.Constants.BASE64_PATTERN;
16+
17+
/**
18+
* Resolves in-use file conflicts.
19+
*/
20+
@DirectoryStreamScoped
21+
public class C9uConflictResolver {
22+
23+
private static final Logger LOG = LoggerFactory.getLogger(C9uConflictResolver.class);
24+
25+
26+
private final InUseManager inUseManager;
27+
28+
@Inject
29+
public C9uConflictResolver(InUseManager inUseManager) {
30+
this.inUseManager = inUseManager;
31+
}
32+
33+
/**
34+
* Processes files with {@value Constants#INUSE_FILE_SUFFIX} file extension. (in-use files)
35+
* <p>
36+
* If the in-use file is not valid bas64 encoding, delete the file.
37+
*
38+
* @param node
39+
* @return an empty stream.
40+
*/
41+
Stream<Node> process(Node node) {
42+
String basename = StringUtils.removeEnd(node.fullCiphertextFileName, Constants.INUSE_FILE_SUFFIX);
43+
Matcher matcher = BASE64_PATTERN.matcher(basename);
44+
matcher.region(0, basename.length());
45+
if (!matcher.matches()) { //any rename is considered bad
46+
//TODO: close UseToken (if existent)
47+
LOG.debug("Found renamed in-use-file {}. Deleting it.", node.ciphertextPath);
48+
try {
49+
Files.deleteIfExists(node.ciphertextPath);
50+
} catch (IOException e) {
51+
LOG.debug("Failed to delete in-use-file {}. Retry on next directory listing.", node.ciphertextPath);
52+
}
53+
}
54+
return Stream.empty();
55+
}
56+
57+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.cryptomator.cryptofs.dir;
2+
3+
import jakarta.inject.Inject;
4+
import org.cryptomator.cryptofs.common.Constants;
5+
6+
import java.util.stream.Stream;
7+
8+
/**
9+
* Processes in-use files (file extension {@value Constants#INUSE_FILE_SUFFIX}.
10+
*/
11+
@DirectoryStreamScoped
12+
public class C9uProcessor {
13+
14+
private final C9uConflictResolver conflictRemover;
15+
16+
@Inject
17+
public C9uProcessor(C9uConflictResolver conflictRemover) {
18+
this.conflictRemover = conflictRemover;
19+
}
20+
21+
public Stream<Node> process(Node node) {
22+
return conflictRemover.process(node);
23+
}
24+
}

src/main/java/org/cryptomator/cryptofs/dir/NodeProcessor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ class NodeProcessor {
1010

1111
private final C9rProcessor c9rProcessor;
1212
private final C9sProcessor c9sProcessor;
13+
private final C9uProcessor c9uProcessor;
1314
private final BrokenDirectoryFilter brokenDirFilter;
1415

1516
@Inject
16-
public NodeProcessor(C9rProcessor c9rProcessor, C9sProcessor c9sProcessor, BrokenDirectoryFilter brokenDirFilter){
17+
public NodeProcessor(C9rProcessor c9rProcessor, C9sProcessor c9sProcessor, C9uProcessor c9uProcessor, BrokenDirectoryFilter brokenDirFilter){
1718
this.c9rProcessor = c9rProcessor;
1819
this.c9sProcessor = c9sProcessor;
20+
this.c9uProcessor = c9uProcessor;
1921
this.brokenDirFilter = brokenDirFilter;
2022
}
2123

@@ -24,6 +26,8 @@ public Stream<Node> process(Node node) {
2426
return c9rProcessor.process(node).flatMap(brokenDirFilter::process);
2527
} else if (node.fullCiphertextFileName.endsWith(Constants.DEFLATED_FILE_SUFFIX)) {
2628
return c9sProcessor.process(node).flatMap(brokenDirFilter::process);
29+
} else if (node.fullCiphertextFileName.endsWith(Constants.INUSE_FILE_SUFFIX)) {
30+
return c9uProcessor.process(node).flatMap(brokenDirFilter::process);
2731
} else {
2832
return Stream.empty();
2933
}

src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.cryptomator.cryptofs.dir;
22

3+
import org.cryptomator.cryptofs.common.Constants;
34
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
45
import org.cryptomator.cryptolib.api.Cryptor;
56
import org.cryptomator.cryptolib.api.FileNameCryptor;
@@ -38,7 +39,7 @@ public void setup() {
3839
"aaaaBBBBccccDDDDeeeeFFFFggggHH==",
3940
})
4041
public void testValidBase64Pattern(String input) {
41-
Assertions.assertTrue(C9rDecryptor.BASE64_PATTERN.matcher(input).matches());
42+
Assertions.assertTrue(Constants.BASE64_PATTERN.matcher(input).matches());
4243
}
4344

4445
@ParameterizedTest
@@ -53,7 +54,7 @@ public void testValidBase64Pattern(String input) {
5354
"aaaaBBBBccccDDDDeeeeFFFF conflict", // only a partial match
5455
})
5556
public void testInvalidBase64Pattern(String input) {
56-
Assertions.assertFalse(C9rDecryptor.BASE64_PATTERN.matcher(input).matches());
57+
Assertions.assertFalse(Constants.BASE64_PATTERN.matcher(input).matches());
5758
}
5859

5960
@Test
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.cryptomator.cryptofs.dir;
2+
3+
import org.cryptomator.cryptofs.common.Constants;
4+
import org.cryptomator.cryptofs.inuse.InUseManager;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.io.TempDir;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
15+
import static org.mockito.Mockito.mock;
16+
17+
public class C9uConflictResolverTest {
18+
19+
C9uConflictResolver c9uConflictResolver;
20+
21+
@BeforeEach
22+
void beforeEach() {
23+
var inUseManager = mock(InUseManager.class);
24+
c9uConflictResolver = new C9uConflictResolver(inUseManager);
25+
}
26+
27+
@Test
28+
@DisplayName("If filename is valid base64, the file exists")
29+
void validBase64KeepsExisting(@TempDir Path tmpDir) throws IOException {
30+
var ciphertextPath = tmpDir.resolve("aaaaBBBBccccDDDDeeeeFFFFggggHH=="+ Constants.INUSE_FILE_SUFFIX);
31+
Files.createFile(ciphertextPath);
32+
var node = new Node(ciphertextPath);
33+
34+
var result = c9uConflictResolver.process(node);
35+
36+
Assertions.assertEquals(0, result.count());
37+
Assertions.assertTrue(Files.exists(ciphertextPath));
38+
}
39+
40+
@Test
41+
@DisplayName("If filename is NOT valid base64, the file is deleted")
42+
void InvalidBase64Deleted(@TempDir Path tmpDir) throws IOException {
43+
var ciphertextPath = tmpDir.resolve("aaaaBBBBccccDDDDeeeeFFFFggggHH== (conflicted copy)"+ Constants.INUSE_FILE_SUFFIX);
44+
Files.createFile(ciphertextPath);
45+
var node = new Node(ciphertextPath);
46+
47+
var result = c9uConflictResolver.process(node);
48+
49+
Assertions.assertEquals(0, result.count());
50+
Assertions.assertTrue(Files.notExists(ciphertextPath));
51+
}
52+
}

0 commit comments

Comments
 (0)