Skip to content

Commit

Permalink
Merge branch 'release/1.2.2'
Browse files Browse the repository at this point in the history
# Conflicts:
#	pom.xml
  • Loading branch information
overheadhunter committed Apr 26, 2017
2 parents 4e87134 + 878a4a9 commit f2f276c
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 30 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.2.1</version>
<version>1.2.2</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
30 changes: 27 additions & 3 deletions src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,23 @@ class CryptoDirectoryStream implements DirectoryStream<Path> {
private final DirectoryStream<Path> ciphertextDirStream;
private final Path cleartextDir;
private final FileNameCryptor filenameCryptor;
private final CryptoPathMapper cryptoPathMapper;
private final LongFileNameProvider longFileNameProvider;
private final ConflictResolver conflictResolver;
private final DirectoryStream.Filter<? super Path> filter;
private final Consumer<CryptoDirectoryStream> onClose;
private final FinallyUtil finallyUtil;

public CryptoDirectoryStream(Directory ciphertextDir, Path cleartextDir, FileNameCryptor filenameCryptor, LongFileNameProvider longFileNameProvider, ConflictResolver conflictResolver,
DirectoryStream.Filter<? super Path> filter, Consumer<CryptoDirectoryStream> onClose, FinallyUtil finallyUtil) throws IOException {
public CryptoDirectoryStream(Directory ciphertextDir, Path cleartextDir, FileNameCryptor filenameCryptor, CryptoPathMapper cryptoPathMapper, LongFileNameProvider longFileNameProvider,
ConflictResolver conflictResolver, DirectoryStream.Filter<? super Path> filter, Consumer<CryptoDirectoryStream> onClose, FinallyUtil finallyUtil) throws IOException {
this.onClose = onClose;
this.finallyUtil = finallyUtil;
this.directoryId = ciphertextDir.dirId;
this.ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path, p -> true);
LOG.trace("OPEN " + directoryId);
this.cleartextDir = cleartextDir;
this.filenameCryptor = filenameCryptor;
this.cryptoPathMapper = cryptoPathMapper;
this.longFileNameProvider = longFileNameProvider;
this.conflictResolver = conflictResolver;
this.filter = filter;
Expand All @@ -62,7 +64,8 @@ public Iterator<Path> iterator() {
Stream<Path> pathIter = StreamSupport.stream(ciphertextDirStream.spliterator(), false);
Stream<Path> resolved = pathIter.map(this::resolveConflictingFileIfNeeded).filter(Objects::nonNull);
Stream<Path> inflated = resolved.map(this::inflateIfNeeded).filter(Objects::nonNull);
Stream<Path> decrypted = inflated.map(this::decrypt).filter(Objects::nonNull);
Stream<Path> sanitized = inflated.filter(this::passesPlausibilityChecks);
Stream<Path> decrypted = sanitized.map(this::decrypt).filter(Objects::nonNull);
Stream<Path> filtered = decrypted.filter(this::isAcceptableByFilter);
return filtered.iterator();
}
Expand Down Expand Up @@ -91,6 +94,27 @@ private Path inflateIfNeeded(Path ciphertextPath) {
}
}

private boolean passesPlausibilityChecks(Path ciphertextPath) {
return !isBrokenDirectoryFile(ciphertextPath);
}

private boolean isBrokenDirectoryFile(Path potentialDirectoryFile) {
if (potentialDirectoryFile.getFileName().toString().startsWith(Constants.DIR_PREFIX)) {
final Path dirPath;
try {
dirPath = cryptoPathMapper.resolveDirectory(potentialDirectoryFile).path;
} catch (IOException e) {
LOG.warn("Broken directory file {}. Exception: {}", potentialDirectoryFile, e.getMessage());
return true;
}
if (!Files.isDirectory(dirPath)) {
LOG.warn("Broken directory file {}. Directory {} does not exist.", potentialDirectoryFile, dirPath);
return true;
}
}
return false;
}

private Path decrypt(Path ciphertextPath) {
String ciphertextFileName = ciphertextPath.getFileName().toString();
Matcher m = BASE32_PATTERN.matcher(ciphertextFileName);
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,17 @@ public Directory getCiphertextDir(CryptoPath cleartextPath) throws IOException {
Directory parent = getCiphertextDir(parentPath);
String cleartextName = cleartextPath.getFileName().toString();
String ciphertextName = getCiphertextFileName(parent.dirId, cleartextName, CiphertextFileType.DIRECTORY);
String dirId = dirIdProvider.load(parent.path.resolve(ciphertextName));
return new Directory(dirId, directoryPathCache.getUnchecked(dirId));
Path dirIdFile = parent.path.resolve(ciphertextName);
return resolveDirectory(dirIdFile);
}
}

public Directory resolveDirectory(Path directoryFile) throws IOException {
String dirId = dirIdProvider.load(directoryFile);
Path dirPath = directoryPathCache.getUnchecked(dirId);
return new Directory(dirId, dirPath);
}

private Path resolveDirectory(String dirId) {
String dirHash = cryptor.fileNameCryptor().hashDirectoryId(dirId);
return dataRoot.resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public DirectoryStream<Path> newDirectoryStream(CryptoPath cleartextDir, Filter<
ciphertextDir, //
cleartextDir, //
cryptor.fileNameCryptor(), //
cryptoPathMapper, //
longFileNameProvider, //
conflictResolver, //
filter, //
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/org/cryptomator/cryptofs/GlobToRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ class GlobToRegex {
* Basic conversions (assuming / as only separator):
*
* <pre>
* {@code
* ? = [^/]
* * = [^/]*
* ** = .*
* [a-z] = [[^/]&&[a-z]]
* [!a-z] = [[^/]&&[^a-z]]
* {a,b,c} = (a|b|c)
* }
* </pre>
*/
public static String toRegex(String glob, char separator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Iterator;
Expand Down Expand Up @@ -57,6 +58,7 @@ public static void setupClass() {
private FileNameCryptor filenameCryptor;
private Path ciphertextDirPath;
private DirectoryStream<Path> dirStream;
private CryptoPathMapper cryptoPathMapper;
private LongFileNameProvider longFileNameProvider;
private ConflictResolver conflictResolver;
private FinallyUtil finallyUtil;
Expand Down Expand Up @@ -84,6 +86,20 @@ public void setup() throws IOException {
return StringUtils.removeEnd(shortName, ".lng");
}
});
cryptoPathMapper = Mockito.mock(CryptoPathMapper.class);
Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).then(invocation -> {
Path dirFilePath = invocation.getArgument(0);
if (dirFilePath.toString().contains("invalid")) {
throw new IOException("Invalid directory.");
}
Path dirPath = Mockito.mock(Path.class);
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
Mockito.when(dirPath.getFileSystem()).thenReturn(fs);
Mockito.when(provider.readAttributes(dirPath, BasicFileAttributes.class)).thenReturn(attrs);
Mockito.when(attrs.isDirectory()).thenReturn(!dirFilePath.toString().contains("noDirectory"));
return new Directory("asdf", dirPath);
});

Mockito.when(conflictResolver.resolveConflictsIfNecessary(Mockito.any(), Mockito.any())).then(returnsFirstArg());

doAnswer(invocation -> {
Expand All @@ -102,14 +118,16 @@ public void testDirListing() throws IOException {
ciphertextFileNames.add(filenameCryptor.encryptFilename("one", "foo".getBytes()));
ciphertextFileNames.add(filenameCryptor.encryptFilename("two", "foo".getBytes()) + "_conflict");
ciphertextFileNames.add("0" + filenameCryptor.encryptFilename("three", "foo".getBytes()));
ciphertextFileNames.add("0invalidDirectory");
ciphertextFileNames.add("0noDirectory");
ciphertextFileNames.add("invalidLongName.lng");
ciphertextFileNames.add(filenameCryptor.encryptFilename("four", "foo".getBytes()) + ".lng");
ciphertextFileNames.add(filenameCryptor.encryptFilename("invalid", "bar".getBytes()));
ciphertextFileNames.add("alsoInvalid");
Mockito.when(dirStream.spliterator()).thenReturn(ciphertextFileNames.stream().map(cleartextPath::resolve).spliterator());

try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, conflictResolver, ACCEPT_ALL, DO_NOTHING_ON_CLOSE,
finallyUtil)) {
try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, cryptoPathMapper, longFileNameProvider, conflictResolver, ACCEPT_ALL,
DO_NOTHING_ON_CLOSE, finallyUtil)) {
Iterator<Path> iter = stream.iterator();
Assert.assertTrue(iter.hasNext());
Assert.assertEquals(cleartextPath.resolve("one"), iter.next());
Expand All @@ -131,8 +149,8 @@ public void testDirListingForEmptyDir() throws IOException {

Mockito.when(dirStream.spliterator()).thenReturn(Spliterators.emptySpliterator());

try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, conflictResolver, ACCEPT_ALL, DO_NOTHING_ON_CLOSE,
finallyUtil)) {
try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, cryptoPathMapper, longFileNameProvider, conflictResolver, ACCEPT_ALL,
DO_NOTHING_ON_CLOSE, finallyUtil)) {
Iterator<Path> iter = stream.iterator();
Assert.assertFalse(iter.hasNext());
iter.next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -493,7 +493,7 @@ public void moveDirectoryReplaceExistingNonEmpty() throws IOException {
thrown.expect(DirectoryNotEmptyException.class);
inTest.move(cleartextSource, cleartextTarget, StandardCopyOption.REPLACE_EXISTING);
} finally {
verify(physicalFsProv, Mockito.never()).move(Mockito.any(), Mockito.any(), Mockito.anyVararg());
verify(physicalFsProv, Mockito.never()).move(Mockito.any(), Mockito.any(), Mockito.any());
}
}

Expand All @@ -505,7 +505,7 @@ public void moveDirectoryReplaceExistingAtomically() throws IOException {
thrown.expect(AtomicMoveNotSupportedException.class);
inTest.move(cleartextSource, cleartextTarget, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} finally {
verify(physicalFsProv, Mockito.never()).move(Mockito.any(), Mockito.any(), Mockito.anyVararg());
verify(physicalFsProv, Mockito.never()).move(Mockito.any(), Mockito.any(), Mockito.any());
}
}

Expand All @@ -519,7 +519,6 @@ public class Copy {
private final FileChannel ciphertextTargetDirFileChannel = mock(FileChannel.class);

@Before
@SuppressWarnings("unchecked")
public void setup() throws IOException, ReflectiveOperationException {
when(cleartextTarget.getParent()).thenReturn(cleartextTargetParent);
when(cryptoPathMapper.getCiphertextDirPath(cleartextTargetParent)).thenReturn(ciphertextTargetParent);
Expand All @@ -528,7 +527,7 @@ public void setup() throws IOException, ReflectiveOperationException {
when(ciphertextTargetDir.getFileSystem()).thenReturn(physicalFs);

when(cryptoPathMapper.getCiphertextDir(cleartextTarget)).thenReturn(new Directory("42", ciphertextTargetDir));
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextTargetDirFile), Mockito.anySet(), Mockito.anyVararg())).thenReturn(ciphertextTargetDirFileChannel);
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextTargetDirFile), Mockito.anySet(), Mockito.any())).thenReturn(ciphertextTargetDirFileChannel);
Field closeLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
closeLockField.setAccessible(true);
closeLockField.set(ciphertextTargetDirFileChannel, new Object());
Expand Down Expand Up @@ -612,8 +611,8 @@ public void moveDirectoryCopyBasicAttributes() throws IOException {
when(srcAttrs.lastModifiedTime()).thenReturn(lastModifiedTime);
when(srcAttrs.lastAccessTime()).thenReturn(lastAccessTime);
when(srcAttrs.creationTime()).thenReturn(createTime);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(BasicFileAttributes.class), Mockito.anyVararg())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(BasicFileAttributeView.class), Mockito.anyVararg())).thenReturn(dstAttrView);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(BasicFileAttributeView.class), Mockito.any())).thenReturn(dstAttrView);

