io.milton.http.http11.PutHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of milton-server-ce Show documentation
Show all versions of milton-server-ce Show documentation
Milton Community Edition: Supports DAV level 1 and is available on Apache2 license
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.milton.http.http11;
import io.milton.common.FileUtils;
import io.milton.common.LogUtils;
import io.milton.common.Path;
import io.milton.common.RandomFileOutputStream;
import io.milton.event.NewFolderEvent;
import io.milton.event.PutEvent;
import io.milton.http.FileItem;
import io.milton.http.Handler;
import io.milton.http.HandlerHelper;
import io.milton.http.HttpManager;
import io.milton.http.Range;
import io.milton.http.Request;
import io.milton.http.Request.Method;
import io.milton.http.RequestParseException;
import static io.milton.http.ResourceHandlerHelper.ATT_NAME_FILES;
import static io.milton.http.ResourceHandlerHelper.ATT_NAME_PARAMS;
import io.milton.http.Response;
import io.milton.http.Response.Status;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.ConflictException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.exceptions.NotFoundException;
import io.milton.http.quota.StorageChecker.StorageErrorReason;
import io.milton.http.webdav.WebDavResponseHandler;
import io.milton.resource.CalendarResource;
import io.milton.resource.CollectionResource;
import io.milton.resource.GetableResource;
import io.milton.resource.MakeCollectionableResource;
import io.milton.resource.PutableResource;
import io.milton.resource.ReplaceableResource;
import io.milton.resource.Resource;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PutHandler implements Handler {
private static final Logger log = LoggerFactory.getLogger(PutHandler.class);
private final Http11ResponseHandler responseHandler;
private final HandlerHelper handlerHelper;
private final PutHelper putHelper;
private final MatchHelper matchHelper;
public PutHandler(Http11ResponseHandler responseHandler, HandlerHelper handlerHelper, PutHelper putHelper, MatchHelper matchHelper) {
this.responseHandler = responseHandler;
this.handlerHelper = handlerHelper;
this.putHelper = putHelper;
this.matchHelper = matchHelper;
checkResponseHandler();
}
private void checkResponseHandler() {
if (!(responseHandler instanceof WebDavResponseHandler)) {
log.warn("response handler is not a WebDavResponseHandler, so locking and quota checking will not be enabled");
}
}
@Override
public String[] getMethods() {
return new String[]{Method.PUT.code};
}
@Override
public boolean isCompatible(Resource handler) {
return (handler instanceof PutableResource);
}
@Override
public void process(HttpManager manager, Request request, Response response) throws NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
// need a linked hash map to preserve ordering of params
Map params = new LinkedHashMap<>();
Map files = new HashMap<>();
try {
request.parseRequestParameters(params, files);
} catch (RequestParseException ex) {
if (log.isTraceEnabled()) {
log.warn("failed to parse request parameters: {}", ex.getMessage(), ex);
} else {
log.warn("failed to parse request parameters: {}", ex.getMessage());
}
return;
}
request.getAttributes().put(ATT_NAME_PARAMS, params);
request.getAttributes().put(ATT_NAME_FILES, files);
if (!handlerHelper.checkExpects(responseHandler, request, response)) {
return;
}
String host = request.getHostHeader();
String urlToCreateOrUpdate = HttpManager.decodeUrl(request.getAbsolutePath());
LogUtils.debug(log, "PUT request. Host:", host, " Url:", urlToCreateOrUpdate, " content length header:", request.getContentLengthHeader());
Path path = Path.path(urlToCreateOrUpdate);
urlToCreateOrUpdate = path.toString();
Resource existingResource = manager.getResourceFactory().getResource(host, urlToCreateOrUpdate);
StorageErrorReason storageErr = null;
if (existingResource != null) {
if (!handlerHelper.checkAuthorisation(manager, existingResource, request)) {
responseHandler.respondUnauthorised(existingResource, response, request);
return;
}
//Make sure the parent collection is not locked by someone else
if (handlerHelper.isLockedOut(request, existingResource)) {
log.warn("resource is locked, but not by the current user");
respondLocked(request, response, existingResource);
return;
}
// Check if the resource has been modified based on etags
if (!matchHelper.checkIfMatch(existingResource, request)) {
log.info("if-match comparison failed, aborting PUT request");
responseHandler.respondPreconditionFailed(request, response, existingResource);
return;
}
if (matchHelper.checkIfNoneMatch(existingResource, request)) {
log.info("if-none-match comparison failed, aborting PUT request");
responseHandler.respondPreconditionFailed(request, response, existingResource);
return;
}
Resource parent = manager.getResourceFactory().getResource(host, path.getParent().toString());
if (parent instanceof CollectionResource) {
CollectionResource parentCol = (CollectionResource) parent;
storageErr = handlerHelper.checkStorageOnReplace(request, parentCol, existingResource, host);
} else {
log.warn("parent exists but is not a collection resource: " + path.getParent());
}
} else {
if (!matchHelper.checkIfMatch(null, request)) {
// Special case: we will get a PUT on a calendar to create a resource with an etag check match
// when no resource exists, when a user accepts a calendar invitation from lightning
// So have a special case here that says to allow if the parent is a calendar
Resource parent = manager.getResourceFactory().getResource(host, path.getParent().toString());
if (parent instanceof CalendarResource) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=540398
log.info("if-match comparison failed on null resource, but parent is a calendar, so allow to proceed");
} else {
log.info("if-match comparison failed on null resource, aborting PUT request");
responseHandler.respondPreconditionFailed(request, response, existingResource);
return;
}
}
if (matchHelper.checkIfNoneMatch(null, request)) {
log.info("if-none-match comparison failed on null resource, aborting PUT request");
responseHandler.respondPreconditionFailed(request, response, existingResource);
return;
}
CollectionResource parentCol = putHelper.findNearestParent(manager, host, path);
if (!handlerHelper.checkAuthorisation(manager, parentCol, request)) {
responseHandler.respondUnauthorised(parentCol, response, request);
return;
}
storageErr = handlerHelper.checkStorageOnAdd(request, parentCol, path.getParent(), host);
}
if (storageErr != null) {
respondInsufficientStorage(request, response, storageErr);
return;
}
ReplaceableResource replacee;
if (existingResource instanceof ReplaceableResource) {
replacee = (ReplaceableResource) existingResource;
} else {
replacee = null;
}
if (replacee != null) {
if (log.isTraceEnabled()) {
log.trace("replacing content in: " + replacee.getName() + " - " + replacee.getClass());
}
long t = System.currentTimeMillis();
try {
manager.onProcessResourceStart(request, response, replacee);
processReplace(manager, request, response, replacee);
manager.getEventManager().fireEvent(new PutEvent(replacee));
} finally {
t = System.currentTimeMillis() - t;
manager.onProcessResourceFinish(request, response, replacee, t);
}
} else {
// either no existing resource, or its not replaceable. check for folder
String nameToCreate = path.getName();
CollectionResource folderResource = findOrCreateFolders(manager, host, path.getParent(), request);
if (folderResource != null) {
long t = System.currentTimeMillis();
try {
if (folderResource instanceof PutableResource) {
//Make sure the parent collection is not locked by someone else
if (handlerHelper.isLockedOut(request, folderResource)) {
respondLocked(request, response, folderResource);
return;
}
PutableResource putableResource = (PutableResource) folderResource;
processCreate(manager, request, response, putableResource, nameToCreate);
files.forEach((key, file) -> org.apache.commons.io.FileUtils.deleteQuietly(new File(file.getPath())));
} else {
LogUtils.debug(log, "method not implemented: PUT on class: ", folderResource.getClass(), folderResource.getName());
manager.getResponseHandler().respondMethodNotImplemented(folderResource, response, request);
}
} finally {
t = System.currentTimeMillis() - t;
manager.onProcessResourceFinish(request, response, folderResource, t);
}
} else {
responseHandler.respondNotFound(response, request);
}
}
}
private void processCreate(HttpManager manager, Request request, Response response, PutableResource folder, String newName) throws ConflictException, BadRequestException, NotAuthorizedException {
LogUtils.debug(log, "process: putting to: ", folder.getName());
try {
Long l = putHelper.getContentLength(request);
String ct = putHelper.findContentTypes(request, newName);
LogUtils.debug(log, "PutHandler: creating resource of type: ", ct);
InputStream inputStream = request.getInputStream();
if (MapUtils.isNotEmpty(request.getFiles())) {
inputStream = request.getFiles().entrySet().stream().findFirst().get().getValue().getInputStream();
}
Resource newlyCreated = folder.createNew(newName, inputStream, l, ct);
if (newlyCreated != null) {
if (newName != null && !newName.equals(newlyCreated.getName())) {
log.warn("getName on the created resource does not match the name requested by the client! requested: " + newName + " - created: " + newlyCreated.getName());
}
manager.getEventManager().fireEvent(new PutEvent(newlyCreated));
manager.getResponseHandler().respondCreated(newlyCreated, response, request);
} else {
throw new RuntimeException("createNew method on: " + folder.getClass() + " returned a null resource. Must return a reference to the newly created or modified resource");
}
} catch (IOException ex) {
throw new RuntimeException("IOException reading input stream. Probably interrupted upload", ex);
}
}
private CollectionResource findOrCreateFolders(HttpManager manager, String host, Path path, Request request) throws NotAuthorizedException, ConflictException, BadRequestException {
if (path == null) {
return null;
}
Resource thisResource = manager.getResourceFactory().getResource(host, path.toString());
if (thisResource != null) {
// Defensive programming test for a common problem where resource factories
// return the wrong resource for a given path
if (thisResource.getName() != null && !thisResource.getName().equals(path.getName())) {
log.warn("Your resource factory returned a resource with a different name to that requested!!! Requested: " + path.getName() + " returned: " + thisResource.getName() + " - resource factory: " + manager.getResourceFactory().getClass());
}
if (thisResource instanceof CollectionResource) {
return (CollectionResource) thisResource;
} else {
log.warn("parent is not a collection: " + path);
return null;
}
}
CollectionResource parent = findOrCreateFolders(manager, host, path.getParent(), request);
if (parent == null) {
log.warn("couldnt find parent: " + path);
return null;
}
Resource r = parent.child(path.getName());
if (r == null) {
log.info("Could not find child: " + path.getName() + " in parent: " + parent.getName() + " - " + parent.getClass());
if (parent instanceof MakeCollectionableResource) {
MakeCollectionableResource mkcol = (MakeCollectionableResource) parent;
if (!handlerHelper.checkAuthorisation(manager, mkcol, request)) {
throw new NotAuthorizedException(mkcol);
}
log.info("autocreating new folder: " + path.getName());
CollectionResource newCol = mkcol.createCollection(path.getName());
manager.getEventManager().fireEvent(new NewFolderEvent(newCol));
return newCol;
} else {
log.info("parent folder isnt a MakeCollectionableResource: " + parent.getName() + " - " + parent.getClass());
return null;
}
} else if (r instanceof CollectionResource) {
return (CollectionResource) r;
} else {
log.info("parent in URL is not a collection: " + r.getName());
return null;
}
}
/**
* "If an existing resource is modified, either the 200 (OK) or 204 (No
* Content) response codes SHOULD be sent to indicate successful completion
* of the request."
*
* @param request
* @param response
* @param replacee
*/
private void processReplace(HttpManager manager, Request request, Response response, ReplaceableResource replacee) throws BadRequestException, NotAuthorizedException, ConflictException, NotFoundException {
if (!handlerHelper.checkAuthorisation(manager, replacee, request)) {
responseHandler.respondUnauthorised(replacee, response, request);
return;
}
try {
Range range = putHelper.parseContentRange(replacee, request);
if (range != null) {
log.debug("partial put: " + range);
if (replacee instanceof PartialllyUpdateableResource) {
log.debug("doing partial put on a PartialllyUpdateableResource");
PartialllyUpdateableResource partialllyUpdateableResource = (PartialllyUpdateableResource) replacee;
partialllyUpdateableResource.replacePartialContent(range, request.getInputStream());
} else if (replacee instanceof GetableResource) {
log.debug("doing partial put on a GetableResource");
File tempFile = File.createTempFile("milton-partial", null);
RandomAccessFile randomAccessFile = null;
// The new length of the resource
long length;
try {
randomAccessFile = new RandomAccessFile(tempFile, "rw");
RandomFileOutputStream tempOut = new RandomFileOutputStream(tempFile);
GetableResource gr = (GetableResource) replacee;
// Update the content with the supplied partial content, and get the result as an inputstream
gr.sendContent(tempOut, null, null, null);
// Calculate new length, if the partial put is extending it
length = randomAccessFile.length();
if (range.getFinish() + 1 > length) {
length = range.getFinish() + 1;
}
randomAccessFile.setLength(length);
randomAccessFile.seek(range.getStart());
int numBytesRead;
byte[] copyBuffer = new byte[1024];
InputStream newContent = request.getInputStream();
while ((numBytesRead = newContent.read(copyBuffer)) != -1) {
randomAccessFile.write(copyBuffer, 0, numBytesRead);
}
} finally {
FileUtils.close(randomAccessFile);
}
InputStream updatedContent = new FileInputStream(tempFile);
BufferedInputStream bufin = new BufferedInputStream(updatedContent);
// Now, finally, we can just do a normal update
replacee.replaceContent(bufin, length);
} else {
throw new BadRequestException(replacee, "Cant apply partial update. Resource does not support PartialllyUpdateableResource or GetableResource");
}
} else {
// Not a partial update, but resource implements Replaceable, so give it the new data
Long l = request.getContentLengthHeader();
replacee.replaceContent(request.getInputStream(), l);
}
} catch (IOException ex) {
log.warn("IOException reading input stream. Probably interrupted upload: " + ex.getMessage());
return;
}
// Respond with a 204
responseHandler.respondNoContent(replacee, response, request);
log.debug("process: finished");
}
public void processExistingResource(HttpManager manager, Request request, Response response, Resource resource) throws NotAuthorizedException, BadRequestException, ConflictException, NotFoundException {
String host = request.getHostHeader();
String urlToCreateOrUpdate = HttpManager.decodeUrl(request.getAbsolutePath());
log.debug("process request: host: " + host + " url: " + urlToCreateOrUpdate);
Path path = Path.path(urlToCreateOrUpdate);
urlToCreateOrUpdate = path.toString();
Resource existingResource = manager.getResourceFactory().getResource(host, urlToCreateOrUpdate);
ReplaceableResource replacee;
if (existingResource != null) {
//Make sure the parent collection is not locked by someone else
if (handlerHelper.isLockedOut(request, existingResource)) {
log.warn("resource is locked, but not by the current user");
response.setStatus(Status.SC_LOCKED); //423
return;
}
}
if (existingResource instanceof ReplaceableResource) {
replacee = (ReplaceableResource) existingResource;
} else {
replacee = null;
}
if (replacee != null) {
processReplace(manager, request, response, (ReplaceableResource) existingResource);
} else {
// either no existing resource, or its not replaceable. check for folder
String urlFolder = path.getParent().toString();
String nameToCreate = path.getName();
CollectionResource folderResource = findOrCreateFolders(manager, host, path.getParent(), request);
if (folderResource != null) {
if (log.isDebugEnabled()) {
log.debug("found folder: " + urlFolder + " - " + folderResource.getClass());
}
if (folderResource instanceof PutableResource) {
//Make sure the parent collection is not locked by someone else
if (handlerHelper.isLockedOut(request, folderResource)) {
response.setStatus(Status.SC_LOCKED); //423
return;
}
PutableResource putableResource = (PutableResource) folderResource;
processCreate(manager, request, response, putableResource, nameToCreate);
} else {
responseHandler.respondMethodNotImplemented(folderResource, response, request);
}
} else {
responseHandler.respondNotFound(response, request);
}
}
}
private void respondLocked(Request request, Response response, Resource existingResource) {
if (responseHandler instanceof WebDavResponseHandler) {
WebDavResponseHandler rh = (WebDavResponseHandler) responseHandler;
rh.respondLocked(request, response, existingResource);
} else {
response.setStatus(Status.SC_LOCKED); //423
}
}
private void respondInsufficientStorage(Request request, Response response, StorageErrorReason storageErrorReason) {
if (responseHandler instanceof WebDavResponseHandler) {
WebDavResponseHandler rh = (WebDavResponseHandler) responseHandler;
rh.respondInsufficientStorage(request, response, storageErrorReason);
} else {
response.setStatus(Status.SC_INSUFFICIENT_STORAGE);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy