Quellcode durchsuchen

Remove files with non-decryptable names from dir listings

Sebastian Stenzel vor 9 Jahren
Ursprung
Commit
6c18103662

+ 10 - 10
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java

@@ -2,14 +2,14 @@ package org.cryptomator.filesystem.crypto;
 
 import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX;
 
+import java.util.Optional;
 import java.util.UUID;
-import java.util.function.UnaryOperator;
+import java.util.function.Function;
 import java.util.regex.MatchResult;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.crypto.engine.CryptoException;
 import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
 import org.slf4j.Logger;
@@ -21,10 +21,10 @@ final class ConflictResolver {
 	private static final int UUID_FIRST_GROUP_STRLEN = 8;
 
 	private final Pattern encryptedNamePattern;
-	private final UnaryOperator<String> nameDecryptor;
-	private final UnaryOperator<String> nameEncryptor;
+	private final Function<String, Optional<String>> nameDecryptor;
+	private final Function<String, Optional<String>> nameEncryptor;
 
-	public ConflictResolver(Pattern encryptedNamePattern, UnaryOperator<String> nameDecryptor, UnaryOperator<String> nameEncryptor) {
+	public ConflictResolver(Pattern encryptedNamePattern, Function<String, Optional<String>> nameDecryptor, Function<String, Optional<String>> nameEncryptor) {
 		this.encryptedNamePattern = encryptedNamePattern;
 		this.nameDecryptor = nameDecryptor;
 		this.nameEncryptor = nameEncryptor;
@@ -47,8 +47,8 @@ final class ConflictResolver {
 	private File resolveConflict(File conflictingFile, MatchResult matchResult) {
 		String ciphertext = matchResult.group();
 		boolean isDirectory = conflictingFile.name().substring(matchResult.end()).startsWith(DIR_SUFFIX);
-		try {
-			String cleartext = nameDecryptor.apply(ciphertext);
+		Optional<String> cleartext = nameDecryptor.apply(ciphertext);
+		if (cleartext.isPresent()) {
 			Folder folder = conflictingFile.parent().get();
 			File canonicalFile = folder.file(isDirectory ? ciphertext + DIR_SUFFIX : ciphertext);
 			if (canonicalFile.exists()) {
@@ -57,8 +57,8 @@ final class ConflictResolver {
 				String conflictId;
 				do {
 					conflictId = createConflictId();
-					String alternativeCleartext = cleartext + " (Conflict " + conflictId + ")";
-					String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext);
+					String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")";
+					String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get();
 					alternativeFile = folder.file(isDirectory ? alternativeCiphertext + DIR_SUFFIX : alternativeCiphertext);
 				} while (alternativeFile.exists());
 				LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
@@ -68,7 +68,7 @@ final class ConflictResolver {
 				conflictingFile.moveTo(canonicalFile);
 				return canonicalFile;
 			}
-		} catch (CryptoException e) {
+		} else {
 			// not decryptable; false positive
 			return conflictingFile;
 		}

+ 1 - 5
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java

@@ -8,8 +8,6 @@
  *******************************************************************************/
 package org.cryptomator.filesystem.crypto;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import java.io.UncheckedIOException;
 import java.nio.file.FileAlreadyExistsException;
 import java.util.Optional;
@@ -27,9 +25,7 @@ class CryptoFile extends CryptoNode implements File {
 
 	@Override
 	protected Optional<String> encryptedName() {
-		return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
-			return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId);
-		});
+		return parent().get().encryptChildName(name());
 	}
 
 	@Override

+ 20 - 11
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java

@@ -25,15 +25,19 @@ import org.apache.commons.lang3.StringUtils;
 import org.cryptomator.common.LazyInitializer;
 import org.cryptomator.common.WeakValuedCache;
 import org.cryptomator.common.streams.AutoClosingStream;
+import org.cryptomator.crypto.engine.CryptoException;
 import org.cryptomator.crypto.engine.Cryptor;
 import org.cryptomator.filesystem.Deleter;
 import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
 import org.cryptomator.filesystem.Node;
 import org.cryptomator.io.FileContents;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 class CryptoFolder extends CryptoNode implements Folder {
 
+	private static final Logger LOG = LoggerFactory.getLogger(CryptoFolder.class);
 	private final WeakValuedCache<String, CryptoFolder> folders = WeakValuedCache.usingLoader(this::newFolder);
 	private final WeakValuedCache<String, CryptoFile> files = WeakValuedCache.usingLoader(this::newFile);
 	private final AtomicReference<String> directoryId = new AtomicReference<>();
@@ -49,9 +53,7 @@ class CryptoFolder extends CryptoNode implements Folder {
 	@Override
 	protected Optional<String> encryptedName() {
 		if (parent().isPresent()) {
-			return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
-				return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId) + DIR_SUFFIX;
-			});
+			return parent().get().encryptChildName(name()).map(s -> s + DIR_SUFFIX);
 		} else {
 			return Optional.of(cryptor.getFilenameCryptor().encryptFilename(name()) + DIR_SUFFIX);
 		}
@@ -95,24 +97,31 @@ class CryptoFolder extends CryptoNode implements Folder {
 		return (File file) -> encryptedNamePattern.matcher(file.name()).find();
 	}
 
-	private String decryptChildName(String ciphertextFileName) {
-		final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
-		return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
+	Optional<String> decryptChildName(String ciphertextFileName) {
+		return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
+			try {
+				return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
+			} catch (CryptoException e) {
+				LOG.warn("Filename decryption of {} failed: {}", ciphertextFileName, e.getMessage());
+				return null;
+			}
+		});
 	}
 
-	private String encryptChildName(String cleartextFileName) {
-		final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
-		return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
+	Optional<String> encryptChildName(String cleartextFileName) {
+		return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
+			return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
+		});
 	}
 
 	@Override
 	public Stream<CryptoFile> files() {
-		return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).map(this::file);
+		return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
 	}
 
 	@Override
 	public Stream<CryptoFolder> folders() {
-		return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).map(this::folder);
+		return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
 	}
 
 	private Predicate<String> endsWithDirSuffix() {

+ 5 - 5
main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java

@@ -2,7 +2,7 @@ package org.cryptomator.filesystem.crypto;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Optional;
-import java.util.function.UnaryOperator;
+import java.util.function.Function;
 import java.util.regex.Pattern;
 
 import org.apache.commons.codec.binary.Base32;
@@ -29,8 +29,8 @@ public class ConflictResolverTest {
 	public void setup() {
 		Pattern base32Pattern = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
 		BaseNCodec base32 = new Base32();
-		UnaryOperator<String> decode = (s) -> new String(base32.decode(s), StandardCharsets.UTF_8);
-		UnaryOperator<String> encode = (s) -> base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8));
+		Function<String, Optional<String>> decode = (s) -> Optional.of(new String(base32.decode(s), StandardCharsets.UTF_8));
+		Function<String, Optional<String>> encode = (s) -> Optional.of(base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8)));
 		conflictResolver = new ConflictResolver(base32Pattern, decode, encode);
 
 		folder = Mockito.mock(Folder.class);
@@ -41,8 +41,8 @@ public class ConflictResolverTest {
 		resolved = Mockito.mock(File.class);
 		unrelatedFile = Mockito.mock(File.class);
 
-		String canonicalFileName = encode.apply("test name");
-		String canonicalFolderName = encode.apply("test name") + Constants.DIR_SUFFIX;
+		String canonicalFileName = encode.apply("test name").get();
+		String canonicalFolderName = canonicalFileName + Constants.DIR_SUFFIX;
 		String conflictingFileName = canonicalFileName + " (version 2)";
 		String conflictingFolderName = canonicalFolderName + " (version 2)";
 		String unrelatedName = "notBa$e32!";