inTest.copy(cleartextSource, cleartextTarget, StandardCopyOption.COPY_ATTRIBUTES);
verify(dstAttrView).setTimes(lastModifiedTime, lastAccessTime, createTime);
Expand All @@ -629,8 +628,8 @@ public void moveDirectoryCopyFileOwnerAttributes() throws IOException {
FileOwnerAttributeView srcAttrsView = mock(FileOwnerAttributeView.class);
FileOwnerAttributeView dstAttrView = mock(FileOwnerAttributeView.class);
when(srcAttrsView.getOwner()).thenReturn(owner);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextSourceDir), Mockito.same(FileOwnerAttributeView.class), Mockito.anyVararg())).thenReturn(srcAttrsView);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(FileOwnerAttributeView.class), Mockito.anyVararg())).thenReturn(dstAttrView);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextSourceDir), Mockito.same(FileOwnerAttributeView.class), Mockito.any())).thenReturn(srcAttrsView);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(FileOwnerAttributeView.class), Mockito.any())).thenReturn(dstAttrView);

inTest.copy(cleartextSource, cleartextTarget, StandardCopyOption.COPY_ATTRIBUTES);
verify(dstAttrView).setOwner(owner);
Expand All @@ -649,8 +648,8 @@ public void moveDirectoryCopyPosixAttributes() throws IOException {
PosixFileAttributeView dstAttrView = mock(PosixFileAttributeView.class);
when(srcAttrs.group()).thenReturn(group);
when(srcAttrs.permissions()).thenReturn(permissions);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(PosixFileAttributes.class), Mockito.anyVararg())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(PosixFileAttributeView.class), Mockito.anyVararg())).thenReturn(dstAttrView);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(PosixFileAttributes.class), Mockito.any())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(PosixFileAttributeView.class), Mockito.any())).thenReturn(dstAttrView);

inTest.copy(cleartextSource, cleartextTarget, StandardCopyOption.COPY_ATTRIBUTES);
verify(dstAttrView).setGroup(group);
Expand All @@ -669,8 +668,8 @@ public void moveDirectoryCopyDosAttributes() throws IOException {
when(srcAttrs.isHidden()).thenReturn(true);
when(srcAttrs.isReadOnly()).thenReturn(true);
when(srcAttrs.isSystem()).thenReturn(true);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(DosFileAttributes.class), Mockito.anyVararg())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(DosFileAttributeView.class), Mockito.anyVararg())).thenReturn(dstAttrView);
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(DosFileAttributes.class), Mockito.any())).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextTargetDir), Mockito.same(DosFileAttributeView.class), Mockito.any())).thenReturn(dstAttrView);

inTest.copy(cleartextSource, cleartextTarget, StandardCopyOption.COPY_ATTRIBUTES);
verify(dstAttrView).setArchive(true);
Expand Down
Loading

0 comments on commit f2f276c

Please sign in to comment.