diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java index df9522f2f..cce407ccf 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java @@ -13,6 +13,7 @@ import java.time.Instant; import java.util.Optional; import java.util.stream.Stream; +import org.cryptomator.common.WeakValuedCache; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.Folder; import org.cryptomator.filesystem.Node; @@ -21,6 +22,8 @@ public abstract class DelegatingFolder folders = WeakValuedCache.usingLoader(this::newFolder); + private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); public DelegatingFolder(D parent, Folder delegate) { super(delegate); @@ -39,27 +42,27 @@ public abstract class DelegatingFolder folders() { - return delegate.folders().map(this::folder); + return delegate.folders().map(folders::get); } @Override public Stream files() throws UncheckedIOException { - return delegate.files().map(this::file); + return delegate.files().map(files::get); } @Override public F file(String name) throws UncheckedIOException { - return file(delegate.file(name)); + return files.get(delegate.file(name)); } - protected abstract F file(File delegate); + protected abstract F newFile(File delegate); @Override public D folder(String name) throws UncheckedIOException { - return folder(delegate.folder(name)); + return folders.get(delegate.folder(name)); } - protected abstract D folder(Folder delegate); + protected abstract D newFolder(Folder delegate); @Override public void create() throws UncheckedIOException { diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java index cf50046a5..d550e71d2 100644 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java +++ b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java @@ -161,4 +161,17 @@ public class DelegatingFolderTest { Mockito.verify(mockFolder).delete(); } + @Test + public void testSubresourcesAreSameInstance() { + Folder mockFolder = Mockito.mock(Folder.class); + Folder mockSubFolder = Mockito.mock(Folder.class); + File mockSubFile = Mockito.mock(File.class); + Mockito.when(mockFolder.folder("mockSubFolder")).thenReturn(mockSubFolder); + Mockito.when(mockFolder.file("mockSubFile")).thenReturn(mockSubFile); + + DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); + Assert.assertSame(delegatingFolder.folder("mockSubFolder"), delegatingFolder.folder("mockSubFolder")); + Assert.assertSame(delegatingFolder.file("mockSubFile"), delegatingFolder.file("mockSubFile")); + } + } diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java index c33627ff7..808cbf619 100644 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java +++ b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java @@ -10,12 +10,12 @@ class TestDelegatingFolder extends DelegatingFolder SHA1 = new ThreadLocalSha1(); - private static final SivMode AES_SIV = new SivMode(); + private static final ThreadLocal AES_SIV = new ThreadLocal() { + @Override + protected SivMode initialValue() { + return new SivMode(); + }; + }; private final SecretKey encryptionKey; private final SecretKey macKey; @@ -39,7 +44,7 @@ class FilenameCryptorImpl implements FilenameCryptor { @Override public String hashDirectoryId(String cleartextDirectoryId) { final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes); + byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes); final byte[] hashedBytes = SHA1.get().digest(encryptedBytes); return BASE32.encodeAsString(hashedBytes); } @@ -47,7 +52,7 @@ class FilenameCryptorImpl implements FilenameCryptor { @Override public String encryptFilename(String cleartextName, byte[]... associatedData) { final byte[] cleartextBytes = cleartextName.getBytes(UTF_8); - final byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes, associatedData); + final byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes, associatedData); return BASE32.encodeAsString(encryptedBytes); } @@ -55,7 +60,7 @@ class FilenameCryptorImpl implements FilenameCryptor { public String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException { final byte[] encryptedBytes = BASE32.decode(ciphertextName); try { - final byte[] cleartextBytes = AES_SIV.decrypt(encryptionKey, macKey, encryptedBytes, associatedData); + final byte[] cleartextBytes = AES_SIV.get().decrypt(encryptionKey, macKey, encryptedBytes, associatedData); return new String(cleartextBytes, UTF_8); } catch (AEADBadTagException e) { throw new AuthenticationFailedException("Authentication failed.", e); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java index 921ea0bf7..acffd1354 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java @@ -22,12 +22,12 @@ class BlockAlignedFolder extends DelegatingFolder folders = WeakValuedCache.usingLoader(this::newFolder); + private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); private final AtomicReference directoryId = new AtomicReference<>(); public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) { @@ -41,7 +44,8 @@ class CryptoFolder extends CryptoNode implements Folder { @Override protected String encryptedName() { - return cryptor.getFilenameCryptor().encryptFilename(name()) + FILE_EXT; + final byte[] parentDirId = parent().map(CryptoFolder::getDirectoryId).map(s -> s.getBytes(UTF_8)).orElse(null); + return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId) + DIR_EXT; } Folder physicalFolder() { @@ -77,31 +81,41 @@ class CryptoFolder extends CryptoNode implements Folder { @Override public Stream files() { - return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFile.FILE_EXT)).map(this::decryptFileName).map(this::file); + return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFile.FILE_EXT)).map(this::decryptChildFileName).map(this::file); } - private String decryptFileName(String encryptedFileName) { + private String decryptChildFileName(String encryptedFileName) { + final byte[] dirId = getDirectoryId().getBytes(UTF_8); final String ciphertext = StringUtils.removeEnd(encryptedFileName, CryptoFile.FILE_EXT); - return cryptor.getFilenameCryptor().decryptFilename(ciphertext); + return cryptor.getFilenameCryptor().decryptFilename(ciphertext, dirId); } @Override public CryptoFile file(String name) { + return files.get(name); + } + + public CryptoFile newFile(String name) { return new CryptoFile(this, name, cryptor); } @Override public Stream folders() { - return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFolder.FILE_EXT)).map(this::decryptFolderName).map(this::folder); + return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFolder.DIR_EXT)).map(this::decryptChildFolderName).map(this::folder); } - private String decryptFolderName(String encryptedFolderName) { - final String ciphertext = StringUtils.removeEnd(encryptedFolderName, CryptoFolder.FILE_EXT); - return cryptor.getFilenameCryptor().decryptFilename(ciphertext); + private String decryptChildFolderName(String encryptedFolderName) { + final byte[] dirId = getDirectoryId().getBytes(UTF_8); + final String ciphertext = StringUtils.removeEnd(encryptedFolderName, CryptoFolder.DIR_EXT); + return cryptor.getFilenameCryptor().decryptFilename(ciphertext, dirId); } @Override public CryptoFolder folder(String name) { + return folders.get(name); + } + + public CryptoFolder newFolder(String name) { return new CryptoFolder(this, name, cryptor); } @@ -139,6 +153,7 @@ class CryptoFolder extends CryptoNode implements Folder { // directoryId is now used by target, we must no longer use the same id // (we'll generate a new one when needed) + target.directoryId.set(getDirectoryId()); directoryId.set(null); } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java index 1f6777c2d..0caaa25b3 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java @@ -49,7 +49,8 @@ abstract class CryptoNode implements Node { @Override public boolean exists() { - return parent.children().anyMatch(node -> node.equals(this)); + return physicalFile().exists(); + // return parent.children().anyMatch(node -> node.equals(this)); } @Override diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java index 9f6df3b31..2edf7e536 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java @@ -77,6 +77,18 @@ public class FilenameCryptorImplTest { filenameCryptor.decryptFilename(new String(encrypted, UTF_8)); } + @Test + public void testEncryptionOfSameFilenamesWithDifferentAssociatedData() { + final byte[] keyBytes = new byte[32]; + final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); + final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); + final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); + + final String encrypted1 = filenameCryptor.encryptFilename("test", "ad1".getBytes(UTF_8)); + final String encrypted2 = filenameCryptor.encryptFilename("test", "ad2".getBytes(UTF_8)); + Assert.assertNotEquals(encrypted1, encrypted2); + } + @Test public void testDeterministicEncryptionOfFilenamesWithAssociatedData() { final byte[] keyBytes = new byte[32]; diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java index 24c7dbddd..f58b5b579 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java @@ -45,7 +45,7 @@ class InMemoryFile extends InMemoryNode implements File { final WriteLock writeLock = lock.writeLock(); writeLock.lock(); final InMemoryFolder parent = parent().get(); - parent.children.compute(this.name(), (k, v) -> { + parent.existingChildren.compute(this.name(), (k, v) -> { if (v == null || v == this) { this.lastModified = Instant.now(); this.creationTime = Instant.now(); @@ -54,7 +54,6 @@ class InMemoryFile extends InMemoryNode implements File { throw new UncheckedIOException(new FileExistsException(k)); } }); - parent.volatileFiles.remove(name); return new InMemoryWritableFile(this::setLastModified, this::setCreationTime, this::getContent, this::setContent, this::delete, writeLock); } @@ -76,11 +75,11 @@ class InMemoryFile extends InMemoryNode implements File { private void delete(Void param) { final InMemoryFolder parent = parent().get(); - parent.children.computeIfPresent(this.name(), (k, v) -> { + parent.existingChildren.computeIfPresent(this.name(), (k, v) -> { // returning null removes the entry. return null; }); - assert !this.exists(); + assert!this.exists(); } @Override diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java index fc3d9b979..c9020596a 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java @@ -10,20 +10,21 @@ package org.cryptomator.filesystem.inmem; import java.io.UncheckedIOException; import java.time.Instant; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import java.util.stream.Stream; import org.apache.commons.io.FileExistsException; +import org.cryptomator.common.WeakValuedCache; import org.cryptomator.filesystem.Folder; class InMemoryFolder extends InMemoryNode implements Folder { - final Map children = new TreeMap<>(); - final Map volatileFiles = new HashMap<>(); - final Map volatileFolders = new HashMap<>(); + final Map existingChildren = new TreeMap<>(); + + private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::newFolder); + private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); public InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) { super(parent, name, lastModified, creationTime); @@ -31,31 +32,25 @@ class InMemoryFolder extends InMemoryNode implements Folder { @Override public Stream children() { - return children.values().stream(); + return existingChildren.values().stream(); } @Override public InMemoryFile file(String name) { - final InMemoryNode node = children.get(name); - if (node instanceof InMemoryFile) { - return (InMemoryFile) node; - } else { - return volatileFiles.computeIfAbsent(name, (n) -> { - return new InMemoryFile(this, n, Instant.MIN, Instant.MIN); - }); - } + return files.get(name); + } + + private InMemoryFile newFile(String name) { + return new InMemoryFile(this, name, Instant.MIN, Instant.MIN); } @Override public InMemoryFolder folder(String name) { - final InMemoryNode node = children.get(name); - if (node instanceof InMemoryFolder) { - return (InMemoryFolder) node; - } else { - return volatileFolders.computeIfAbsent(name, (n) -> { - return new InMemoryFolder(this, n, Instant.MIN, Instant.MIN); - }); - } + return folders.get(name); + } + + private InMemoryFolder newFolder(String name) { + return new InMemoryFolder(this, name, Instant.MIN, Instant.MIN); } @Override @@ -64,7 +59,7 @@ class InMemoryFolder extends InMemoryNode implements Folder { return; } parent.create(); - parent.children.compute(name, (k, v) -> { + parent.existingChildren.compute(name, (k, v) -> { if (v == null) { this.lastModified = Instant.now(); return this; @@ -72,7 +67,6 @@ class InMemoryFolder extends InMemoryNode implements Folder { throw new UncheckedIOException(new FileExistsException(k)); } }); - parent.volatileFolders.remove(name); assert this.exists(); creationTime = Instant.now(); } @@ -82,22 +76,22 @@ class InMemoryFolder extends InMemoryNode implements Folder { if (target.exists()) { target.delete(); } - assert !target.exists(); + assert!target.exists(); target.create(); this.copyTo(target); this.delete(); - assert !this.exists(); + assert!this.exists(); } @Override public void delete() { // remove ourself from parent: - parent.children.computeIfPresent(name, (k, v) -> { + parent.existingChildren.computeIfPresent(name, (k, v) -> { // returning null removes the entry. return null; }); // delete all children: - for (Iterator> iterator = children.entrySet().iterator(); iterator.hasNext();) { + for (Iterator> iterator = existingChildren.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = iterator.next(); iterator.remove(); // recursively on folders: @@ -108,7 +102,7 @@ class InMemoryFolder extends InMemoryNode implements Folder { subFolder.delete(); } } - assert !this.exists(); + assert!this.exists(); } @Override diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java index 2bfc0a782..79feedf97 100644 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java @@ -28,16 +28,16 @@ class BlacklistingFolder extends DelegatingFolder folders() { - return delegate.folders().filter(hiddenNodes.negate()).map(this::folder); + return delegate.folders().filter(hiddenNodes.negate()).map(this::newFolder); } @Override public Stream files() { - return delegate.files().filter(hiddenNodes.negate()).map(this::file); + return delegate.files().filter(hiddenNodes.negate()).map(this::newFile); } @Override - protected BlacklistingFile file(File delegate) { + protected BlacklistingFile newFile(File delegate) { if (hiddenNodes.test(delegate)) { throw new UncheckedIOException("'" + delegate.name() + "' is a reserved name.", new FileAlreadyExistsException(delegate.name())); } @@ -45,7 +45,7 @@ class BlacklistingFolder extends DelegatingFolder METHODS_TO_LOG_DETAILED = methodsToLog(); + + private static final Set methodsToLog() { + String methodsToLog = System.getProperty("cryptomator.LoggingHttpFilter.methodsToLogDetailed"); + if (methodsToLog == null) { + return Collections.emptySet(); + } else { + return new HashSet<>(asList(methodsToLog.toUpperCase().split(","))); + } + } + + private final Logger LOG = LoggerFactory.getLogger(LoggingHttpFilter.class); + + @Override + public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + if (METHODS_TO_LOG_DETAILED.contains(request.getMethod().toUpperCase())) { + logDetailed(request, response, chain); + } else { + logBasic(request, response, chain); + } + } + + private void logBasic(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + Optional thrown = Optional.empty(); + try { + chain.doFilter(request, response); + } catch (IOException | ServletException e) { + thrown = Optional.of(e); + throw e; + } catch (RuntimeException | Error e) { + thrown = Optional.of(e); + throw e; + } finally { + if (thrown.isPresent()) { + logError(request, thrown.get()); + } else { + logSuccess(request, response); + } + } + } + + private void logDetailed(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + RecordingHttpServletRequest recordingRequest = new RecordingHttpServletRequest(request); + RecordingHttpServletResponse recordingResponse = new RecordingHttpServletResponse(response); + Optional thrown = Optional.empty(); + try { + chain.doFilter(recordingRequest, recordingResponse); + } catch (IOException | ServletException e) { + thrown = Optional.of(e); + throw e; + } catch (RuntimeException | Error e) { + thrown = Optional.of(e); + throw e; + } finally { + if (thrown.isPresent()) { + logError(recordingRequest, thrown.get()); + } else { + logSuccess(recordingRequest, recordingResponse); + } + } + } + + private void logSuccess(HttpServletRequest request, HttpServletResponse response) { + LOG.debug(format( + "## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "## Response ##\n" // + + "%s %s\n" // + + "%s\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + request.getProtocol(), response.getStatus(), // + headers(response))); + } + + private void logError(HttpServletRequest request, Throwable throwable) { + LOG.error( + format("## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request)), // + throwable); + } + + private void logSuccess(RecordingHttpServletRequest request, RecordingHttpServletResponse response) { + LOG.debug(format( + "## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n" // + + "## Response ##\n" // + + "%s %s\n" // + + "%s\n" // + + "%s", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + new String(request.getRecording()), // + request.getProtocol(), response.getStatus(), // + headers(response), // + new String(response.getRecording()))); + } + + private void logError(RecordingHttpServletRequest request, Throwable throwable) { + LOG.error( + format("## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + new String(request.getRecording())), // + throwable); + } + + private String headers(HttpServletResponse response) { + StringBuilder result = new StringBuilder(); + for (String headerName : response.getHeaderNames()) { + for (String value : response.getHeaders(headerName)) { + result.append(headerName).append(": ").append(value).append('\n'); + } + } + return result.toString(); + } + + private String headers(HttpServletRequest request) { + StringBuilder result = new StringBuilder(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + Enumeration values = request.getHeaders(headerName); + while (values.hasMoreElements()) { + result.append(headerName).append(": ").append(values.nextElement()).append('\n'); + } + } + return result.toString(); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // empty + } + + @Override + public void destroy() { + // empty + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java new file mode 100644 index 000000000..dc4734dd3 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java @@ -0,0 +1,27 @@ +package org.cryptomator.webdav.filters; + +import java.io.IOException; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +class RecordingHttpServletRequest extends HttpServletRequestWrapper { + + private final RecordingServletInputStream recording; + + public RecordingHttpServletRequest(HttpServletRequest request) throws IOException { + super(request); + recording = new RecordingServletInputStream(request.getInputStream()); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return recording; + } + + public byte[] getRecording() { + return recording.getRecording(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java new file mode 100644 index 000000000..76444f2ab --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java @@ -0,0 +1,27 @@ +package org.cryptomator.webdav.filters; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +class RecordingHttpServletResponse extends HttpServletResponseWrapper { + + private final RecordingServletOutputStream recording; + + public RecordingHttpServletResponse(HttpServletResponse response) throws IOException { + super(response); + recording = new RecordingServletOutputStream(response.getOutputStream()); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return recording; + } + + public byte[] getRecording() { + return recording.getRecording(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java new file mode 100644 index 000000000..8ed3b0c55 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java @@ -0,0 +1,73 @@ +package org.cryptomator.webdav.filters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +import org.apache.commons.io.input.TeeInputStream; + +class RecordingServletInputStream extends ServletInputStream { + + private final ServletInputStream delegate; + private final TeeInputStream teeInputStream; + private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); + + public RecordingServletInputStream(ServletInputStream delegate) { + this.delegate = delegate; + this.teeInputStream = new TeeInputStream(delegate, recording); + } + + public int read() throws IOException { + return teeInputStream.read(); + } + + public int read(byte[] b) throws IOException { + return teeInputStream.read(b); + } + + public int read(byte[] b, int off, int len) throws IOException { + return teeInputStream.read(b, off, len); + } + + public boolean isFinished() { + return delegate.isFinished(); + } + + public boolean isReady() { + return delegate.isReady(); + } + + public void setReadListener(ReadListener readListener) { + delegate.setReadListener(readListener); + } + + public long skip(long n) throws IOException { + return teeInputStream.skip(n); + } + + public int available() throws IOException { + return teeInputStream.available(); + } + + public void close() throws IOException { + teeInputStream.close(); + } + + public byte[] getRecording() { + return recording.toByteArray(); + } + + public void mark(int readlimit) { + } + + public void reset() throws IOException { + throw new IOException("Mark not supported"); + } + + public boolean markSupported() { + return false; + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java new file mode 100644 index 000000000..6c291163d --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java @@ -0,0 +1,54 @@ +package org.cryptomator.webdav.filters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +import org.apache.commons.io.output.TeeOutputStream; + +class RecordingServletOutputStream extends ServletOutputStream { + + private final ServletOutputStream delegate; + private final TeeOutputStream teeOutputStream; + private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); + + public RecordingServletOutputStream(ServletOutputStream delegate) { + this.delegate = delegate; + this.teeOutputStream = new TeeOutputStream(delegate, recording); + } + + public void write(int b) throws IOException { + teeOutputStream.write(b); + } + + public void write(byte[] b) throws IOException { + teeOutputStream.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + teeOutputStream.write(b, off, len); + } + + public void flush() throws IOException { + teeOutputStream.flush(); + } + + public void close() throws IOException { + teeOutputStream.close(); + } + + public boolean isReady() { + return delegate.isReady(); + } + + public void setWriteListener(WriteListener writeListener) { + delegate.setWriteListener(writeListener); + } + + public byte[] getRecording() { + return recording.toByteArray(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java index 54dae472e..9081b6fe2 100644 --- a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java @@ -18,6 +18,7 @@ import javax.servlet.DispatcherType; import org.cryptomator.filesystem.FileSystem; import org.cryptomator.webdav.filters.AcceptRangeFilter; +import org.cryptomator.webdav.filters.LoggingHttpFilter; import org.cryptomator.webdav.filters.UriNormalizationFilter; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; @@ -54,6 +55,7 @@ class FileSystemBasedWebDavServer { servletContext.addServlet(servletHolder, "/*"); servletContext.addFilter(AcceptRangeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); servletContext.addFilter(UriNormalizationFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + servletContext.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); servletCollection.mapContexts(); server.setConnectors(new Connector[] { localConnector }); diff --git a/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml b/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml index 4f9b18066..39c2f8545 100644 --- a/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml +++ b/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml @@ -23,7 +23,6 @@ -