Skip to content

Commit

Permalink
Merge branch 'release/1.8.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed May 7, 2019
2 parents 06cf5fb + 4e27d7a commit 20e00a0
Show file tree
Hide file tree
Showing 27 changed files with 256 additions and 156 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.8.2</version>
<version>1.8.3</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
35 changes: 35 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/CiphertextFileType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.cryptomator.cryptofs;

import java.util.Arrays;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
* Filename prefix as defined <a href="https://github.com/cryptomator/cryptofs/issues/38">issue 38</a>.
*/
public enum CiphertextFileType {
FILE(""), DIRECTORY("0"), SYMLINK("1S");

private final String prefix;

CiphertextFileType(String prefix) {
this.prefix = prefix;
}

public String getPrefix() {
return prefix;
}

public boolean isTypeOfFile(String filename) {
return filename.startsWith(prefix);
}

public static CiphertextFileType forFileName(String filename) {
return nonTrivialValues().filter(type -> type.isTypeOfFile(filename)).findAny().orElse(CiphertextFileType.FILE);
}

public static Stream<CiphertextFileType> nonTrivialValues() {
Predicate<CiphertextFileType> isTrivial = FILE::equals;
return Arrays.stream(values()).filter(isTrivial.negate());
}
}
63 changes: 31 additions & 32 deletions src/main/java/org/cryptomator/cryptofs/ConflictResolver.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.cryptomator.cryptofs;

import static org.cryptomator.cryptofs.Constants.DIR_PREFIX;
import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
import static org.cryptomator.cryptofs.LongFileNameProvider.LONG_NAME_FILE_EXT;
import com.google.common.base.Preconditions;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
Expand All @@ -15,18 +18,14 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
import static org.cryptomator.cryptofs.LongFileNameProvider.LONG_NAME_FILE_EXT;

@CryptoFileSystemScoped
class ConflictResolver {

private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class);
private static final Pattern BASE32_PATTERN = Pattern.compile("(0|1[A-Z0-9])?(([A-Z2-7]{8})*[A-Z2-7=]{8})");
private static final Pattern CIPHERTEXT_FILENAME_PATTERN = Pattern.compile("(0|1[A-Z0-9])?([A-Z2-7]{8})*[A-Z2-7=]{8}");
private static final int MAX_DIR_FILE_SIZE = 87; // "normal" file header has 88 bytes

