fixed range requests

This commit is contained in:
Tobias Hagemann
2017-01-25 15:48:21 +01:00
parent 28fedafb59
commit 8a6265658e
3 changed files with 23 additions and 76 deletions

View File

@@ -16,7 +16,6 @@ import java.util.Objects;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.OutputContext;
@@ -36,7 +35,7 @@ class DavFileWithRange extends DavFile {
private final Pair<String, String> requestRange;
public DavFileWithRange(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node, Pair<String, String> requestRange) throws DavException {
public DavFileWithRange(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node, Pair<String, String> requestRange) {
super(factory, lockManager, session, node);
this.requestRange = Objects.requireNonNull(requestRange);
}
@@ -48,18 +47,18 @@ class DavFileWithRange extends DavFile {
return;
}
final long contentLength = node.size();
final Pair<Long, Long> range = getEffectiveRange(contentLength);
if (range.getLeft() < 0 || range.getLeft() > range.getRight() || range.getRight() > contentLength) {
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
throw new UncheckedDavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE, "Valid Range would be in [0, " + contentLength + "]");
}
final Long rangeLength = range.getRight() - range.getLeft() + 1;
outputContext.setContentLength(rangeLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), contentRangeResponseHeader(range.getLeft(), range.getRight(), contentLength));
outputContext.setContentType(CONTENT_TYPE_VALUE);
outputContext.setProperty(CONTENT_DISPOSITION_HEADER, CONTENT_DISPOSITION_VALUE);
outputContext.setProperty(X_CONTENT_TYPE_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_VALUE);
try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) {
final Pair<Long, Long> range = getEffectiveRange(contentLength);
if (range.getLeft() < 0 || range.getLeft() > range.getRight() || range.getRight() > contentLength) {
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
throw new UncheckedDavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE, "Valid Range would be in [0, " + contentLength + "]");
}
final Long rangeLength = range.getRight() - range.getLeft() + 1;
outputContext.setContentLength(rangeLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), contentRangeResponseHeader(range.getLeft(), range.getRight(), contentLength));
outputContext.setContentType(CONTENT_TYPE_VALUE);
outputContext.setProperty(CONTENT_DISPOSITION_HEADER, CONTENT_DISPOSITION_VALUE);
outputContext.setProperty(X_CONTENT_TYPE_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_VALUE);
src.position(range.getLeft());
InputStream limitedIn = ByteStreams.limit(Channels.newInputStream(src), rangeLength);
ByteStreams.copy(limitedIn, out);

View File

@@ -1,50 +0,0 @@
/*******************************************************************************
* 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.jackrabbitservlet;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.jackrabbit.FileLocator;
import org.eclipse.jetty.http.HttpHeader;
import com.google.common.io.ByteStreams;
/**
* Sends the full file in reaction to an unsatisfiable range.
*
* @see {@link https://tools.ietf.org/html/rfc7233#section-4.2}
*/
class DavFileWithUnsatisfiableRange extends DavFile {
public DavFileWithUnsatisfiableRange(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) throws DavException {
super(factory, lockManager, session, node);
}
@Override
public void spool(OutputContext outputContext) throws IOException {
outputContext.setModificationTime(node.lastModified().toEpochMilli());
if (!outputContext.hasStream()) {
return;
}
final long contentLength = node.size();
outputContext.setContentLength(contentLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) {
ByteStreams.copy(src, Channels.newChannel(out));
}
}
}

View File

@@ -85,17 +85,11 @@ class FilesystemResourceFactory implements DavResourceFactory {
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
try {
// 206 for ranged resources:
final Pair<String, String> parsedRange = parseRangeRequestHeader(rangeHeader);
final Pair<String, String> parsedRange = parseSingleByteRange(rangeHeader);
response.setStatus(DavServletResponse.SC_PARTIAL_CONTENT);
return new DavFileWithRange(this, lockManager, session, file, parsedRange);
} catch (DavException ex) {
if (ex.getErrorCode() == DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE) {
// 416 for unsatisfiable ranges:
response.setStatus(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
return new DavFileWithUnsatisfiableRange(this, lockManager, session, file);
} else {
throw new DavException(ex.getErrorCode(), ex);
}
} catch (NotASingleByteRangeException ex) {
return createFile(file, session);
}
}
@@ -108,17 +102,18 @@ class FilesystemResourceFactory implements DavResourceFactory {
* </code>
*
* @return Tuple of lower and upper range.
* @throws DavException HTTP statuscode 400 for malformed requests. 416 if requested range is not supported.
* @throws DavException HTTP statuscode 400 for malformed requests.
* @throws NotASingleByteRangeException Indicating a range that is not supported by this server, i.e. range header should be ignored.
*/
private Pair<String, String> parseRangeRequestHeader(String rangeHeader) throws DavException {
private Pair<String, String> parseSingleByteRange(String rangeHeader) throws DavException, NotASingleByteRangeException {
assert rangeHeader != null;
if (!rangeHeader.startsWith(RANGE_BYTE_PREFIX)) {
throw new DavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
throw new NotASingleByteRangeException();
}
final String byteRangeSet = StringUtils.removeStartIgnoreCase(rangeHeader, RANGE_BYTE_PREFIX);
final String[] byteRanges = StringUtils.split(byteRangeSet, RANGE_SET_SEP);
if (byteRanges.length != 1) {
throw new DavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
throw new NotASingleByteRangeException();
}
final String byteRange = byteRanges[0];
final String[] bytePos = StringUtils.splitPreserveAllTokens(byteRange, RANGE_SEP);
@@ -146,4 +141,7 @@ class FilesystemResourceFactory implements DavResourceFactory {
}
}
private static class NotASingleByteRangeException extends Exception {
}
}