org.cryptomator.frontend.webdav.WebDavServerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptomator.frontend.webdav.WebDavServerTest.java

Source

/*******************************************************************************
 * Copyright (c) 2016 Sebastian Stenzel and others.
 * This file is licensed under the terms of the MIT license.
 * See the LICENSE.txt file for more info.
 *
 * Contributors:
 *     Sebastian Stenzel - initial API and implementation
 *******************************************************************************/
package org.cryptomator.frontend.webdav;

import static org.hamcrest.collection.IsArrayContaining.hasItemInArray;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathException;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.client.methods.CopyMethod;
import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
import org.apache.jackrabbit.webdav.client.methods.LockMethod;
import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
import org.apache.jackrabbit.webdav.lock.LockInfo;
import org.apache.jackrabbit.webdav.lock.Scope;
import org.apache.jackrabbit.webdav.lock.Type;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

public class WebDavServerTest {

    private static final WebDavServer SERVER = DaggerWebDavComponent.create().server();
    private static final Logger LOG = LoggerFactory.getLogger(WebDavServerTest.class);
    private String servletRoot;
    private FileSystem fs;
    private ServletContextHandler servlet;

    @BeforeClass
    public static void startServer() throws URISyntaxException {
        SERVER.start();
    }

    @AfterClass
    public static void stopServer() {
        SERVER.stop();
    }

    @Before
    public void startServlet() throws Exception {
        fs = new InMemoryFileSystem();
        servletRoot = "http://localhost:" + SERVER.getPort() + "/test";
        servlet = SERVER.addServlet(fs, URI.create(servletRoot));
        servlet.start();
    }

    @After
    public void stopServlet() throws Exception {
        servlet.stop();
    }

    @Test
    public void testGet() throws HttpException, IOException {
        final HttpClient client = new HttpClient();

        // write test content:
        final byte[] testContent = "hello world".getBytes();
        try (WritableFile w = fs.file("foo.txt").openWritable()) {
            w.write(ByteBuffer.wrap(testContent));
        }

        // check get response body:
        final HttpMethod getMethod = new GetMethod(servletRoot + "/foo.txt");
        final int statusCode = client.executeMethod(getMethod);
        Assert.assertEquals(200, statusCode);
        Assert.assertThat(getMethod.getResponseHeaders(), hasItemInArray(new Header("Accept-Ranges", "bytes")));
        Assert.assertTrue(
                IOUtils.contentEquals(new ByteArrayInputStream(testContent), getMethod.getResponseBodyAsStream()));
        getMethod.releaseConnection();
    }

    @Test
    public void testMkcol() throws HttpException, IOException {
        final HttpClient client = new HttpClient();

        // create directory:
        final HttpMethod mkcolMethod = new MkColMethod(servletRoot + "/foo");
        final int statusCode = client.executeMethod(mkcolMethod);
        Assert.assertEquals(201, statusCode);
        Assert.assertTrue(fs.folder("foo").exists());

        mkcolMethod.releaseConnection();
    }

    @Test
    public void testPut() throws HttpException, IOException {
        final HttpClient client = new HttpClient();

        // create file:
        final byte[] testContent = "hello world".getBytes();
        final EntityEnclosingMethod putMethod = new PutMethod(servletRoot + "/foo.txt");
        putMethod.setRequestEntity(new ByteArrayRequestEntity(testContent));
        final int putResponse = client.executeMethod(putMethod);
        Assert.assertEquals(201, putResponse);
        Assert.assertTrue(fs.file("foo.txt").exists());

        // check file contents:
        ByteBuffer buf = ByteBuffer.allocate(testContent.length);
        try (ReadableFile r = fs.file("foo.txt").openReadable()) {
            r.read(buf);
        }
        Assert.assertArrayEquals(testContent, buf.array());

        putMethod.releaseConnection();
    }

    /* PROPFIND */

    @Test
    public void testPropfind() throws HttpException, IOException, ParserConfigurationException, SAXException,
            XPathException, DavException {
        final HttpClient client = new HttpClient();

        fs.folder("folder1").create();
        fs.folder("folder2").create();
        try (WritableFile w = fs.file("file1").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        // get directory listing of /:
        final DavMethodBase propfindMethod = new PropFindMethod(servletRoot + "/");
        final int statusCode = client.executeMethod(propfindMethod);
        Assert.assertEquals(207, statusCode);

        // get hrefs contained in dirlisting response:
        MultiStatus ms = propfindMethod.getResponseBodyAsMultiStatus();
        propfindMethod.releaseConnection();
        Collection<String> hrefs = Arrays.asList(ms.getResponses()).stream().map(MultiStatusResponse::getHref)
                .collect(Collectors.toSet());

        Assert.assertTrue(CollectionUtils.containsAll(hrefs,
                Arrays.asList(servletRoot + "/folder1/", servletRoot + "/folder2/", servletRoot + "/file1")));
    }

    /* LOCK */

    @Test
    public void testLockExisting() throws IOException, DavException {
        final HttpClient client = new HttpClient();

        // create file:
        try (WritableFile writable = fs.file("foo.txt").openWritable()) {
            writable.write(ByteBuffer.allocate(0));
        }

        // lock existing file:
        LockInfo lockInfo = new LockInfo(Scope.EXCLUSIVE, Type.WRITE, "le me", 3600, false);
        final DavMethodBase lockMethod = new LockMethod(servletRoot + "/foo.txt", lockInfo);
        final int lockResponse = client.executeMethod(lockMethod);
        Assert.assertEquals(200, lockResponse);

        lockMethod.releaseConnection();
    }

    @Test
    public void testLockNonExisting() throws IOException, DavException {
        final HttpClient client = new HttpClient();

        // lock nonexisting file:
        LockInfo lockInfo = new LockInfo(Scope.EXCLUSIVE, Type.WRITE, "le me", 3600, false);
        final DavMethodBase lockMethod = new LockMethod(servletRoot + "/foo.txt", lockInfo);
        final int lockResponse = client.executeMethod(lockMethod);
        Assert.assertEquals(201, lockResponse);
        Assert.assertTrue(fs.file("foo.txt").exists());

        lockMethod.releaseConnection();
    }

    /* MOVE */

    @Test
    public void testMoveFolder() throws HttpException, IOException, ParserConfigurationException, SAXException,
            XPathException, DavException {
        final HttpClient client = new HttpClient();

        fs.folder("srcFolder").create();

        final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder",
                false);
        client.executeMethod(moveMethod);
        Assert.assertTrue(moveMethod.succeeded());
        moveMethod.releaseConnection();

        Assert.assertFalse(fs.folder("srcFolder").exists());
        Assert.assertTrue(fs.folder("dstFolder").exists());
    }

    @Test
    public void testMoveFolderOverwrite() throws HttpException, IOException, ParserConfigurationException,
            SAXException, XPathException, DavException {
        final HttpClient client = new HttpClient();

        fs.folder("srcFolder").create();
        try (WritableFile w = fs.file("dstFolder").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder",
                true);
        client.executeMethod(moveMethod);
        Assert.assertTrue(moveMethod.succeeded());
        moveMethod.releaseConnection();

        Assert.assertFalse(fs.folder("srcFolder").exists());
        Assert.assertTrue(fs.folder("dstFolder").exists());
        Assert.assertFalse(fs.file("dstFolder").exists());
    }

    @Test
    public void testMoveFile() throws HttpException, IOException, ParserConfigurationException, SAXException,
            XPathException, DavException {
        final HttpClient client = new HttpClient();

        try (WritableFile w = fs.file("srcFile").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/",
                false);
        client.executeMethod(moveMethod);
        Assert.assertTrue(moveMethod.succeeded());
        moveMethod.releaseConnection();

        Assert.assertFalse(fs.file("srcFile").exists());
        Assert.assertTrue(fs.file("dstFile").exists());
    }

    @Test
    public void testMoveFileOverwrite() throws HttpException, IOException, ParserConfigurationException,
            SAXException, XPathException, DavException {
        final HttpClient client = new HttpClient();

        try (WritableFile w = fs.file("srcFile").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }
        fs.folder("dstFile").create();

        final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", true);
        client.executeMethod(moveMethod);
        Assert.assertTrue(moveMethod.succeeded());
        moveMethod.releaseConnection();

        Assert.assertFalse(fs.file("srcFile").exists());
        Assert.assertTrue(fs.file("dstFile").exists());
        Assert.assertFalse(fs.folder("dstFile").exists());
    }