private final LongFileNameProvider longFileNameProvider;
Expand All @@ -51,10 +50,10 @@ public ConflictResolver(LongFileNameProvider longFileNameProvider, Cryptor crypt
public Path resolveConflictsIfNecessary(Path ciphertextPath, String dirId) throws IOException {
String ciphertextFileName = ciphertextPath.getFileName().toString();
String basename = StringUtils.removeEnd(ciphertextFileName, LONG_NAME_FILE_EXT);
Matcher m = BASE32_PATTERN.matcher(basename);
Matcher m = CIPHERTEXT_FILENAME_PATTERN.matcher(basename);
if (!m.matches() && m.find(0)) {
// no full match, but still contains base32 -> partial match
return resolveConflict(ciphertextPath, m.group(2), dirId);
return resolveConflict(ciphertextPath, m.group(0), dirId);
} else {
// full match or no match at all -> nothing to resolve
return ciphertextPath;
Expand All @@ -65,35 +64,35 @@ public Path resolveConflictsIfNecessary(Path ciphertextPath, String dirId) throw
* Resolves a conflict.
*
* @param conflictingPath The path of a file containing a valid base 32 part.
* @param base32match The base32 part inside the filename of the conflicting file.
* @param ciphertextFileName The base32 part inside the filename of the conflicting file.
* @param dirId The directory id of the file's parent directory.
* @return The new path of the conflicting file after the conflict has been resolved.
* @throws IOException
*/
private Path resolveConflict(Path conflictingPath, String base32match, String dirId) throws IOException {
final Path directory = conflictingPath.getParent();
final String originalFileName = conflictingPath.getFileName().toString();
final String ciphertext;
final boolean isDirectory;
final String dirPrefix;
final Path canonicalPath;
if (longFileNameProvider.isDeflated(originalFileName)) {
String inflated = longFileNameProvider.inflate(base32match + LONG_NAME_FILE_EXT);
ciphertext = StringUtils.removeStart(inflated, DIR_PREFIX);
isDirectory = inflated.startsWith(DIR_PREFIX);
dirPrefix = isDirectory ? DIR_PREFIX : "";
canonicalPath = directory.resolve(base32match + LONG_NAME_FILE_EXT);
private Path resolveConflict(Path conflictingPath, String ciphertextFileName, String dirId) throws IOException {
String conflictingFileName = conflictingPath.getFileName().toString();
Preconditions.checkArgument(conflictingFileName.contains(ciphertextFileName), "%s does not contain %s", conflictingPath, ciphertextFileName);

Path parent = conflictingPath.getParent();
String inflatedFileName;
Path canonicalPath;
if (longFileNameProvider.isDeflated(conflictingFileName)) {
String deflatedName = ciphertextFileName + LONG_NAME_FILE_EXT;
inflatedFileName = longFileNameProvider.inflate(deflatedName);
canonicalPath = parent.resolve(deflatedName);
} else {
ciphertext = base32match;
isDirectory = originalFileName.startsWith(DIR_PREFIX);
dirPrefix = isDirectory ? DIR_PREFIX : "";
canonicalPath = directory.resolve(dirPrefix + ciphertext);
inflatedFileName = ciphertextFileName;
canonicalPath = parent.resolve(ciphertextFileName);
}

if (isDirectory && resolveDirectoryConflictTrivially(canonicalPath, conflictingPath)) {
CiphertextFileType type = CiphertextFileType.forFileName(inflatedFileName);
assert inflatedFileName.startsWith(type.getPrefix());
String ciphertext = inflatedFileName.substring(type.getPrefix().length());

if (CiphertextFileType.DIRECTORY.equals(type) && resolveDirectoryConflictTrivially(canonicalPath, conflictingPath)) {
return canonicalPath;
} else {
return renameConflictingFile(canonicalPath, conflictingPath, ciphertext, dirId, dirPrefix);
return renameConflictingFile(canonicalPath, conflictingPath, ciphertext, dirId, type.getPrefix());
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/cryptomator/cryptofs/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
*******************************************************************************/
package org.cryptomator.cryptofs;

public final class Constants {
Expand All @@ -15,7 +15,6 @@ 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 SHORT_NAMES_MAX_LENGTH = 129;
static final String ROOT_DIR_ID = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryIteratorException;
Expand All @@ -21,12 +27,6 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

class CryptoDirectoryStream implements DirectoryStream<Path> {
Expand Down Expand Up @@ -149,7 +149,7 @@ private ProcessedPaths inflatePermanently(ProcessedPaths paths, String longFileN

private boolean isBrokenDirectoryFile(ProcessedPaths paths) {
Path potentialDirectoryFile = paths.getCiphertextPath();
if (paths.getInflatedPath().getFileName().toString().startsWith(Constants.DIR_PREFIX)) {
if (paths.getInflatedPath().getFileName().toString().startsWith(CiphertextFileType.DIRECTORY.getPrefix())) {
final Path dirPath;
try {
dirPath = cryptoPathMapper.resolveDirectory(potentialDirectoryFile).path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
package org.cryptomator.cryptofs;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextFileType;
import org.cryptomator.cryptofs.attr.AttributeByNameProvider;
import org.cryptomator.cryptofs.attr.AttributeProvider;
import org.cryptomator.cryptofs.attr.AttributeViewProvider;
Expand Down
15 changes: 0 additions & 15 deletions src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.concurrent.ExecutionException;

import static org.cryptomator.cryptofs.Constants.DATA_DIR_NAME;
import static org.cryptomator.cryptofs.Constants.DIR_PREFIX;

@CryptoFileSystemScoped
public class CryptoPathMapper {
Expand Down Expand Up @@ -57,20 +56,6 @@ public class CryptoPathMapper {
this.rootDirectory = resolveDirectory(Constants.ROOT_DIR_ID);
}

public enum CiphertextFileType {
FILE(""), DIRECTORY(DIR_PREFIX), SYMLINK("1S");

private final String prefix;

CiphertextFileType(String prefix) {
this.prefix = prefix;
}

public String getPrefix() {
return prefix;
}
}

/**
* Verifies that no node exists for the given path. Otherwise a {@link FileAlreadyExistsException} will be thrown.
*
Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/cryptomator/cryptofs/Symlinks.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.cryptomator.cryptofs;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextFileType;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;

import javax.inject.Inject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.cryptomator.cryptofs.attr;

import org.cryptomator.cryptofs.ArrayUtils;
import org.cryptomator.cryptofs.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
Expand Down Expand Up @@ -48,7 +49,7 @@ protected Optional<OpenCryptoFile> getOpenCryptoFile() throws IOException {
}

private Path getCiphertextPath(CryptoPath path) throws IOException {
CryptoPathMapper.CiphertextFileType type = pathMapper.getCiphertextFileType(path);
CiphertextFileType type = pathMapper.getCiphertextFileType(path);
switch (type) {
case SYMLINK:
if (ArrayUtils.contains(linkOptions, LinkOption.NOFOLLOW_LINKS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.cryptomator.cryptofs.attr;

import org.cryptomator.cryptofs.ArrayUtils;
import org.cryptomator.cryptofs.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
Expand Down Expand Up @@ -61,7 +62,7 @@ public <A extends BasicFileAttributes> A readAttributes(CryptoPath cleartextPath
if (!ATTR_CONSTRUCTORS.containsKey(type)) {
throw new UnsupportedOperationException("Unsupported file attribute type: " + type);
}
CryptoPathMapper.CiphertextFileType ciphertextFileType = pathMapper.getCiphertextFileType(cleartextPath);
CiphertextFileType ciphertextFileType = pathMapper.getCiphertextFileType(cleartextPath);
switch (ciphertextFileType) {
case SYMLINK: {
if (ArrayUtils.contains(options, LinkOption.NOFOLLOW_LINKS)) {
Expand All @@ -85,7 +86,7 @@ public <A extends BasicFileAttributes> A readAttributes(CryptoPath cleartextPath
}
}

private <A extends BasicFileAttributes> A readAttributes(CryptoPathMapper.CiphertextFileType ciphertextFileType, Path ciphertextPath, Class<A> type) throws IOException {
private <A extends BasicFileAttributes> A readAttributes(CiphertextFileType ciphertextFileType, Path ciphertextPath, Class<A> type) throws IOException {
assert ATTR_CONSTRUCTORS.containsKey(type);
A ciphertextAttrs = Files.readAttributes(ciphertextPath, type);
AttributesConstructor<A> constructor = (AttributesConstructor<A>) ATTR_CONSTRUCTORS.get(type);
Expand All @@ -94,7 +95,7 @@ private <A extends BasicFileAttributes> A readAttributes(CryptoPathMapper.Cipher

@FunctionalInterface
private interface AttributesConstructor<A extends BasicFileAttributes> {
A construct(A delegate, CryptoPathMapper.CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem);
A construct(A delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*******************************************************************************/
package org.cryptomator.cryptofs.attr;

import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextFileType;
import org.cryptomator.cryptofs.CiphertextFileType;
import org.cryptomator.cryptofs.fh.OpenCryptoFile;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.nio.file.attribute.DosFileAttributes;
import java.util.Optional;

import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.CiphertextFileType;
import org.cryptomator.cryptofs.fh.OpenCryptoFile;
import org.cryptomator.cryptolib.api.Cryptor;

Expand All @@ -21,7 +21,7 @@ class CryptoDosFileAttributes extends CryptoBasicFileAttributes implements DosFi
private final boolean readonlyFileSystem;
private final DosFileAttributes delegate;

public CryptoDosFileAttributes(DosFileAttributes delegate, CryptoPathMapper.CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem) {
public CryptoDosFileAttributes(DosFileAttributes delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem) {
super(delegate, ciphertextFileType, ciphertextPath, cryptor, openCryptoFile, readonlyFileSystem);
this.readonlyFileSystem = readonlyFileSystem;
this.delegate = delegate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
package org.cryptomator.cryptofs.attr;

import com.google.common.collect.Sets;
import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.CiphertextFileType;
import org.cryptomator.cryptofs.fh.OpenCryptoFile;
import org.cryptomator.cryptolib.api.Cryptor;

Expand All @@ -34,7 +34,7 @@ class CryptoPosixFileAttributes extends CryptoBasicFileAttributes implements Pos
private final GroupPrincipal group;
private final Set<PosixFilePermission> permissions;

public CryptoPosixFileAttributes(PosixFileAttributes delegate, CryptoPathMapper.CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem) {
public CryptoPosixFileAttributes(PosixFileAttributes delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional<OpenCryptoFile> openCryptoFile, boolean readonlyFileSystem) {
super(delegate, ciphertextFileType, ciphertextPath, cryptor, openCryptoFile, readonlyFileSystem);
this.owner = delegate.owner();
this.group = delegate.group();
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.cryptomator.cryptofs.fh;

import com.google.common.base.Preconditions;
import com.google.common.io.MoreFiles;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import org.cryptomator.cryptofs.ch.ChannelComponent;
import org.cryptomator.cryptofs.ch.CleartextFileChannel;
Expand Down Expand Up @@ -82,17 +83,31 @@ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options) thr
.build();
cleartextFileChannel = channelComponent.channel();
} finally {
if (cleartextFileChannel == null && ciphertextFileChannel != null) {
// something didn't work
ciphertextFileChannel.close();
if (cleartextFileChannel == null) { // i.e. something didn't work
closeQuietly(ciphertextFileChannel);
// is this the first file channel to be opened?
if (openChannels.isEmpty()) {
close(); // then also close the file again.
}
}
}

assert cleartextFileChannel != null; // otherwise there would have been an exception
openChannels.put(cleartextFileChannel, ciphertextFileChannel);
chunkIO.registerChannel(ciphertextFileChannel, options.writable());
return cleartextFileChannel;
}

private void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
// no-op
}
}
}

/**
* Called by {@link #newFileChannel(EffectiveOpenOptions)} to determine the fileSize.
* <p>
Expand Down
Loading

0 comments on commit 20e00a0

Please sign in to comment.