Skip to content

Commit

Permalink
Merge branch 'release/2.7.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Nov 18, 2024
2 parents a1ea3bc + 89aacb8 commit 4d6a56b
Show file tree
Hide file tree
Showing 27 changed files with 484 additions and 168 deletions.
1 change: 1 addition & 0 deletions .github/workflows/publish-central.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ jobs:
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
1 change: 1 addition & 0 deletions .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
Expand Down
50 changes: 39 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.7.0</version>
<version>2.7.1</version>
<name>Cryptomator Crypto Filesystem</name>
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
<url>https://github.com/cryptomator/cryptofs</url>
Expand All @@ -23,19 +23,30 @@
<dagger.version>2.51.1</dagger.version>
<guava.version>33.2.1-jre</guava.version>
<caffeine.version>3.1.8</caffeine.version>
<slf4j.version>2.0.13</slf4j.version>
<slf4j.version>2.0.16</slf4j.version>

<!-- test dependencies -->
<junit.jupiter.version>5.10.3</junit.jupiter.version>
<mockito.version>5.12.0</mockito.version>
<junit.jupiter.version>5.11.3</junit.jupiter.version>
<mockito.version>5.14.2</mockito.version>
<hamcrest.version>3.0</hamcrest.version>
<jimfs.version>1.3.0</jimfs.version>

<!-- build plugin dependencies -->
<dependency-check.version>10.0.3</dependency-check.version>
<mvn-compiler.version>3.13.0</mvn-compiler.version>
<mvn-dependency.version>3.7.1</mvn-dependency.version>
<mvn-surefire.version>3.5.1</mvn-surefire.version>
<mvn-jar.version>3.4.2</mvn-jar.version>
<mvn-source.version>3.3.1</mvn-source.version>
<mvn-javadoc.version>3.10.1</mvn-javadoc.version>
<mvn-gpg.version>3.2.7</mvn-gpg.version>

<dependency-check.version>11.0.0</dependency-check.version>
<junit-tree-reporter.version>1.3.0</junit-tree-reporter.version>
<jacoco.version>0.8.12</jacoco.version>
<nexus-staging.version>1.7.0</nexus-staging.version>

<!-- Property used by surefire to determine jacoco engine -->
<surefire.jacoco.args></surefire.jacoco.args>
</properties>

<licenses>
Expand Down Expand Up @@ -143,7 +154,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>${mvn-compiler.version}</version>
<configuration>
<showWarnings>true</showWarnings>
<annotationProcessorPaths>
Expand All @@ -155,10 +166,23 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${mvn-dependency.version}</version>
<executions>
<execution>
<id>jar-paths-to-properties</id>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
<version>${mvn-surefire.version}</version>
<dependencies>
<dependency>
<groupId>me.fabriciorby</groupId>
Expand All @@ -176,16 +200,17 @@
<statelessTestsetInfoReporter
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
</statelessTestsetInfoReporter>
<argLine>@{surefire.jacoco.args} -javaagent:${net.bytebuddy:byte-buddy-agent:jar}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<version>${mvn-jar.version}</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<version>${mvn-source.version}</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -197,7 +222,7 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.8.0</version>
<version>${mvn-javadoc.version}</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down Expand Up @@ -281,6 +306,9 @@
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>surefire.jacoco.args</propertyName>
</configuration>
</execution>
<execution>
<id>report</id>
Expand All @@ -300,7 +328,7 @@
<plugins>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.4</version>
<version>${mvn-gpg.version}</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down
82 changes: 82 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.cryptomator.cryptofs;

import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

/**
* Caches for the cleartext path of a directory its ciphertext path to the content directory.
*/
public class CiphertextDirCache {

private static final int MAX_CACHED_PATHS = 5000;
private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20);

private final AsyncCache<CryptoPath, CiphertextDirectory> ciphertextDirectories = Caffeine.newBuilder() //
.maximumSize(MAX_CACHED_PATHS) //
.expireAfterWrite(MAX_CACHE_AGE) //
.buildAsync();

/**
* Removes all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}.
*
* @param basePrefix The prefix key which the keys are checked against
*/
void removeAllKeysWithPrefix(CryptoPath basePrefix) {
ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix));
}

/**
* Remaps all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}.
* The new key is computed by replacing the oldPrefix with the newPrefix.
*
* @param oldPrefix the prefix key which the keys are checked against
* @param newPrefix the prefix key which replaces {@code oldPrefix}
*/
void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) {
var remappedEntries = new ArrayList<CacheEntry>();
ciphertextDirectories.asMap().entrySet().removeIf(e -> {
if (e.getKey().startsWith(oldPrefix)) {
var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey()));
return remappedEntries.add(new CacheEntry(remappedPath, e.getValue()));
} else {
return false;
}
});
remappedEntries.forEach(e -> ciphertextDirectories.put(e.clearPath(), e.cipherDir()));
}


