Merge remote-tracking branch 'refs/remotes/cryptomator/master'

This commit is contained in:
jncharon
2016-05-16 14:20:55 +02:00
26 changed files with 548 additions and 62 deletions

View File

@@ -14,6 +14,12 @@
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/fixed-binaries</directory>
<filtered>false</filtered>
<outputDirectory>fixed-binaries</outputDirectory>
<fileMode>755</fileMode>
</fileSet>
<fileSet>
<directory>target/package</directory>
<filtered>false</filtered>

View File

@@ -60,6 +60,16 @@
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>fixed-binaries/**</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>fixed-binaries/**</include>
</includes>
</resource>
</resources>
</configuration>

View File

@@ -50,6 +50,7 @@
<fx:resources>
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar"/>
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
</fx:resources>
<fx:permissions elevated="false" />
<fx:preferences install="true" />

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,50 @@
#!/bin/sh
# postinst script for APPLICATION_NAME
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure)
echo Adding shortcut to the menu
SECONDARY_LAUNCHERS_INSTALL
APP_CDS_CACHE
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
FILE_ASSOCIATION_INSTALL
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
if [ $(uname -m) = "x86_64" ]; then
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
else
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

View File

@@ -0,0 +1,54 @@
Summary: APPLICATION_SUMMARY
Name: APPLICATION_PACKAGE
Version: APPLICATION_VERSION
Release: 1
License: APPLICATION_LICENSE_TYPE
Vendor: APPLICATION_VENDOR
Prefix: /opt
Provides: APPLICATION_PACKAGE
Requires: ld-linux.so.2 libX11.so.6 libXext.so.6 libXi.so.6 libXrender.so.1 libXtst.so.6 libasound.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libthread_db.so.1
Autoprov: 0
Autoreq: 0
#avoid ARCH subfolder
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
#comment line below to enable effective jar compression
#it could easily get your package size from 40 to 15Mb but
#build time will substantially increase and it may require unpack200/system java to install
%define __jar_repack %{nil}
%description
APPLICATION_DESCRIPTION
%prep
%build
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/opt
cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}/opt
%files
APPLICATION_LICENSE_FILE
/opt/APPLICATION_FS_NAME
%post
SECONDARY_LAUNCHERS_INSTALL
APP_CDS_CACHE
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
FILE_ASSOCIATION_INSTALL
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
if [ $(uname -m) = "x86_64" ]; then
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
else
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
fi
%preun
SECONDARY_LAUNCHERS_REMOVE
xdg-desktop-menu uninstall --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
FILE_ASSOCIATION_REMOVE
%clean

View File

@@ -8,7 +8,7 @@
*******************************************************************************/
package org.cryptomator.crypto.engine;
abstract class CryptoException extends RuntimeException {
public abstract class CryptoException extends RuntimeException {
public CryptoException() {
super();

View File

@@ -8,6 +8,8 @@
*******************************************************************************/
package org.cryptomator.crypto.engine;
import java.util.regex.Pattern;
/**
* Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
* otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
@@ -22,12 +24,9 @@ public interface FilenameCryptor {
String hashDirectoryId(String cleartextDirectoryId);
/**
* Tests without an actual decryption attempt, if a name is a well-formed ciphertext.
*
* @param ciphertextName Filename in question
* @return <code>true</code> if the given name is likely to be a valid ciphertext
* @return A Pattern that can be used to test, if a name is a well-formed ciphertext.
*/
boolean isEncryptedFilename(String ciphertextName);
Pattern encryptedNamePattern();
/**
* @param cleartextName original filename including cleartext file extension

View File

@@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Pattern;
import javax.crypto.AEADBadTagException;
import javax.crypto.SecretKey;
@@ -25,6 +26,7 @@ import org.cryptomator.siv.SivMode;
class FilenameCryptorImpl implements FilenameCryptor {
private static final BaseNCodec BASE32 = new Base32();
private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
private static final ThreadLocal<SivMode> AES_SIV = new ThreadLocal<SivMode>() {
@Override
@@ -50,8 +52,8 @@ class FilenameCryptorImpl implements FilenameCryptor {
}
@Override
public boolean isEncryptedFilename(String ciphertextName) {
return BASE32.isInAlphabet(ciphertextName);
public Pattern encryptedNamePattern() {
return BASE32_PATTERN;
}
@Override

View File

@@ -0,0 +1,81 @@
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.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.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class ConflictResolver {
private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class);
private static final int UUID_FIRST_GROUP_STRLEN = 8;
private final Pattern encryptedNamePattern;
private final Function<String, Optional<String>> nameDecryptor;
private final Function<String, Optional<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;
}
public File resolveIfNecessary(File file) {
Matcher m = encryptedNamePattern.matcher(StringUtils.removeEnd(file.name(), DIR_SUFFIX));
if (m.matches()) {
// full match, use file as is
return file;
} else if (m.find(0)) {
// partial match, might be conflicting
return resolveConflict(file, m.toMatchResult());
} else {
// no match, file not relevant
return file;
}
}
private File resolveConflict(File conflictingFile, MatchResult matchResult) {
String ciphertext = matchResult.group();
boolean isDirectory = conflictingFile.name().substring(matchResult.end()).startsWith(DIR_SUFFIX);
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()) {
// conflict detected! look for an alternative name:
File alternativeFile;
String conflictId;
do {
conflictId = createConflictId();
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);
conflictingFile.moveTo(alternativeFile);
return alternativeFile;
} else {
conflictingFile.moveTo(canonicalFile);
return canonicalFile;
}
} else {
// not decryptable; false positive
return conflictingFile;
}
}
private String createConflictId() {
return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN);
}
}

View File

@@ -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

View File

@@ -18,35 +18,42 @@ import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
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<>();
private final ConflictResolver conflictResolver;
public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) {
super(parent, name, cryptor);
this.conflictResolver = new ConflictResolver(cryptor.getFilenameCryptor().encryptedNamePattern(), this::decryptChildName, this::encryptChildName);
}
/* ======================= name + directory id ======================= */
@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);
}
@@ -73,24 +80,56 @@ class CryptoFolder extends CryptoNode implements Folder {
}));
}
/* ======================= children ======================= */
@Override
public Stream<? extends Node> children() {
return AutoClosingStream.from(Stream.concat(files(), folders()));
}
private Stream<File> nonConflictingFiles() {
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
return files.filter(containsEncryptedName()).map(conflictResolver::resolveIfNecessary);
}
private Predicate<File> containsEncryptedName() {
final Pattern encryptedNamePattern = cryptor.getFilenameCryptor().encryptedNamePattern();
return (File file) -> encryptedNamePattern.matcher(file.name()).find();
}
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;
}
});
}
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() {
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
return files.map(File::name).filter(isEncryptedFileName()).map(this::decryptChildFileName).map(this::file);
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
}
private Predicate<String> isEncryptedFileName() {
return (String name) -> !name.endsWith(DIR_SUFFIX) && cryptor.getFilenameCryptor().isEncryptedFilename(name);
@Override
public Stream<CryptoFolder> folders() {
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
}
private String decryptChildFileName(String encryptedFileName) {
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
return cryptor.getFilenameCryptor().decryptFilename(encryptedFileName, dirId);
private Predicate<String> endsWithDirSuffix() {
return (String encryptedFolderName) -> StringUtils.endsWith(encryptedFolderName, DIR_SUFFIX);
}
private String removeDirSuffix(String encryptedFolderName) {
return StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX);
}
@Override
@@ -98,35 +137,21 @@ class CryptoFolder extends CryptoNode implements Folder {
return files.get(name);
}
public CryptoFile newFile(String name) {
return new CryptoFile(this, name, cryptor);
}
@Override
public Stream<CryptoFolder> folders() {
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
return files.map(File::name).filter(isEncryptedDirectoryName()).map(this::decryptChildFolderName).map(this::folder);
}
private Predicate<String> isEncryptedDirectoryName() {
return (String name) -> name.endsWith(DIR_SUFFIX) && cryptor.getFilenameCryptor().isEncryptedFilename(StringUtils.removeEnd(name, DIR_SUFFIX));
}
private String decryptChildFolderName(String encryptedFolderName) {
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
final String ciphertext = StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX);
return cryptor.getFilenameCryptor().decryptFilename(ciphertext, dirId);
}
@Override
public CryptoFolder folder(String name) {
return folders.get(name);
}
public CryptoFolder newFolder(String name) {
private CryptoFile newFile(String name) {
return new CryptoFile(this, name, cryptor);
}
private CryptoFolder newFolder(String name) {
return new CryptoFolder(this, name, cryptor);
}
/* ======================= create/move/delete ======================= */
@Override
public void create() {
parent.create();
@@ -176,7 +201,7 @@ class CryptoFolder extends CryptoNode implements Folder {
// cut all ties:
this.invalidateDirectoryIdsRecursively();
assert!exists();
assert !exists();
assert target.exists();
}

View File

@@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Pattern;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.BaseNCodec;
@@ -19,6 +20,7 @@ import org.apache.commons.codec.binary.BaseNCodec;
class NoFilenameCryptor implements FilenameCryptor {
private static final BaseNCodec BASE32 = new Base32();
private static final Pattern WILDCARD_PATTERN = Pattern.compile(".*");
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
@Override
@@ -29,8 +31,8 @@ class NoFilenameCryptor implements FilenameCryptor {
}
@Override
public boolean isEncryptedFilename(String ciphertextName) {
return true;
public Pattern encryptedNamePattern() {
return WILDCARD_PATTERN;
}
@Override

View File

@@ -0,0 +1,110 @@
package org.cryptomator.filesystem.crypto;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.BaseNCodec;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class ConflictResolverTest {
private ConflictResolver conflictResolver;
private Folder folder;
private File canonicalFile;
private File canonicalFolder;
private File conflictingFile;
private File conflictingFolder;
private File resolved;
private File unrelatedFile;
@Before
public void setup() {
Pattern base32Pattern = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
BaseNCodec base32 = new Base32();
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);
canonicalFile = Mockito.mock(File.class);
canonicalFolder = Mockito.mock(File.class);
conflictingFile = Mockito.mock(File.class);
conflictingFolder = Mockito.mock(File.class);
resolved = Mockito.mock(File.class);
unrelatedFile = Mockito.mock(File.class);
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!";
Mockito.when(canonicalFile.name()).thenReturn(canonicalFileName);
Mockito.when(canonicalFolder.name()).thenReturn(canonicalFolderName);
Mockito.when(conflictingFile.name()).thenReturn(conflictingFileName);
Mockito.when(conflictingFolder.name()).thenReturn(conflictingFolderName);
Mockito.when(unrelatedFile.name()).thenReturn(unrelatedName);
Mockito.when(canonicalFile.exists()).thenReturn(true);
Mockito.when(canonicalFolder.exists()).thenReturn(true);
Mockito.when(conflictingFile.exists()).thenReturn(true);
Mockito.when(conflictingFolder.exists()).thenReturn(true);
Mockito.when(unrelatedFile.exists()).thenReturn(true);
Mockito.doReturn(Optional.of(folder)).when(canonicalFile).parent();
Mockito.doReturn(Optional.of(folder)).when(canonicalFolder).parent();
Mockito.doReturn(Optional.of(folder)).when(conflictingFile).parent();
Mockito.doReturn(Optional.of(folder)).when(conflictingFolder).parent();
Mockito.doReturn(Optional.of(folder)).when(unrelatedFile).parent();
Mockito.when(folder.file(Mockito.startsWith(canonicalFileName.substring(0, 8)))).thenReturn(resolved);
Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile);
Mockito.when(folder.file(canonicalFolderName)).thenReturn(canonicalFolder);
Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile);
Mockito.when(folder.file(conflictingFolderName)).thenReturn(conflictingFolder);
Mockito.when(folder.file(unrelatedName)).thenReturn(unrelatedFile);
}
@Test
public void testCanonicalName() {
File resolved = conflictResolver.resolveIfNecessary(canonicalFile);
Assert.assertSame(canonicalFile, resolved);
}
@Test
public void testUnrelatedName() {
File resolved = conflictResolver.resolveIfNecessary(unrelatedFile);
Assert.assertSame(unrelatedFile, resolved);
}
@Test
public void testConflictingFile() {
File resolved = conflictResolver.resolveIfNecessary(conflictingFile);
Mockito.verify(conflictingFile).moveTo(resolved);
Assert.assertSame(resolved, resolved);
}
@Test
public void testConflictingFileIfCanonicalDoesnExist() {
Mockito.when(canonicalFile.exists()).thenReturn(false);
File resolved = conflictResolver.resolveIfNecessary(conflictingFile);
Mockito.verify(conflictingFile).moveTo(canonicalFile);
Assert.assertSame(canonicalFile, resolved);
}
@Test
public void testConflictingFolder() {
File resolved = conflictResolver.resolveIfNecessary(conflictingFolder);
Mockito.verify(conflictingFolder).moveTo(resolved);
Assert.assertSame(resolved, resolved);
}
}

View File

@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.filesystem.shortening;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -19,9 +17,12 @@ import org.apache.commons.codec.binary.BaseNCodec;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.io.FileContents;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class FilenameShortener {
private static final Logger LOG = LoggerFactory.getLogger(FilenameShortener.class);
private static final String LONG_NAME_FILE_EXT = ".lng";
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
private static final BaseNCodec BASE32 = new Base32();
@@ -71,7 +72,8 @@ class FilenameShortener {
private String loadMapping(String shortName) {
final File mappingFile = mappingFile(shortName);
if (!mappingFile.exists()) {
throw new UncheckedIOException(new FileNotFoundException("Mapping file not found " + mappingFile));
LOG.warn("Mapping file not found: " + mappingFile);
return shortName;
} else {
return FileContents.UTF_8.readContents(mappingFile);
}

View File

@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.filesystem.shortening;
import java.io.UncheckedIOException;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
import org.junit.Assert;
@@ -45,12 +43,12 @@ public class FilenameShortenerTest {
Assert.assertEquals("short", shortener.inflate("short"));
}
@Test(expected = UncheckedIOException.class)
@Test
public void testInflateWithoutMappingFile() {
FileSystem fs = new InMemoryFileSystem();
FilenameShortener shortener = new FilenameShortener(fs, 10);
shortener.inflate("iJustMadeThisNameUp.lng");
Assert.assertEquals("iJustMadeThisNameUp.lng", shortener.inflate("iJustMadeThisNameUp.lng"));
}
}

View File

@@ -16,6 +16,7 @@ import static org.junit.Assert.assertThat;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.concurrent.TimeoutException;
@@ -102,6 +103,31 @@ public class ShorteningFileSystemTest {
Assert.assertTrue(correspondingMetadataFile.exists());
}
@Test
public void testInflate() {
final FileSystem underlyingFs = new InMemoryFileSystem();
final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME);
final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10);
final File correspondingMetadataFile = metadataRoot.folder("QM").folder("JL").file("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
shortenedFolder.create();
correspondingMetadataFile.parent().get().create();
try (WritableFile w = correspondingMetadataFile.openWritable()) {
w.write(ByteBuffer.wrap("morethantenchars".getBytes(StandardCharsets.UTF_8)));
}
Assert.assertTrue(correspondingMetadataFile.exists());
Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("morethantenchars")));
}
@Test
public void testInflateFailedDueToMissingMapping() {
final FileSystem underlyingFs = new InMemoryFileSystem();
final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10);
final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
shortenedFolder.create();
Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng")));
}
@Test
public void testMoveLongFolders() {
final FileSystem underlyingFs = new InMemoryFileSystem();

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Console>
<Console name="StdErr" target="SYSTEM_ERR">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
<AppenderRef ref="StdErr" />
</Root>
</Loggers>
</Configuration>

View File

@@ -43,6 +43,9 @@ public class SettingsController extends LocalizedFXMLViewController {
@FXML
private TextField portField;
@FXML
private Label useIpv6Label;
@FXML
private CheckBox useIpv6Checkbox;
@@ -55,7 +58,8 @@ public class SettingsController extends LocalizedFXMLViewController {
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally());
portField.setText(String.valueOf(settings.getPort()));
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
useIpv6Checkbox.setDisable(!SystemUtils.IS_OS_WINDOWS);
useIpv6Label.setVisible(SystemUtils.IS_OS_WINDOWS);
useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS);
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
@@ -81,7 +85,7 @@ public class SettingsController extends LocalizedFXMLViewController {
private void portDidChange(String newValue) {
try {
int port = Integer.parseInt(newValue);
if (port < Settings.MIN_PORT || port > Settings.MAX_PORT) {
if (!settings.isPortValid(port)) {
settings.setPort(Settings.DEFAULT_PORT);
} else {
settings.setPort(port);

View File

@@ -93,8 +93,8 @@ public class Settings implements Serializable {
}
}
private boolean isPortValid(int port) {
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT;
public boolean isPortValid(int port) {
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT || port == 0;
}
public boolean shouldUseIpv6() {

View File

@@ -38,7 +38,7 @@
<TextField GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" />
<!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%settings.useipv6.label" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="useIpv6Checkbox" cacheShape="true" cache="true" />
</children>
</GridPane>

View File

@@ -3,6 +3,8 @@
# See the LICENSE.txt file for more info.
#
# Contributors:
# Christian Schmickler
# Joscha Feth
# Markus Kreusch
# Michael Schmetter
# Sebastian Wiesendahl
@@ -86,3 +88,8 @@ tray.menu.quit = Beenden
tray.infoMsg.title = Cryptomator läuft noch
tray.infoMsg.msg = Cryptomator läuft noch. Mit dem Tray-Icon beenden.
tray.infoMsg.msg.osx = Cryptomator läuft noch. Über die Menüleiste beenden.
initialize.messageLabel.passwordStrength.0 = Sehr schwach
initialize.messageLabel.passwordStrength.1 = Schwach
initialize.messageLabel.passwordStrength.2 = Mittel
initialize.messageLabel.passwordStrength.3 = Stark
initialize.messageLabel.passwordStrength.4 = Sehr stark

View File

@@ -82,3 +82,8 @@ tray.menu.quit = 종료
tray.infoMsg.title = 계속 실행 중입니다.
tray.infoMsg.msg = Cryptomator가 계속 실행 중입니다. 종료하실려면 트레이 아이콘에서 해주세요.
tray.infoMsg.msg.osx = Cryptomator가 계속 실행중입니다. 종료하실려면 메뉴 바 아이콘에서 해주세요.
initialize.messageLabel.passwordStrength.0 = 너무 약함
initialize.messageLabel.passwordStrength.1 = 약함
initialize.messageLabel.passwordStrength.2 = 괜찮음
initialize.messageLabel.passwordStrength.3 = 강력함
initialize.messageLabel.passwordStrength.4 = 매우 강력함

View File

@@ -6,9 +6,6 @@
# Filip Havrlent
# Tatiana Chovancová
# Copyright (c) 2016 The Cryptomator Contributors
# This file is licensed under the terms of the MIT license.
# See the LICENSE.txt file for more info.
app.name = Cryptomator
# main.fxml
main.emptyListInstructions = Pridať trezor

View File

@@ -0,0 +1,89 @@
# Copyright (c) 2016 The Cryptomator Contributors
# This file is licensed under the terms of the MIT license.
# See the LICENSE.txt file for more info.
#
# Contributors:
# Cem KOÇ
app.name = Cryptomator
# main.fxml
main.emptyListInstructions = Kasa eklemek için tıkla
main.directoryList.contextMenu.remove = Listeden sil
main.directoryList.contextMenu.changePassword = Şifreyi değiştir
main.addDirectory.contextMenu.new = Yeni bir kasa yarat
main.addDirectory.contextMenu.open = Var olan kasayı
# welcome.fxml
welcome.checkForUpdates.label.currentlyChecking = Güncellemeler kontrol ediliyor...
welcome.newVersionMessage = Sürüm %s indirilebilir. Şu anki sürüm\: %s
# initialize.fxml
initialize.label.password = Şifre
initialize.label.retypePassword = Şifre (tekrar)
initialize.button.ok = Kasa oluştur
initialize.messageLabel.alreadyInitialized = Kasa çoktan başlatıldı
initialize.messageLabel.initializationFailed = Kasa başlatılamadı. Detaylar için log dosyasına bakın.
# notfound.fxml
notfound.label = Kasa bulunamadı. Yeri değişmiş olabilir mi ?
# upgrade.fxml
upgrade.button = Kasayı yükselt.
upgrade.version3dropBundleExtension.msg = Bu kasanın yeni formata geçirilmesi gerekmekte. "%1$s" ismi "%2$s" olarak değiştirilecek. Devam etmeden önce senkronizasyonun bittiğine emin olun.
upgrade.version3dropBundleExtension.err.alreadyExists = Otomatik format değiştirme sırasında hata. "%s" zaten bulunmakta.
# unlock.fxml
unlock.label.password = Şifre
unlock.label.mountName = Sürücü ismi
unlock.label.winDriveLetter = Sürücü konumu
unlock.label.downloadsPageLink = Tüm Cryptomator sürümleri
unlock.label.advancedHeading = Gelişmiş seçenekler
unlock.button.unlock = Kasayı
unlock.button.advancedOptions.show = Daha fazla seçenek
unlock.button.advancedOptions.hide = Daha az seçenek
unlock.choicebox.winDriveLetter.auto = Otomatik ata
unlock.errorMessage.wrongPassword = Yanlış şifre
unlock.errorMessage.mountingFailed = Başlama başarısız. Detaylar için log dosyasına bakın
unlock.errorMessage.unsupportedKeyLengthInstallJCE = Şifre çözme başarısız. Lütfen Oracle JCE Unlimited Strength Policy yükleyin.
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Desteklenmeyen kasa. Bu kasa daha eski bir Cryptomator sürümü ile oluşturulmuş.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Desteklenmeyen kasa. Bu kasa daha yeni bir Cryptomator sürümü ile oluşturulmuş.
unlock.messageLabel.startServerFailed = WebDAV sunucu başlatma başarısız.
# change_password.fxml
changePassword.label.oldPassword = Eski şifre
changePassword.label.newPassword = Yeni şifre
changePassword.label.retypePassword = Yeni şifre (tekrar)
changePassword.label.downloadsPageLink = Tüm Cryptomator sürümleri
changePassword.button.change = Şifreyi değiştir
changePassword.errorMessage.wrongPassword = Yanlış şifre
changePassword.errorMessage.decryptionFailed = Şifre çözme başarısız
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Şifre çözme başarısız. Lütfen Oracle JCE Unlimited Strength Policy yükleyin.
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Desteklenmeyen kasa. Bu kasa daha eski bir Cryptomator sürümü ile oluşturulmuş.
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Desteklenmeyen kasa. Bu kasa daha yeni bir Cryptomator sürümü ile oluşturulmuş.
changePassword.infoMessage.success = Şifre değiştirildi.
# unlocked.fxml
unlocked.button.lock = Kasayı kilitle
unlocked.moreOptions.reveal = Sürücüyü göster
unlocked.moreOptions.copyUrl = WebDAV URl'sini kopyala
unlocked.label.revealFailed = Komut başarısız
unlocked.label.unmountFailed = Sürücü çıkarma başarısız
unlocked.label.statsEncrypted = şifrelenmiş
unlocked.label.statsDecrypted = şifresi çözülmüş
unlocked.ioGraph.yAxis.label = Veri hacmi (MiB/s)
# mac_warnings.fxml
macWarnings.windowTitle = Tehlike - Bozuk dosya - %s
macWarnings.message = Cryptomator bu satırlarda kötücül olma olasılığına sahip bozulmalar tespit etti\:
macWarnings.moreInformationButton = Daha fazla öğren
macWarnings.whitelistButton = Seçilenleri her halükarda şifrele
# settings.fxml
settings.version.label = Sürüm %s
settings.checkForUpdates.label = Güncellemeleri denetle
settings.port.label = WebDAV Port *
settings.port.prompt = 0 \= Otomatik seç
settings.useipv6.label = IPv6 kullan
settings.requiresRestartLabel = * Cryptomator yeniden başlatılması gerek
# tray icon
tray.menu.open = Aç
tray.menu.quit = Çıkış
tray.infoMsg.title = Hala çalışıyor
tray.infoMsg.msg = Cryptomator hala çalışıyor. Bildirim simgesi ile çıkış yapın.
tray.infoMsg.msg.osx = Cryptomator hala çalışıyor. Menü bar simgesi ile çıkış yapın.
initialize.messageLabel.passwordStrength.0 = Çok zayıf
initialize.messageLabel.passwordStrength.1 = Zayıf
initialize.messageLabel.passwordStrength.2 = İyi
initialize.messageLabel.passwordStrength.3 = Güçlü
initialize.messageLabel.passwordStrength.4 = Çok güçlü