Skip to content

Commit

Permalink
Merge branch 'release/1.4.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Nov 24, 2017
2 parents 4033e3e + 8158249 commit cb2bd2e
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 104 deletions.
2 changes: 1 addition & 1 deletion 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>1.4.4</version>
<version>1.4.5</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 Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package org.cryptomator.cryptofs;

import static java.nio.file.FileVisitResult.CONTINUE;
import static com.google.common.io.MoreFiles.deleteRecursively;
import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
import static java.util.stream.Collectors.toSet;
import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.NO_FILES_DELETED;
import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.SOME_FILES_DELETED;

import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Set;

import javax.inject.Inject;
Expand All @@ -30,6 +29,11 @@ public void deleteCiphertextDirIncludingNonCiphertextFiles(Path ciphertextDir, C
try {
Files.deleteIfExists(ciphertextDir);
} catch (DirectoryNotEmptyException e) {
/*
* The directory may not be empty due to two reasons:
* 1.
* 2.
*/
switch (deleteNonCiphertextFiles(ciphertextDir, cleartextDir)) {
case NO_FILES_DELETED:
throw e;
Expand All @@ -43,76 +47,25 @@ public void deleteCiphertextDirIncludingNonCiphertextFiles(Path ciphertextDir, C
}

private DeleteResult deleteNonCiphertextFiles(Path ciphertextDir, CryptoPath cleartextDir) throws IOException {
NonCiphertextFilesDeletingFileVisitor visitor;
DeleteResult result = NO_FILES_DELETED;
Set<Path> ciphertextFiles = ciphertextFiles(cleartextDir);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(ciphertextDir, p -> !ciphertextFiles.contains(p))) {
for (Path path : stream) {
result = SOME_FILES_DELETED;
deleteRecursively(path, ALLOW_INSECURE);
}
}
return result;
}

private Set<Path> ciphertextFiles(CryptoPath cleartextDir) throws IOException {
try (CryptoDirectoryStream directoryStream = directoryStreamFactory.newDirectoryStream(cleartextDir, ignored -> true)) {
Set<Path> ciphertextFiles = directoryStream.ciphertextDirectoryListing().collect(toSet());
visitor = new NonCiphertextFilesDeletingFileVisitor(ciphertextFiles);
return directoryStream.ciphertextDirectoryListing().collect(toSet());
}
Files.walkFileTree(ciphertextDir, visitor);
return visitor.getNumDeleted() == 0 //
? NO_FILES_DELETED //
: SOME_FILES_DELETED;
}

static enum DeleteResult {
NO_FILES_DELETED, SOME_FILES_DELETED
}

private static class NonCiphertextFilesDeletingFileVisitor implements FileVisitor<Path> {

private final Set<Path> ciphertextFiles;

private int numDeleted = 0;
private int level = 0;

public NonCiphertextFilesDeletingFileVisitor(Set<Path> ciphertextFiles) {
this.ciphertextFiles = ciphertextFiles;
}

public int getNumDeleted() {
return numDeleted;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
level++;
return CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!isOnRootLevel() || !isCiphertextFile(file)) {
Files.delete(file);
numDeleted++;
}
return CONTINUE;
}

private boolean isOnRootLevel() {
return level == 1;
}

private boolean isCiphertextFile(Path file) throws IOException {
return ciphertextFiles.contains(file);
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
throw exc;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (exc != null) {
throw exc;
}
level--;
if (level > 0) {
Files.delete(dir);
numDeleted++;
}
return CONTINUE;
}
};

}
6 changes: 3 additions & 3 deletions src/main/java/org/cryptomator/cryptofs/ConflictResolver.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.cryptomator.cryptofs;

import static org.cryptomator.cryptofs.Constants.DIR_PREFIX;
import static org.cryptomator.cryptofs.Constants.NAME_SHORTENING_THRESHOLD;
import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
import static org.cryptomator.cryptofs.LongFileNameProvider.LONG_NAME_FILE_EXT;