/**
* Gets the cipher directory for the given cleartext path. If a cache miss occurs, the mapping is loaded with the {@code ifAbsent} function.
* @param cleartextPath Cleartext path key
* @param ifAbsent Function to compute the (cleartextPath, cipherDir) mapping on a cache miss.
* @return a {@link CiphertextDirectory}, containing the dirId and the ciphertext content directory path
* @throws IOException if the loading function throws an IOException
*/
CiphertextDirectory get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException {
var futureMapping = new CompletableFuture<CiphertextDirectory>();
var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping);
if (currentMapping != null) {
return currentMapping.join();
} else {
futureMapping.complete(ifAbsent.load());
return futureMapping.join();
}
}

@FunctionalInterface
interface CipherDirLoader {

CiphertextDirectory load() throws IOException;
}

private record CacheEntry(CryptoPath clearPath, CompletableFuture<CiphertextDirectory> cipherDir) {

}

}
21 changes: 21 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/CiphertextDirectory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.cryptomator.cryptofs;

import java.nio.file.Path;
import java.util.Objects;

//own file due to dagger

/**
* Represents a ciphertext directory without it's mount point in the virtual filesystem.
*
* @param dirId The (ciphertext) dir id (not encrypted, just a uuid)
* @param path The path to content directory (which contains the actual encrypted files and links to subdirectories)
*/
public record CiphertextDirectory(String dirId, Path path) {

public CiphertextDirectory(String dirId, Path path) {
this.dirId = Objects.requireNonNull(dirId);
this.path = Objects.requireNonNull(path);
}

}
21 changes: 10 additions & 11 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
import org.cryptomator.cryptofs.attr.AttributeByNameProvider;
import org.cryptomator.cryptofs.attr.AttributeProvider;
import org.cryptomator.cryptofs.attr.AttributeViewProvider;
Expand Down Expand Up @@ -142,7 +141,7 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException {
var p = CryptoPath.castAndAssertAbsolute(cleartextPath);
var nodeType = cryptoPathMapper.getCiphertextFileType(p);
if (nodeType == CiphertextFileType.DIRECTORY) {
return cryptoPathMapper.getCiphertextDir(p).path;
return cryptoPathMapper.getCiphertextDir(p).path();
}
var cipherFile = cryptoPathMapper.getCiphertextFilePath(p);
if (nodeType == CiphertextFileType.SYMLINK) {
Expand Down Expand Up @@ -316,22 +315,22 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
if (cleartextParentDir == null) {
return;
}
Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path;
Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path();
if (!Files.exists(ciphertextParentDir)) {
throw new NoSuchFileException(cleartextParentDir.toString());
}
cryptoPathMapper.assertNonExisting(cleartextDir);
CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextDir);
Path ciphertextDirFile = ciphertextPath.getDirFilePath();
CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
var ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
// atomically check for FileAlreadyExists and create otherwise:
Files.createDirectory(ciphertextPath.getRawPath());
try (FileChannel channel = FileChannel.open(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), attrs)) {
channel.write(UTF_8.encode(ciphertextDir.dirId));
channel.write(UTF_8.encode(ciphertextDir.dirId()));
}
// create dir if and only if the dirFile has been created right now (not if it has been created before):
try {
Files.createDirectories(ciphertextDir.path);
Files.createDirectories(ciphertextDir.path());
dirIdBackup.execute(ciphertextDir);
ciphertextPath.persistLongFileName();
} catch (IOException e) {
Expand Down Expand Up @@ -432,7 +431,7 @@ private void deleteFileOrSymlink(CiphertextFilePath ciphertextPath) throws IOExc
}

private void deleteDirectory(CryptoPath cleartextPath, CiphertextFilePath ciphertextPath) throws IOException {
Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path;
Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path();
Path ciphertextDirFile = ciphertextPath.getDirFilePath();
try {
ciphertextDirDeleter.deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDir, cleartextPath);
Expand Down Expand Up @@ -505,7 +504,7 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
ciphertextTarget.persistLongFileName();
} else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) {
// keep existing (if empty):
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
try (DirectoryStream<Path> ds = Files.newDirectoryStream(ciphertextTargetDir)) {
if (ds.iterator().hasNext()) {
throw new DirectoryNotEmptyException(cleartextTarget.toString());
Expand All @@ -515,8 +514,8 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
throw new FileAlreadyExistsException(cleartextTarget.toString(), null, "Ciphertext file already exists: " + ciphertextTarget);
}
if (ArrayUtils.contains(options, StandardCopyOption.COPY_ATTRIBUTES)) {
Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path;
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path();
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
copyAttributes(ciphertextSourceDir, ciphertextTargetDir);
}
}
Expand Down Expand Up @@ -622,7 +621,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
throw new AtomicMoveNotSupportedException(cleartextSource.toString(), cleartextTarget.toString(), "Replacing directories during move requires non-atomic status checks.");
}
// check if dir is empty:
Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path();
boolean targetCiphertextDirExists = true;
try (DirectoryStream<Path> ds = Files.newDirectoryStream(targetCiphertextDirContentDir, DirectoryStreamFilters.EXCLUDE_DIR_ID_BACKUP)) {
if (ds.iterator().hasNext()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
Files.createDirectories(vaultCipherRootPath);
// create dirId backup:
DirectoryIdBackup.backupManually(cryptor, new CryptoPathMapper.CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}
Expand Down
Loading

0 comments on commit 4d6a56b

Please sign in to comment.