org.cryptomator.frontend.webdav.servlet.DavResourceFactoryImpl.java Source code

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2016, 2017 Sebastian Stenzel and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the accompanying LICENSE.txt.
 *
 * Contributors:
 *     Sebastian Stenzel - initial API and implementation
 *******************************************************************************/
package org.cryptomator.frontend.webdav.servlet;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;

import javax.inject.Inject;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletRequest;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.cryptomator.frontend.webdav.servlet.ByteRange.MalformedByteRangeException;
import org.cryptomator.frontend.webdav.servlet.ByteRange.UnsupportedRangeException;
import org.cryptomator.frontend.webdav.servlet.WebDavServletModule.PerServlet;
import org.cryptomator.frontend.webdav.servlet.WebDavServletModule.RootPath;
import org.eclipse.jetty.http.HttpHeader;

@PerServlet
class DavResourceFactoryImpl implements DavResourceFactory {

    private final Path rootPath;
    private final LockManager lockManager;

    @Inject
    public DavResourceFactoryImpl(@RootPath Path rootPath, ExclusiveSharedLockManager lockManager) {
        this.rootPath = rootPath;
        this.lockManager = lockManager;
    }

    @Override
    public DavResource createResource(DavResourceLocator locator, DavServletRequest request,
            DavServletResponse response) throws DavException {
        if (locator instanceof DavLocatorImpl && locator.equals(request.getRequestLocator())) {
            return createRequestResource((DavLocatorImpl) locator, request, response);
        } else if (locator instanceof DavLocatorImpl && locator.equals(request.getDestinationLocator())) {
            return createDestinationResource((DavLocatorImpl) locator, request, response);
        } else {
            throw new IllegalArgumentException("Unsupported locator of type " + locator.getClass());
        }
    }

    private DavResource createRequestResource(DavLocatorImpl locator, DavServletRequest request,
            DavServletResponse response) throws DavException {
        assert locator.equals(request.getRequestLocator());
        Path p = rootPath.resolve(locator.getResourcePath());
        Optional<BasicFileAttributes> attr = readBasicFileAttributes(p);
        if (!attr.isPresent() && DavMethods.METHOD_PUT.equals(request.getMethod())) {
            return createFile(locator, p, Optional.empty(), request.getDavSession());
        } else if (!attr.isPresent() && DavMethods.METHOD_MKCOL.equals(request.getMethod())) {
            return createFolder(locator, p, Optional.empty(), request.getDavSession());
        } else if (!attr.isPresent() && DavMethods.METHOD_LOCK.equals(request.getMethod())) {
            // locking non-existing resources must create a non-collection resource:
            // https://tools.ietf.org/html/rfc4918#section-9.10.4
            // See also: DavFile#lock(...)
            return createFile(locator, p, Optional.empty(), request.getDavSession());
        } else if (!attr.isPresent()) {
            throw new DavException(DavServletResponse.SC_NOT_FOUND);
        } else if (attr.get().isRegularFile() && DavMethods.METHOD_GET.equals(request.getMethod())
                && request.getHeader(HttpHeader.RANGE.asString()) != null) {
            return createFileRange(locator, p, attr.get(), request.getDavSession(), request, response);
        } else if (attr.get().isRegularFile()) {
            return createFile(locator, p, attr, request.getDavSession());
        } else if (attr.get().isDirectory()) {
            return createFolder(locator, p, attr, request.getDavSession());
        } else {
            throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Resource is neither file nor directory.");
        }
    }

    private DavResource createDestinationResource(DavLocatorImpl locator, DavServletRequest request,
            DavServletResponse response) throws DavException {
        assert locator.equals(request.getDestinationLocator());
        assert ArrayUtils.contains(new String[] { DavMethods.METHOD_MOVE, DavMethods.METHOD_COPY },
                request.getMethod());
        Path srcP = rootPath.resolve(request.getRequestLocator().getResourcePath());
        Path dstP = rootPath.resolve(locator.getResourcePath());
        Optional<BasicFileAttributes> srcAttr = readBasicFileAttributes(srcP);
        Optional<BasicFileAttributes> dstAttr = readBasicFileAttributes(dstP);
        if (!srcAttr.isPresent()) {
            throw new DavException(DavServletResponse.SC_NOT_FOUND);
        } else if (srcAttr.get().isRegularFile()) {
            return createFile(locator, dstP, dstAttr, request.getDavSession());
        } else if (srcAttr.get().isDirectory()) {
            return createFolder(locator, dstP, dstAttr, request.getDavSession());
        } else {
            throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Resource is neither file nor directory.");
        }
    }