    /* COPY */

    @Test
    public void testCopyFolder() throws HttpException, IOException, ParserConfigurationException, SAXException,
            XPathException, DavException {
        final HttpClient client = new HttpClient();

        fs.folder("srcFolder").folder("sub").create();
        try (WritableFile w = fs.folder("srcFolder").file("file").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder",
                false);
        client.executeMethod(copyMethod);
        Assert.assertTrue(copyMethod.succeeded());
        copyMethod.releaseConnection();

        Assert.assertTrue(fs.folder("srcFolder").folder("sub").exists());
        Assert.assertTrue(fs.folder("srcFolder").file("file").exists());
        Assert.assertTrue(fs.folder("dstFolder").folder("sub").exists());
        Assert.assertTrue(fs.folder("dstFolder").file("file").exists());
    }

    @Test
    public void testCopyFolderOverwrite() throws HttpException, IOException, ParserConfigurationException,
            SAXException, XPathException, DavException {
        final HttpClient client = new HttpClient();

        fs.folder("srcFolder").create();
        try (WritableFile w = fs.file("dstFolder").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder/",
                true);
        client.executeMethod(copyMethod);
        Assert.assertTrue(copyMethod.succeeded());
        copyMethod.releaseConnection();

        Assert.assertTrue(fs.folder("srcFolder").exists());
        Assert.assertTrue(fs.folder("dstFolder").exists());
        Assert.assertFalse(fs.file("dstFolder").exists());
    }

    @Test
    public void testCopyFile() throws HttpException, IOException, ParserConfigurationException, SAXException,
            XPathException, DavException {
        final HttpClient client = new HttpClient();

        try (WritableFile w = fs.file("srcFile").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }

        final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/",
                false);
        client.executeMethod(copyMethod);
        Assert.assertTrue(copyMethod.succeeded());
        copyMethod.releaseConnection();