import java.io.IOException;
Expand Down Expand Up @@ -77,7 +77,7 @@ private Path resolveConflict(Path conflictingPath, String base32match, String di
final boolean isDirectory;
final String dirPrefix;
final Path canonicalPath;
if (LongFileNameProvider.isDeflated(originalFileName)) {
if (longFileNameProvider.isDeflated(originalFileName)) {
String inflated = longFileNameProvider.inflate(base32match + LONG_NAME_FILE_EXT);
ciphertext = StringUtils.removeStart(inflated, DIR_PREFIX);
isDirectory = inflated.startsWith(DIR_PREFIX);
Expand Down Expand Up @@ -116,7 +116,7 @@ private Path renameConflictingFile(Path canonicalPath, Path conflictingPath, Str
String alternativeCleartext = cleartext + " (Conflict " + i + ")";
String alternativeCiphertext = cryptor.fileNameCryptor().encryptFilename(alternativeCleartext, dirId.getBytes(StandardCharsets.UTF_8));
String alternativeCiphertextFileName = dirPrefix + alternativeCiphertext;
if (alternativeCiphertextFileName.length() >= NAME_SHORTENING_THRESHOLD) {
if (alternativeCiphertextFileName.length() > SHORT_NAMES_MAX_LENGTH) {
alternativeCiphertextFileName = longFileNameProvider.deflate(alternativeCiphertextFileName);
}
alternativePath = canonicalPath.resolveSibling(alternativeCiphertextFileName);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/cryptomator/cryptofs/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public final class Constants {
static final String DATA_DIR_NAME = "d";
static final String METADATA_DIR_NAME = "m";
static final String DIR_PREFIX = "0";
static final int NAME_SHORTENING_THRESHOLD = 129;
static final int SHORT_NAMES_MAX_LENGTH = 129;
static final String ROOT_DIR_ID = "";

static final String SEPARATOR = "/";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ public BasicFileAttributes getDelegate() {
return delegate;
}

@Override
public boolean isRegularFile() {
return getDelegate().isRegularFile();
}

@Override
public boolean isDirectory() {
return getDelegate().isDirectory();
}

@Override
public boolean isOther() {
return false;
Expand All @@ -53,10 +43,14 @@ public boolean isSymbolicLink() {

@Override
public long size() {
if (isRegularFile()) {
return Cryptors.cleartextSize(getDelegate().size() - cryptor.fileHeaderCryptor().headerSize(), cryptor);
} else {
if (isDirectory()) {
return getDelegate().size();
} else if (isOther()) {
return -1l;
} else if (isSymbolicLink()) {
return -1l;
} else {
return Cryptors.cleartextSize(getDelegate().size() - cryptor.fileHeaderCryptor().headerSize(), cryptor);
}
}

Expand Down
21 changes: 17 additions & 4 deletions src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -93,12 +95,17 @@ private ProcessedPaths resolveConflictingFileIfNeeded(ProcessedPaths paths) {
}
}

private ProcessedPaths inflateIfNeeded(ProcessedPaths paths) {
ProcessedPaths inflateIfNeeded(ProcessedPaths paths) {
String fileName = paths.getCiphertextPath().getFileName().toString();
if (LongFileNameProvider.isDeflated(fileName)) {
if (longFileNameProvider.isDeflated(fileName)) {
try {
String longFileName = longFileNameProvider.inflate(fileName);
return paths.withInflatedPath(paths.getCiphertextPath().resolveSibling(longFileName));
if (longFileName.length() <= SHORT_NAMES_MAX_LENGTH) {
// "unshortify" filenames on the fly due to previously shorter threshold
return inflatePermanently(paths, longFileName);
} else {
return paths.withInflatedPath(paths.getCiphertextPath().resolveSibling(longFileName));
}
} catch (IOException e) {
LOG.warn(paths.getCiphertextPath() + " could not be inflated.");
return null;
Expand Down Expand Up @@ -134,6 +141,12 @@ private boolean passesPlausibilityChecks(ProcessedPaths paths) {
return !isBrokenDirectoryFile(paths);
}

private ProcessedPaths inflatePermanently(ProcessedPaths paths, String longFileName) throws IOException {
Path newCiphertextPath = paths.getCiphertextPath().resolveSibling(longFileName);
Files.move(paths.getCiphertextPath(), newCiphertextPath);
return paths.withCiphertextPath(newCiphertextPath).withInflatedPath(newCiphertextPath);
}

private boolean isBrokenDirectoryFile(ProcessedPaths paths) {
Path potentialDirectoryFile = paths.getCiphertextPath();
if (paths.getInflatedPath().getFileName().toString().startsWith(Constants.DIR_PREFIX)) {
Expand Down Expand Up @@ -169,7 +182,7 @@ public void close() throws IOException {
() -> LOG.trace("CLOSE {}", directoryId));
}

private static class ProcessedPaths {
static class ProcessedPaths {

private final Path ciphertextPath;
private final Path inflatedPath;
Expand Down
23 changes: 22 additions & 1 deletion src/main/java/org/cryptomator/cryptofs/CryptoFileSystemUri.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* <p>
Expand All @@ -33,16 +35,35 @@
public class CryptoFileSystemUri {

public static final String URI_SCHEME = "cryptomator";
private static final Pattern UNC_URI_PATTERN = Pattern.compile("^file://[^@/]+(@SSL)?(@[0-9]+)?.*");

private final Path pathToVault;
private final String pathInsideVault;

private CryptoFileSystemUri(URI uri) {
validate(uri);
pathToVault = Paths.get(URI.create(uri.getAuthority()));
pathToVault = uncCompatibleUriToPath(URI.create(uri.getAuthority()));
pathInsideVault = uri.getPath();
}

/**
* Creates Path from the given URI. On Windows this will recognize UNC paths.
*
* @param uri <code>file:///file/path</code> or <code>file://server@SSL@123/actual/file/path</code>
* @return <code>/file/path</code> or <code>\\server@SSL@123\actual\file\path</code>
*/
// visible for testing
static Path uncCompatibleUriToPath(URI uri) {
String in = uri.toString();
Matcher m = UNC_URI_PATTERN.matcher(in);
if (m.find() && (m.group(1) != null || m.group(2) != null)) { // this is an UNC path!
String out = in.substring("file:".length()).replace('/', '\\');
return Paths.get(out);
} else {
return Paths.get(uri);
}
}

/**
* Constructs a CryptoFileSystem URI by using the given absolute path to a vault and constructing a path inside the vault from components.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private String getCiphertextFileName(String dirId, String cleartextName, Ciphert
// TODO overheadhunter: cache ciphertext names
String prefix = (fileType == CiphertextFileType.DIRECTORY) ? DIR_PREFIX : "";
String ciphertextName = prefix + cryptor.fileNameCryptor().encryptFilename(cleartextName, dirId.getBytes(StandardCharsets.UTF_8));
if (ciphertextName.length() >= Constants.NAME_SHORTENING_THRESHOLD) {
if (ciphertextName.length() > Constants.SHORT_NAMES_MAX_LENGTH) {
return longFileNameProvider.deflate(ciphertextName);
} else {
return ciphertextName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public String load(String shortName) throws IOException {

}

public static boolean isDeflated(String possiblyDeflatedFileName) {
public boolean isDeflated(String possiblyDeflatedFileName) {
return possiblyDeflatedFileName.endsWith(LONG_NAME_FILE_EXT);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,18 @@ public void testRenameNormalFile() throws IOException {
Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("abcdef ("), Mockito.any())).thenReturn(ciphertextName);
Mockito.doThrow(new NoSuchFileException(ciphertextName)).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName(ciphertextName)));
Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
Mockito.verifyNoMoreInteractions(longFileNameProvider);
Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName("ABCDEF== (1)")), Mockito.argThat(hasFileName(ciphertextName)), Mockito.any());
Assert.assertEquals(ciphertextName, resolved.getFileName().toString());
}

@Test
public void testRenameLongFile() throws IOException {
String longCiphertextName = "ABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGH2345====";
assert longCiphertextName.length() > Constants.NAME_SHORTENING_THRESHOLD;
assert longCiphertextName.length() > Constants.SHORT_NAMES_MAX_LENGTH;
Mockito.when(testFileName.toString()).thenReturn("ABCDEF== (1).lng");
Mockito.when(longFileNameProvider.inflate("ABCDEF==.lng")).thenReturn("FEDCBA==");
Mockito.when(longFileNameProvider.deflate(longCiphertextName)).thenReturn("FEDCBA==.lng");
Mockito.when(longFileNameProvider.isDeflated("ABCDEF== (1).lng")).thenReturn(true);
Mockito.when(filenameCryptor.decryptFilename(Mockito.eq("FEDCBA=="), Mockito.any())).thenReturn("fedcba");
Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("fedcba ("), Mockito.any())).thenReturn(longCiphertextName);
Mockito.doThrow(new NoSuchFileException("FEDCBA==.lng")).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName("FEDCBA==.lng")));
Expand Down Expand Up @@ -187,7 +187,6 @@ public void testRenameDirectoryFile() throws IOException, ReflectiveOperationExc
Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("abcdef ("), Mockito.any())).thenReturn(ciphertext);
Mockito.doThrow(new NoSuchFileException(ciphertextName)).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName(ciphertextName)));
Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
Mockito.verifyNoMoreInteractions(longFileNameProvider);
Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName("0ABCDEF== (1)")), Mockito.argThat(hasFileName(ciphertextName)), Mockito.any());
Assert.assertEquals(ciphertextName, resolved.getFileName().toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,26 @@ public void testIsSymbolicLink() {
}

@Test
public void testSize() throws IOException {
public void testSizeOfFile() throws IOException {
Mockito.when(delegateAttr.size()).thenReturn(88l + 16 + 1337 + 32);
Mockito.when(delegateAttr.isRegularFile()).thenReturn(true);
Mockito.when(delegateAttr.isDirectory()).thenReturn(false);
Mockito.when(delegateAttr.isSymbolicLink()).thenReturn(false);
Mockito.when(delegateAttr.isOther()).thenReturn(false);
Mockito.when(ciphertextFilePath.getFileName()).thenReturn(Paths.get("foo"));

BasicFileAttributes attr = new CryptoBasicFileAttributes(delegateAttr, ciphertextFilePath, cryptor);

Assert.assertEquals(1337l, attr.size());
}

Mockito.when(delegateAttr.isRegularFile()).thenReturn(false);
Assert.assertEquals(-1l, attr.size());
@Test
public void testSizeOfDirectory() throws IOException {
Mockito.when(delegateAttr.size()).thenReturn(4096l);
Mockito.when(delegateAttr.isDirectory()).thenReturn(true);

BasicFileAttributes attr = new CryptoBasicFileAttributes(delegateAttr, ciphertextFilePath, cryptor);

Assert.assertEquals(4096l, attr.size());
}

@Test(expected = IllegalArgumentException.class)
Expand Down
Loading

0 comments on commit cb2bd2e

Please sign in to comment.