Skip to content

Commit 4d6a56b

Browse files
committed
Merge branch 'release/2.7.1'
2 parents a1ea3bc + 89aacb8 commit 4d6a56b

27 files changed

+484
-168
lines changed

.github/workflows/publish-central.yml

+1
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ jobs:
3636
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
3737
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
3838
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
39+
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"

.github/workflows/publish-github.yml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2424
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
2525
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
26+
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
2627
- name: Slack Notification
2728
uses: rtCamp/action-slack-notify@v2
2829
env:

pom.xml

+39-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<modelVersion>4.0.0</modelVersion>
33
<groupId>org.cryptomator</groupId>
44
<artifactId>cryptofs</artifactId>
5-
<version>2.7.0</version>
5+
<version>2.7.1</version>
66
<name>Cryptomator Crypto Filesystem</name>
77
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
88
<url>https://github.com/cryptomator/cryptofs</url>
@@ -23,19 +23,30 @@
2323
<dagger.version>2.51.1</dagger.version>
2424
<guava.version>33.2.1-jre</guava.version>
2525
<caffeine.version>3.1.8</caffeine.version>
26-
<slf4j.version>2.0.13</slf4j.version>
26+
<slf4j.version>2.0.16</slf4j.version>
2727

2828
<!-- test dependencies -->
29-
<junit.jupiter.version>5.10.3</junit.jupiter.version>
30-
<mockito.version>5.12.0</mockito.version>
29+
<junit.jupiter.version>5.11.3</junit.jupiter.version>
30+
<mockito.version>5.14.2</mockito.version>
3131
<hamcrest.version>3.0</hamcrest.version>
3232
<jimfs.version>1.3.0</jimfs.version>
3333

3434
<!-- build plugin dependencies -->
35-
<dependency-check.version>10.0.3</dependency-check.version>
35+
<mvn-compiler.version>3.13.0</mvn-compiler.version>
36+
<mvn-dependency.version>3.7.1</mvn-dependency.version>
37+
<mvn-surefire.version>3.5.1</mvn-surefire.version>
38+
<mvn-jar.version>3.4.2</mvn-jar.version>
39+
<mvn-source.version>3.3.1</mvn-source.version>
40+
<mvn-javadoc.version>3.10.1</mvn-javadoc.version>
41+
<mvn-gpg.version>3.2.7</mvn-gpg.version>
42+
43+
<dependency-check.version>11.0.0</dependency-check.version>
3644
<junit-tree-reporter.version>1.3.0</junit-tree-reporter.version>
3745
<jacoco.version>0.8.12</jacoco.version>
3846
<nexus-staging.version>1.7.0</nexus-staging.version>
47+
48+
<!-- Property used by surefire to determine jacoco engine -->
49+
<surefire.jacoco.args></surefire.jacoco.args>
3950
</properties>
4051