    @Override
    public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
        if (locator instanceof DavLocatorImpl) {
            return createResourceInternal((DavLocatorImpl) locator, session);
        } else {
            throw new IllegalArgumentException("Unsupported locator of type " + locator.getClass());
        }
    }

    private DavResource createResourceInternal(DavLocatorImpl locator, DavSession session) throws DavException {
        Path p = rootPath.resolve(locator.getResourcePath());
        Optional<BasicFileAttributes> attr = readBasicFileAttributes(p);
        if (!attr.isPresent()) {
            throw new DavException(DavServletResponse.SC_NOT_FOUND);
        } else if (attr.get().isRegularFile()) {
            return createFile(locator, p, attr, session);
        } else if (attr.get().isDirectory()) {
            return createFolder(locator, p, attr, session);
        } else {
            throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Resource is neither file nor directory.");
        }
    }

    /**
     * @return BasicFileAttributes or {@link Optional#empty()} if the file/folder for the given path does not exist.
     * @throws DavException If an {@link IOException} occured during {@link Files#readAttributes(Path, Class, java.nio.file.LinkOption...)}.
     */
    private Optional<BasicFileAttributes> readBasicFileAttributes(Path path) throws DavException {
        try {
            return Optional.of(Files.readAttributes(path, BasicFileAttributes.class));
        } catch (NoSuchFileException e) {
            return Optional.empty();
        } catch (IOException e) {
            throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e);
        }
    }

    DavFolder createFolder(DavLocatorImpl locator, Path path, Optional<BasicFileAttributes> attr,
            DavSession session) {
        return new DavFolder(this, lockManager, locator, path, attr, session);
    }

    DavFile createFile(DavLocatorImpl locator, Path path, Optional<BasicFileAttributes> attr, DavSession session) {
        return new DavFile(this, lockManager, locator, path, attr, session);
    }

    private DavFile createFileRange(DavLocatorImpl locator, Path path, BasicFileAttributes attr, DavSession session,
            DavServletRequest request, DavServletResponse response) throws DavException {
        // 200 for "normal" resources, if if-range is not satisified:
        final String ifRangeHeader = request.getHeader(HttpHeader.IF_RANGE.asString());
        if (!isIfRangeHeaderSatisfied(attr, ifRangeHeader)) {
            return createFile(locator, path, Optional.of(attr), session);
        }

        final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
        try {
            // 206 for ranged resources:
            final ByteRange byteRange = new ByteRange(rangeHeader);
            response.setStatus(DavServletResponse.SC_PARTIAL_CONTENT);
            return new DavFileWithRange(this, lockManager, locator, path, attr, session, byteRange);
        } catch (UnsupportedRangeException ex) {
            return createFile(locator, path, Optional.of(attr), session);
        } catch (MalformedByteRangeException e) {
            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Malformed range header: " + rangeHeader);
        }
    }

    /**
     * @return <code>true</code> if a partial response should be generated according to an If-Range precondition.
     */
    private boolean isIfRangeHeaderSatisfied(BasicFileAttributes attr, String ifRangeHeader) throws DavException {
        if (ifRangeHeader == null) {
            // no header set -> satisfied implicitly
            return true;
        } else {
            try {
                Instant expectedTime = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(ifRangeHeader));
                Instant actualTime = attr.lastModifiedTime().toInstant();
                return expectedTime.compareTo(actualTime) == 0;
            } catch (DateTimeParseException e) {
                throw new DavException(DavServletResponse.SC_BAD_REQUEST,
                        "Unsupported If-Range header: " + ifRangeHeader);
            }
        }
    }

}