        Assert.assertTrue(fs.file("srcFile").exists());
        Assert.assertTrue(fs.file("dstFile").exists());
    }

    @Test
    public void testCopyFileOverwrite() throws HttpException, IOException, ParserConfigurationException,
            SAXException, XPathException, DavException {
        final HttpClient client = new HttpClient();

        try (WritableFile w = fs.file("srcFile").openWritable()) {
            w.write(ByteBuffer.allocate(0));
        }
        fs.folder("dstFile").create();

        final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", true);
        client.executeMethod(copyMethod);
        Assert.assertTrue(copyMethod.succeeded());
        copyMethod.releaseConnection();

        Assert.assertTrue(fs.file("srcFile").exists());
        Assert.assertTrue(fs.file("dstFile").exists());
        Assert.assertFalse(fs.folder("dstFile").exists());
    }

    /* Range requests */

    @Test
    public void testGetWithUnsatisfiableRange() throws IOException {
        final HttpClient client = new HttpClient();

        // write test content:
        final byte[] testContent = "hello world".getBytes();
        try (WritableFile w = fs.file("foo.txt").openWritable()) {
            w.write(ByteBuffer.wrap(testContent));
        }

        // check get response body:
        final HttpMethod getMethod = new GetMethod(servletRoot + "/foo.txt");
        getMethod.addRequestHeader("Range", "chunks=1-2");
        final int statusCode = client.executeMethod(getMethod);
        Assert.assertEquals(200, statusCode);
        Assert.assertArrayEquals(testContent, getMethod.getResponseBody());
        getMethod.releaseConnection();
    }

    @Test
    public void testMultipleGetWithRangeAsync() throws IOException, URISyntaxException, InterruptedException {
        final String testResourceUrl = servletRoot + "/foo.txt";

        // prepare 8MiB test data:
        final byte[] plaintextData = new byte[2097152 * Integer.BYTES];
        final ByteBuffer plaintextDataByteBuffer = ByteBuffer.wrap(plaintextData);
        for (int i = 0; i < 2097152; i++) {
            plaintextDataByteBuffer.putInt(i);
        }
        try (WritableFile w = fs.file("foo.txt").openWritable()) {
            plaintextDataByteBuffer.flip();
            w.write(plaintextDataByteBuffer);
        }

        final MultiThreadedHttpConnectionManager cm = new MultiThreadedHttpConnectionManager();
        cm.getParams().setDefaultMaxConnectionsPerHost(50);
        final HttpClient client = new HttpClient(cm);

        // multiple async range requests:
        final List<ForkJoinTask<?>> tasks = new ArrayList<>();
        final Random generator = new Random(System.currentTimeMillis());

        final AtomicBoolean success = new AtomicBoolean(true);

        // 10 full interrupted requests:
        for (int i = 0; i < 10; i++) {
            final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
                try {
                    final HttpMethod getMethod = new GetMethod(testResourceUrl);
                    final int statusCode = client.executeMethod(getMethod);
                    if (statusCode != 200) {
                        LOG.error("Invalid status code for interrupted full request");
                        success.set(false);
                    }
                    getMethod.getResponseBodyAsStream().read();
                    getMethod.getResponseBodyAsStream().close();
                    getMethod.releaseConnection();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            tasks.add(task);
        }

        // 50 crappy interrupted range requests:
        for (int i = 0; i < 50; i++) {
            final int lower = generator.nextInt(plaintextData.length);
            final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
                try {
                    final HttpMethod getMethod = new GetMethod(testResourceUrl);
                    getMethod.addRequestHeader("Range", "bytes=" + lower + "-");
                    final int statusCode = client.executeMethod(getMethod);
                    if (statusCode != 206) {
                        LOG.error("Invalid status code for interrupted range request");
                        success.set(false);
                    }
                    getMethod.getResponseBodyAsStream().read();
                    getMethod.getResponseBodyAsStream().close();
                    getMethod.releaseConnection();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            tasks.add(task);
        }

        // 50 normal open range requests:
        for (int i = 0; i < 50; i++) {
            final int lower = generator.nextInt(plaintextData.length - 512);
            final int upper = plaintextData.length - 1;
            final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
                try {
                    final HttpMethod getMethod = new GetMethod(testResourceUrl);
                    getMethod.addRequestHeader("Range", "bytes=" + lower + "-");
                    final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1);
                    final int statusCode = client.executeMethod(getMethod);
                    final byte[] responseBody = new byte[upper - lower + 10];
                    final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody);
                    getMethod.releaseConnection();
                    if (statusCode != 206) {
                        LOG.error("Invalid status code for open range request");
                        success.set(false);
                    } else if (upper - lower + 1 != bytesRead) {
                        LOG.error("Invalid response length for open range request");
                        success.set(false);
                    } else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) {
                        LOG.error("Invalid response body for open range request");
                        success.set(false);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            tasks.add(task);
        }

        // 200 normal closed range requests:
        for (int i = 0; i < 200; i++) {
            final int pos1 = generator.nextInt(plaintextData.length - 512);
            final int pos2 = pos1 + 512;
            final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
                try {
                    final int lower = Math.min(pos1, pos2);
                    final int upper = Math.max(pos1, pos2);
                    final HttpMethod getMethod = new GetMethod(testResourceUrl);
                    getMethod.addRequestHeader("Range", "bytes=" + lower + "-" + upper);
                    final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1);
                    final int statusCode = client.executeMethod(getMethod);
                    final byte[] responseBody = new byte[upper - lower + 1];
                    final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody);
                    getMethod.releaseConnection();
                    if (statusCode != 206) {
                        LOG.error("Invalid status code for closed range request");
                        success.set(false);
                    } else if (upper - lower + 1 != bytesRead) {
                        LOG.error("Invalid response length for closed range request");
                        success.set(false);
                    } else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) {
                        LOG.error("Invalid response body for closed range request");
                        success.set(false);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            tasks.add(task);
        }

        Collections.shuffle(tasks, generator);

        final ForkJoinPool pool = new ForkJoinPool(4);
        for (ForkJoinTask<?> task : tasks) {
            pool.execute(task);
        }
        for (ForkJoinTask<?> task : tasks) {
            task.join();
        }
        pool.shutdown();
        cm.shutdown();

        Assert.assertTrue(success.get());
    }

}