4152
<licenses>
@@ -143,7 +154,7 @@
143154
<plugin>
144155
<groupId>org.apache.maven.plugins</groupId>
145156
<artifactId>maven-compiler-plugin</artifactId>
146-
<version>3.13.0</version>
157+
<version>${mvn-compiler.version}</version>
147158
<configuration>
148159
<showWarnings>true</showWarnings>
149160
<annotationProcessorPaths>
@@ -155,10 +166,23 @@
155166
</annotationProcessorPaths>
156167
</configuration>
157168
</plugin>
169+
<plugin>
170+
<groupId>org.apache.maven.plugins</groupId>
171+
<artifactId>maven-dependency-plugin</artifactId>
172+
<version>${mvn-dependency.version}</version>
173+
<executions>
174+
<execution>
175+
<id>jar-paths-to-properties</id>
176+
<goals>
177+
<goal>properties</goal>
178+
</goals>
179+
</execution>
180+
</executions>
181+
</plugin>
158182
<plugin>
159183
<groupId>org.apache.maven.plugins</groupId>
160184
<artifactId>maven-surefire-plugin</artifactId>
161-
<version>3.3.1</version>
185+
<version>${mvn-surefire.version}</version>
162186
<dependencies>
163187
<dependency>
164188
<groupId>me.fabriciorby</groupId>
@@ -176,16 +200,17 @@
176200
<statelessTestsetInfoReporter
177201
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
178202
</statelessTestsetInfoReporter>
203+
<argLine>@{surefire.jacoco.args} -javaagent:${net.bytebuddy:byte-buddy-agent:jar}</argLine>
179204
</configuration>
180205
</plugin>
181206
<plugin>
182207
<groupId>org.apache.maven.plugins</groupId>
183208
<artifactId>maven-jar-plugin</artifactId>
184-
<version>3.4.2</version>
209+
<version>${mvn-jar.version}</version>
185210
</plugin>
186211
<plugin>
187212
<artifactId>maven-source-plugin</artifactId>
188-
<version>3.3.1</version>
213+
<version>${mvn-source.version}</version>
189214
<executions>
190215
<execution>
191216
<id>attach-sources</id>
@@ -197,7 +222,7 @@
197222
</plugin>
198223
<plugin>
199224
<artifactId>maven-javadoc-plugin</artifactId>
200-
<version>3.8.0</version>
225+
<version>${mvn-javadoc.version}</version>
201226
<executions>
202227
<execution>
203228
<id>attach-javadocs</id>
@@ -281,6 +306,9 @@
281306
<goals>
282307
<goal>prepare-agent</goal>
283308
</goals>
309+
<configuration>
310+
<propertyName>surefire.jacoco.args</propertyName>
311+
</configuration>
284312
</execution>
285313
<execution>
286314
<id>report</id>
@@ -300,7 +328,7 @@
300328
<plugins>
301329
<plugin>
302330
<artifactId>maven-gpg-plugin</artifactId>
303-
<version>3.2.4</version>
331+
<version>${mvn-gpg.version}</version>
304332
<executions>
305333
<execution>
306334
<id>sign-artifacts</id>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.cryptomator.cryptofs;
2+
3+
import com.github.benmanes.caffeine.cache.AsyncCache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
6+
import java.io.IOException;
7+
import java.time.Duration;
8+
import java.util.ArrayList;
9+
import java.util.concurrent.CompletableFuture;
10+
11+
/**
12+
* Caches for the cleartext path of a directory its ciphertext path to the content directory.
13+
*/
14+
public class CiphertextDirCache {
15+
16+
private static final int MAX_CACHED_PATHS = 5000;
17+
private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20);
18+
19+
private final AsyncCache<CryptoPath, CiphertextDirectory> ciphertextDirectories = Caffeine.newBuilder() //
20+
.maximumSize(MAX_CACHED_PATHS) //
21+
.expireAfterWrite(MAX_CACHE_AGE) //
22+
.buildAsync();
23+
24+
/**
25+
* Removes all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}.
26+
*
27+
* @param basePrefix The prefix key which the keys are checked against
28+
*/
29+
void removeAllKeysWithPrefix(CryptoPath basePrefix) {
30+
ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix));
31+
}
32+
33+
/**
34+
* Remaps all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}.
35+
* The new key is computed by replacing the oldPrefix with the newPrefix.
36+
*
37+
* @param oldPrefix the prefix key which the keys are checked against
38+
* @param newPrefix the prefix key which replaces {@code oldPrefix}
39+
*/
40+
void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) {
41+
var remappedEntries = new ArrayList<CacheEntry>();
42+
ciphertextDirectories.asMap().entrySet().removeIf(e -> {
43+
if (e.getKey().startsWith(oldPrefix)) {
44+
var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey()));
45+
return remappedEntries.add(new CacheEntry(remappedPath, e.getValue()));
46+
} else {
47+
return false;
48+
}
49+
});
50+
remappedEntries.forEach(e -> ciphertextDirectories.put(e.clearPath(), e.cipherDir()));
51+
}
52+
53+
54+
/**
55+
* Gets the cipher directory for the given cleartext path. If a cache miss occurs, the mapping is loaded with the {@code ifAbsent} function.
56+
* @param cleartextPath Cleartext path key
57+
* @param ifAbsent Function to compute the (cleartextPath, cipherDir) mapping on a cache miss.
58+
* @return a {@link CiphertextDirectory}, containing the dirId and the ciphertext content directory path
59+
* @throws IOException if the loading function throws an IOException
60+
*/
61+
CiphertextDirectory get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException {
62+
var futureMapping = new CompletableFuture<CiphertextDirectory>();
63+
var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping);
64+
if (currentMapping != null) {
65+
return currentMapping.join();
66+
} else {
67+
futureMapping.complete(ifAbsent.load());
68+
return futureMapping.join();
69+
}
70+
}
71+
72+
@FunctionalInterface
73+
interface CipherDirLoader {
74+
75+
CiphertextDirectory load() throws IOException;
76+
}
77+
78+
private record CacheEntry(CryptoPath clearPath, CompletableFuture<CiphertextDirectory> cipherDir) {
79+
80+
}
81+
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.cryptomator.cryptofs;
2+
3+
import java.nio.file.Path;
4+
import java.util.Objects;
5+
6+
//own file due to dagger
7+
8+
/**
9+
* Represents a ciphertext directory without it's mount point in the virtual filesystem.
10+
*
11+
* @param dirId The (ciphertext) dir id (not encrypted, just a uuid)
12+
* @param path The path to content directory (which contains the actual encrypted files and links to subdirectories)
13+
*/
14+
public record CiphertextDirectory(String dirId, Path path) {
15+
16+
public CiphertextDirectory(String dirId, Path path) {
17+
this.dirId = Objects.requireNonNull(dirId);
18+
this.path = Objects.requireNonNull(path);
19+
}
20+
21+
}

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*******************************************************************************/
99
package org.cryptomator.cryptofs;
1010

11-
import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
1211
import org.cryptomator.cryptofs.attr.AttributeByNameProvider;
1312
import org.cryptomator.cryptofs.attr.AttributeProvider;
1413
import org.cryptomator.cryptofs.attr.AttributeViewProvider;
@@ -142,7 +141,7 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException {
142141
var p = CryptoPath.castAndAssertAbsolute(cleartextPath);
143142
var nodeType = cryptoPathMapper.getCiphertextFileType(p);
144143
if (nodeType == CiphertextFileType.DIRECTORY) {
145-
return cryptoPathMapper.getCiphertextDir(p).path;
144+
return cryptoPathMapper.getCiphertextDir(p).path();
146145
}
147146
var cipherFile = cryptoPathMapper.getCiphertextFilePath(p);
148147
if (nodeType == CiphertextFileType.SYMLINK) {
@@ -316,22 +315,22 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
316315
if (cleartextParentDir == null) {
317316
return;
318317
}
319-
Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path;
318+
Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path();
320319
if (!Files.exists(ciphertextParentDir)) {
321320
throw new NoSuchFileException(cleartextParentDir.toString());
322321
}
323322
cryptoPathMapper.assertNonExisting(cleartextDir);
324323
CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextDir);
325324
Path ciphertextDirFile = ciphertextPath.getDirFilePath();
326-
CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
325+
var ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
327326
// atomically check for FileAlreadyExists and create otherwise:
328327
Files.createDirectory(ciphertextPath.getRawPath());
329328
try (FileChannel channel = FileChannel.open(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), attrs)) {
330-
channel.write(UTF_8.encode(ciphertextDir.dirId));
329+
channel.write(UTF_8.encode(ciphertextDir.dirId()));
331330
}
332331
// create dir if and only if the dirFile has been created right now (not if it has been created before):
333332
try {
334-
Files.createDirectories(ciphertextDir.path);
333+
Files.createDirectories(ciphertextDir.path());
335334
dirIdBackup.execute(ciphertextDir);
336335
ciphertextPath.persistLongFileName();
337336
} catch (IOException e) {
@@ -432,7 +431,7 @@ private void deleteFileOrSymlink(CiphertextFilePath ciphertextPath) throws IOExc
432431
}
433432

434433
private void deleteDirectory(CryptoPath cleartextPath, CiphertextFilePath ciphertextPath) throws IOException {
435-
Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path;
434+
Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path();
436435
Path ciphertextDirFile = ciphertextPath.getDirFilePath();
437436
try {
438437
ciphertextDirDeleter.deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDir, cleartextPath);
@@ -505,7 +504,7 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
505504
ciphertextTarget.persistLongFileName();
506505
} else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) {
507506
// keep existing (if empty):
508-
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
507+
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
509508
try (DirectoryStream<Path> ds = Files.newDirectoryStream(ciphertextTargetDir)) {
510509
if (ds.iterator().hasNext()) {
511510
throw new DirectoryNotEmptyException(cleartextTarget.toString());
@@ -515,8 +514,8 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
515514
throw new FileAlreadyExistsException(cleartextTarget.toString(), null, "Ciphertext file already exists: " + ciphertextTarget);
516515
}
517516
if (ArrayUtils.contains(options, StandardCopyOption.COPY_ATTRIBUTES)) {
518-
Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path;
519-
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
517+
Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path();
518+
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
520519
copyAttributes(ciphertextSourceDir, ciphertextTargetDir);
521520
}
522521
}
@@ -622,7 +621,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
622621
throw new AtomicMoveNotSupportedException(cleartextSource.toString(), cleartextTarget.toString(), "Replacing directories during move requires non-atomic status checks.");
623622
}
624623
// check if dir is empty:
625-
Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
624+
Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
626625
boolean targetCiphertextDirExists = true;
627626
try (DirectoryStream<Path> ds = Files.newDirectoryStream(targetCiphertextDirContentDir, DirectoryStreamFilters.EXCLUDE_DIR_ID_BACKUP)) {
628627
if (ds.iterator().hasNext()) {

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
155155
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
156156
Files.createDirectories(vaultCipherRootPath);
157157
// create dirId backup:
158-
DirectoryIdBackup.backupManually(cryptor, new CryptoPathMapper.CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
158+
DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
159159
} finally {
160160
Arrays.fill(rawKey, (byte) 0x00);
161161
}

0 commit comments

Comments
 (0)