All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.webdav.server.AbstractWebdavServlet Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show 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 org.apache.jackrabbit.webdav.server;

import org.apache.jackrabbit.webdav.ContentCodingAwareRequest;
import org.apache.jackrabbit.webdav.DavCompliance;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavMethods;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavServletRequest;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSessionProvider;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.WebdavRequest;
import org.apache.jackrabbit.webdav.WebdavRequestImpl;
import org.apache.jackrabbit.webdav.WebdavResponse;
import org.apache.jackrabbit.webdav.WebdavResponseImpl;
import org.apache.jackrabbit.webdav.bind.RebindInfo;
import org.apache.jackrabbit.webdav.bind.UnbindInfo;
import org.apache.jackrabbit.webdav.bind.BindableResource;
import org.apache.jackrabbit.webdav.bind.BindInfo;
import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
import org.apache.jackrabbit.webdav.io.InputContext;
import org.apache.jackrabbit.webdav.io.InputContextImpl;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.io.OutputContextImpl;
import org.apache.jackrabbit.webdav.lock.ActiveLock;
import org.apache.jackrabbit.webdav.lock.LockDiscovery;
import org.apache.jackrabbit.webdav.lock.LockInfo;
import org.apache.jackrabbit.webdav.observation.EventDiscovery;
import org.apache.jackrabbit.webdav.observation.ObservationResource;
import org.apache.jackrabbit.webdav.observation.Subscription;
import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
import org.apache.jackrabbit.webdav.ordering.OrderPatch;
import org.apache.jackrabbit.webdav.ordering.OrderingResource;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
import org.apache.jackrabbit.webdav.property.PropEntry;
import org.apache.jackrabbit.webdav.search.SearchConstants;
import org.apache.jackrabbit.webdav.search.SearchInfo;
import org.apache.jackrabbit.webdav.search.SearchResource;
import org.apache.jackrabbit.webdav.security.AclProperty;
import org.apache.jackrabbit.webdav.security.AclResource;
import org.apache.jackrabbit.webdav.transaction.TransactionInfo;
import org.apache.jackrabbit.webdav.transaction.TransactionResource;
import org.apache.jackrabbit.webdav.util.CSRFUtil;
import org.apache.jackrabbit.webdav.util.HttpDateTimeFormatter;
import org.apache.jackrabbit.webdav.version.ActivityResource;
import org.apache.jackrabbit.webdav.version.DeltaVConstants;
import org.apache.jackrabbit.webdav.version.DeltaVResource;
import org.apache.jackrabbit.webdav.version.LabelInfo;
import org.apache.jackrabbit.webdav.version.MergeInfo;
import org.apache.jackrabbit.webdav.version.OptionsInfo;
import org.apache.jackrabbit.webdav.version.OptionsResponse;
import org.apache.jackrabbit.webdav.version.UpdateInfo;
import org.apache.jackrabbit.webdav.version.VersionControlledResource;
import org.apache.jackrabbit.webdav.version.VersionResource;
import org.apache.jackrabbit.webdav.version.VersionableResource;
import org.apache.jackrabbit.webdav.version.report.Report;
import org.apache.jackrabbit.webdav.version.report.ReportInfo;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;

/**
 * AbstractWebdavServlet
 * 

*/ abstract public class AbstractWebdavServlet extends HttpServlet implements DavConstants { // todo respect Position header /** * default logger */ private static Logger log = LoggerFactory.getLogger(AbstractWebdavServlet.class); /** the 'missing-auth-mapping' init parameter */ public final static String INIT_PARAM_MISSING_AUTH_MAPPING = "missing-auth-mapping"; /** * Name of the optional init parameter that defines the value of the * 'WWW-Authenticate' header. *

* If the parameter is omitted the default value * {@link #DEFAULT_AUTHENTICATE_HEADER "Basic Realm=Jackrabbit Webdav Server"} * is used. * * @see #getAuthenticateHeaderValue() */ public static final String INIT_PARAM_AUTHENTICATE_HEADER = "authenticate-header"; /** * Default value for the 'WWW-Authenticate' header, that is set, if request * results in a {@link DavServletResponse#SC_UNAUTHORIZED 401 (Unauthorized)} * error. * * @see #getAuthenticateHeaderValue() */ public static final String DEFAULT_AUTHENTICATE_HEADER = "Basic realm=\"Jackrabbit Webdav Server\""; /** * Name of the parameter that specifies the configuration of the CSRF protection. * May contain a comma-separated list of allowed referrer hosts. * If the parameter is omitted or left empty the behaviour is to only allow requests which have an empty referrer * or a referrer host equal to the server host. * If the parameter is set to 'disabled' no referrer checks will be performed at all. */ public static final String INIT_PARAM_CSRF_PROTECTION = "csrf-protection"; /** * Name of the 'createAbsoluteURI' init parameter that defines whether hrefs * should be created with a absolute URI or as absolute Path (ContextPath). * The value should be 'true' or 'false'. The default value if not set is true. */ public final static String INIT_PARAM_CREATE_ABSOLUTE_URI = "createAbsoluteURI"; /** * Header value as specified in the {@link #INIT_PARAM_AUTHENTICATE_HEADER} parameter. */ private String authenticate_header; /** * CSRF protection utility */ private CSRFUtil csrfUtil; /** * Create per default absolute URI hrefs */ private boolean createAbsoluteURI = true; @Override public void init() throws ServletException { super.init(); // authenticate header authenticate_header = getInitParameter(INIT_PARAM_AUTHENTICATE_HEADER); if (authenticate_header == null) { authenticate_header = DEFAULT_AUTHENTICATE_HEADER; } log.info(INIT_PARAM_AUTHENTICATE_HEADER + " = " + authenticate_header); // read csrf protection params String csrfParam = getInitParameter(INIT_PARAM_CSRF_PROTECTION); csrfUtil = new CSRFUtil(csrfParam); log.info(INIT_PARAM_CSRF_PROTECTION + " = " + csrfParam); //create absolute URI hrefs.. String param = getInitParameter(INIT_PARAM_CREATE_ABSOLUTE_URI); if (param != null) { createAbsoluteURI = Boolean.parseBoolean(param); } log.info(INIT_PARAM_CREATE_ABSOLUTE_URI + " = " + createAbsoluteURI); } /** * Checks if the precondition for this request and resource is valid. * * @param request * @param resource * @return */ abstract protected boolean isPreconditionValid(WebdavRequest request, DavResource resource); /** * Returns the DavSessionProvider. * * @return the session provider */ abstract public DavSessionProvider getDavSessionProvider(); /** * Returns the DavSessionProvider. * * @param davSessionProvider */ abstract public void setDavSessionProvider(DavSessionProvider davSessionProvider); /** * Returns the DavLocatorFactory. * * @return the locator factory */ abstract public DavLocatorFactory getLocatorFactory(); /** * Sets the DavLocatorFactory. * * @param locatorFactory */ abstract public void setLocatorFactory(DavLocatorFactory locatorFactory); /** * Returns the DavResourceFactory. * * @return the resource factory */ abstract public DavResourceFactory getResourceFactory(); /** * Sets the DavResourceFactory. * * @param resourceFactory */ abstract public void setResourceFactory(DavResourceFactory resourceFactory); /** * Returns the value of the 'WWW-Authenticate' header, that is returned in * case of 401 error: the value is retrireved from the corresponding init * param or defaults to {@link #DEFAULT_AUTHENTICATE_HEADER}. * * @return corresponding init parameter or {@link #DEFAULT_AUTHENTICATE_HEADER}. * @see #INIT_PARAM_AUTHENTICATE_HEADER */ public String getAuthenticateHeaderValue() { return authenticate_header; } /** * Returns if a absolute URI should be created for hrefs. * * @return absolute URI hrefs */ protected boolean isCreateAbsoluteURI() { return createAbsoluteURI; } /** * Service the given request. * * @param request * @param response * @throws ServletException * @throws IOException */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { WebdavRequest webdavRequest = new WebdavRequestImpl(request, getLocatorFactory(), isCreateAbsoluteURI()); // DeltaV requires 'Cache-Control' header for all methods except 'VERSION-CONTROL' and 'REPORT'. int methodCode = DavMethods.getMethodCode(request.getMethod()); boolean noCache = DavMethods.isDeltaVMethod(webdavRequest) && !(DavMethods.DAV_VERSION_CONTROL == methodCode || DavMethods.DAV_REPORT == methodCode); WebdavResponse webdavResponse = new WebdavResponseImpl(response, noCache); try { WebdavRequestContextHolder.setContext(new WebdavRequestContextImpl(webdavRequest)); // make sure there is a authenticated user if (!getDavSessionProvider().attachSession(webdavRequest)) { return; } // perform referrer host checks if CSRF protection is enabled if (!csrfUtil.isValidRequest(webdavRequest)) { webdavResponse.sendError(HttpServletResponse.SC_FORBIDDEN); return; } // JCR-4165: reject any content-coding in request until we can // support it (see JCR-4166) if (!(webdavRequest instanceof ContentCodingAwareRequest)) { List ces = getContentCodings(request); if (!ces.isEmpty()) { webdavResponse.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); webdavResponse.setHeader("Accept-Encoding", "identity"); webdavResponse.setContentType("text/plain; charset=UTF-8"); webdavResponse.getWriter().println("Content-Encodings not supported, but received: " + ces); webdavResponse.getWriter().flush(); } } // check matching if=header for lock-token relevant operations DavResource resource = getResourceFactory().createResource(webdavRequest.getRequestLocator(), webdavRequest, webdavResponse); if (!isPreconditionValid(webdavRequest, resource)) { webdavResponse.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return; } if (!execute(webdavRequest, webdavResponse, methodCode, resource)) { super.service(request, response); } } catch (DavException e) { handleDavException(webdavRequest, webdavResponse, e); } catch (IOException ex) { Throwable cause = ex.getCause(); if (cause instanceof DavException) { handleDavException(webdavRequest, webdavResponse, (DavException) cause); } else { throw ex; } } finally { WebdavRequestContextHolder.clearContext(); getDavSessionProvider().releaseSession(webdavRequest); } } private void handleDavException(WebdavRequest webdavRequest, WebdavResponse webdavResponse, DavException ex) throws IOException { if (ex.getErrorCode() == HttpServletResponse.SC_UNAUTHORIZED) { sendUnauthorized(webdavRequest, webdavResponse, ex); } else { Element condition = ex.getErrorCondition(); if (DomUtil.matches(condition, ContentCodingAwareRequest.PRECONDITION_SUPPORTED)) { if (webdavRequest instanceof ContentCodingAwareRequest) { webdavResponse.setHeader("Accept-Encoding", ((ContentCodingAwareRequest) webdavRequest).getAcceptableCodings()); } } webdavResponse.sendError(ex); } } /** * If request payload was uncompressed, hint about acceptable content codings (RFC 7694) */ private void addHintAboutPotentialRequestEncodings(WebdavRequest webdavRequest, WebdavResponse webdavResponse) { if (webdavRequest instanceof ContentCodingAwareRequest) { ContentCodingAwareRequest ccr = (ContentCodingAwareRequest)webdavRequest; List ces = ccr.getRequestContentCodings(); if (ces.isEmpty()) { webdavResponse.setHeader("Accept-Encoding", ccr.getAcceptableCodings()); } } } /** * Sets the "WWW-Authenticate" header and writes the appropriate error * to the given webdav response. * * @param request The webdav request. * @param response The webdav response. * @param error The DavException that leads to the unauthorized response. * @throws IOException */ protected void sendUnauthorized(WebdavRequest request, WebdavResponse response, DavException error) throws IOException { response.setHeader("WWW-Authenticate", getAuthenticateHeaderValue()); if (error == null || error.getErrorCode() != HttpServletResponse.SC_UNAUTHORIZED) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } else { response.sendError(error.getErrorCode(), error.getStatusPhrase()); } } /** * Executes the respective method in the given webdav context * * @param request * @param response * @param method * @param resource * @throws ServletException * @throws IOException * @throws DavException */ protected boolean execute(WebdavRequest request, WebdavResponse response, int method, DavResource resource) throws ServletException, IOException, DavException { switch (method) { case DavMethods.DAV_GET: doGet(request, response, resource); break; case DavMethods.DAV_HEAD: doHead(request, response, resource); break; case DavMethods.DAV_PROPFIND: doPropFind(request, response, resource); break; case DavMethods.DAV_PROPPATCH: doPropPatch(request, response, resource); break; case DavMethods.DAV_POST: doPost(request, response, resource); break; case DavMethods.DAV_PUT: doPut(request, response, resource); break; case DavMethods.DAV_DELETE: doDelete(request, response, resource); break; case DavMethods.DAV_COPY: doCopy(request, response, resource); break; case DavMethods.DAV_MOVE: doMove(request, response, resource); break; case DavMethods.DAV_MKCOL: doMkCol(request, response, resource); break; case DavMethods.DAV_OPTIONS: doOptions(request, response, resource); break; case DavMethods.DAV_LOCK: doLock(request, response, resource); break; case DavMethods.DAV_UNLOCK: doUnlock(request, response, resource); break; case DavMethods.DAV_ORDERPATCH: doOrderPatch(request, response, resource); break; case DavMethods.DAV_SUBSCRIBE: doSubscribe(request, response, resource); break; case DavMethods.DAV_UNSUBSCRIBE: doUnsubscribe(request, response, resource); break; case DavMethods.DAV_POLL: doPoll(request, response, resource); break; case DavMethods.DAV_SEARCH: doSearch(request, response, resource); break; case DavMethods.DAV_VERSION_CONTROL: doVersionControl(request, response, resource); break; case DavMethods.DAV_LABEL: doLabel(request, response, resource); break; case DavMethods.DAV_REPORT: doReport(request, response, resource); break; case DavMethods.DAV_CHECKIN: doCheckin(request, response, resource); break; case DavMethods.DAV_CHECKOUT: doCheckout(request, response, resource); break; case DavMethods.DAV_UNCHECKOUT: doUncheckout(request, response, resource); break; case DavMethods.DAV_MERGE: doMerge(request, response, resource); break; case DavMethods.DAV_UPDATE: doUpdate(request, response, resource); break; case DavMethods.DAV_MKWORKSPACE: doMkWorkspace(request, response, resource); break; case DavMethods.DAV_MKACTIVITY: doMkActivity(request, response, resource); break; case DavMethods.DAV_BASELINE_CONTROL: doBaselineControl(request, response, resource); break; case DavMethods.DAV_ACL: doAcl(request, response, resource); break; case DavMethods.DAV_REBIND: doRebind(request, response, resource); break; case DavMethods.DAV_UNBIND: doUnbind(request, response, resource); break; case DavMethods.DAV_BIND: doBind(request, response, resource); break; default: // any other method return false; } return true; } /** * The OPTION method * * @param request * @param response * @param resource */ protected void doOptions(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { response.addHeader(DavConstants.HEADER_DAV, resource.getComplianceClass()); response.addHeader("Allow", resource.getSupportedMethods()); response.addHeader("MS-Author-Via", DavConstants.HEADER_DAV); if (resource instanceof SearchResource) { String[] langs = ((SearchResource) resource).getQueryGrammerSet().getQueryLanguages(); for (String lang : langs) { response.addHeader(SearchConstants.HEADER_DASL, "<" + lang + ">"); } } // with DeltaV the OPTIONS request may contain a Xml body. OptionsResponse oR = null; OptionsInfo oInfo = request.getOptionsInfo(); if (oInfo != null && resource instanceof DeltaVResource) { oR = ((DeltaVResource) resource).getOptionResponse(oInfo); } if (oR == null) { response.setStatus(DavServletResponse.SC_OK); } else { response.sendXmlResponse(oR, DavServletResponse.SC_OK); } } /** * The HEAD method * * @param request * @param response * @param resource * @throws IOException */ protected void doHead(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException { spoolResource(request, response, resource, false); } /** * The GET method * * @param request * @param response * @param resource * @throws IOException */ protected void doGet(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { spoolResource(request, response, resource, true); } /** * @param request * @param response * @param resource * @param sendContent * @throws IOException */ private void spoolResource(WebdavRequest request, WebdavResponse response, DavResource resource, boolean sendContent) throws IOException { if (!resource.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } long modSince = UNDEFINED_TIME; try { // will throw if multiple field lines present String value = getSingletonField(request, "If-Modified-Since"); if (value != null) { modSince = HttpDateTimeFormatter.parse(value); } } catch (IllegalArgumentException | DateTimeParseException ex) { log.debug("illegal value for if-modified-since ignored: " + ex.getMessage()); } if (modSince > UNDEFINED_TIME) { long modTime = resource.getModificationTime(); // test if resource has been modified. note that formatted modification // time lost the milli-second precision if (modTime != UNDEFINED_TIME && (modTime / 1000 * 1000) <= modSince) { // resource has not been modified since the time indicated in the // 'If-Modified-Since' header. DavProperty etagProp = resource.getProperty(DavPropertyName.GETETAG); if (etagProp != null) { // 304 response MUST contain Etag when available response.setHeader("etag", etagProp.getValue().toString()); } response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } } // spool resource properties and eventually resource content. OutputStream out = (sendContent) ? response.getOutputStream() : null; resource.spool(getOutputContext(response, out)); response.flushBuffer(); } /** * The PROPFIND method * * @param request * @param response * @param resource * @throws IOException */ protected void doPropFind(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!resource.exists()) { response.sendError(DavServletResponse.SC_NOT_FOUND); return; } int depth = request.getDepth(DEPTH_INFINITY); DavPropertyNameSet requestProperties = request.getPropFindProperties(); int propfindType = request.getPropFindType(); MultiStatus mstatus = new MultiStatus(); mstatus.addResourceProperties(resource, requestProperties, propfindType, depth); addHintAboutPotentialRequestEncodings(request, response); response.sendMultiStatus(mstatus, acceptsGzipEncoding(request) ? Collections.singletonList("gzip") : Collections.emptyList()); } /** * The PROPPATCH method * * @param request * @param response * @param resource * @throws IOException */ protected void doPropPatch(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { List changeList = request.getPropPatchChangeList(); if (changeList.isEmpty()) { response.sendError(DavServletResponse.SC_BAD_REQUEST); return; } MultiStatus ms = new MultiStatus(); MultiStatusResponse msr = resource.alterProperties(changeList); ms.addResponse(msr); addHintAboutPotentialRequestEncodings(request, response); response.sendMultiStatus(ms); } /** * The POST method. Delegate to PUT * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doPost(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } /** * The PUT method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doPut(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (request.getHeader("Content-Range") != null) { response.sendError(DavServletResponse.SC_BAD_REQUEST, "Content-Range in PUT request not supported"); return; } DavResource parentResource = resource.getCollection(); if (parentResource == null || !parentResource.exists()) { // parent does not exist response.sendError(DavServletResponse.SC_CONFLICT); return; } int status; // test if resource already exists if (resource.exists()) { status = DavServletResponse.SC_NO_CONTENT; } else { status = DavServletResponse.SC_CREATED; } parentResource.addMember(resource, getInputContext(request, request.getInputStream())); response.setStatus(status); } /** * The MKCOL method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doMkCol(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { DavResource parentResource = resource.getCollection(); if (parentResource == null || !parentResource.exists() || !parentResource.isCollection()) { // parent does not exist or is not a collection response.sendError(DavServletResponse.SC_CONFLICT); return; } // shortcut: mkcol is only allowed on deleted/non-existing resources if (resource.exists()) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } if (request.getContentLength() > 0 || request.getHeader("Transfer-Encoding") != null) { parentResource.addMember(resource, getInputContext(request, request.getInputStream())); } else { parentResource.addMember(resource, getInputContext(request, null)); } response.setStatus(DavServletResponse.SC_CREATED); } /** * The DELETE method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doDelete(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { DavResource parent = resource.getCollection(); if (parent != null) { parent.removeMember(resource); response.setStatus(DavServletResponse.SC_NO_CONTENT); } else { response.sendError(DavServletResponse.SC_FORBIDDEN, "Cannot remove the root resource."); } } /** * The COPY method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doCopy(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { // only depth 0 and infinity is allowed int depth = request.getDepth(DEPTH_INFINITY); if (!(depth == DEPTH_0 || depth == DEPTH_INFINITY)) { response.sendError(DavServletResponse.SC_BAD_REQUEST); return; } DavResource destResource = getResourceFactory().createResource(request.getDestinationLocator(), request, response); int status = validateDestination(destResource, request, true); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; } resource.copy(destResource, depth == DEPTH_0); response.setStatus(status); } /** * The MOVE method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doMove(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { DavResource destResource = getResourceFactory().createResource(request.getDestinationLocator(), request, response); int status = validateDestination(destResource, request, true); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; } resource.move(destResource); response.setStatus(status); } /** * The BIND method * * @param request * @param response * @param resource the collection resource to which a new member will be added * @throws IOException * @throws DavException */ protected void doBind(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!resource.exists()) { response.sendError(DavServletResponse.SC_NOT_FOUND); } BindInfo bindInfo = request.getBindInfo(); DavResource oldBinding = getResourceFactory().createResource(request.getHrefLocator(bindInfo.getHref()), request, response); if (!(oldBinding instanceof BindableResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } DavResource newBinding = getResourceFactory().createResource(request.getMemberLocator(bindInfo.getSegment()), request, response); int status = validateDestination(newBinding, request, false); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; } ((BindableResource) oldBinding).bind(resource, newBinding); response.setStatus(status); } /** * The REBIND method * * @param request * @param response * @param resource the collection resource to which a new member will be added * @throws IOException * @throws DavException */ protected void doRebind(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!resource.exists()) { response.sendError(DavServletResponse.SC_NOT_FOUND); } RebindInfo rebindInfo = request.getRebindInfo(); DavResource oldBinding = getResourceFactory().createResource(request.getHrefLocator(rebindInfo.getHref()), request, response); if (!(oldBinding instanceof BindableResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } DavResource newBinding = getResourceFactory().createResource(request.getMemberLocator(rebindInfo.getSegment()), request, response); int status = validateDestination(newBinding, request, false); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; } ((BindableResource) oldBinding).rebind(resource, newBinding); response.setStatus(status); } /** * The UNBIND method * * @param request * @param response * @param resource the collection resource from which a member will be removed * @throws IOException * @throws DavException */ protected void doUnbind(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { UnbindInfo unbindInfo = request.getUnbindInfo(); DavResource srcResource = getResourceFactory().createResource(request.getMemberLocator(unbindInfo.getSegment()), request, response); resource.removeMember(srcResource); } /** * Validate the given destination resource and return the proper status * code: Any return value greater/equal than {@link DavServletResponse#SC_NO_CONTENT} * indicates an error. * * @param destResource destination resource to be validated. * @param request * @param checkHeader flag indicating if the destination header must be present. * @return status code indicating whether the destination is valid. */ protected int validateDestination(DavResource destResource, WebdavRequest request, boolean checkHeader) throws DavException { if (checkHeader) { String destHeader = request.getHeader(HEADER_DESTINATION); if (destHeader == null || "".equals(destHeader)) { return DavServletResponse.SC_BAD_REQUEST; } } if (destResource.getLocator().equals(request.getRequestLocator())) { return DavServletResponse.SC_FORBIDDEN; } int status; if (destResource.exists()) { if (request.isOverwrite()) { // matching if-header required for existing resources if (!request.matchesIfHeader(destResource)) { return DavServletResponse.SC_PRECONDITION_FAILED; } else { // overwrite existing resource DavResource col; try { col = destResource.getCollection(); } catch (IllegalArgumentException ex) { return DavServletResponse.SC_BAD_GATEWAY; } col.removeMember(destResource); status = DavServletResponse.SC_NO_CONTENT; } } else { // cannot copy/move to an existing item, if overwrite is not forced return DavServletResponse.SC_PRECONDITION_FAILED; } } else { // destination does not exist >> copy/move can be performed status = DavServletResponse.SC_CREATED; } return status; } /** * The LOCK method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doLock(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { LockInfo lockInfo = request.getLockInfo(); if (lockInfo.isRefreshLock()) { // refresh any matching existing locks ActiveLock[] activeLocks = resource.getLocks(); List lList = new ArrayList(); for (ActiveLock activeLock : activeLocks) { // adjust lockinfo with type/scope retrieved from the lock. lockInfo.setType(activeLock.getType()); lockInfo.setScope(activeLock.getScope()); DavProperty etagProp = resource.getProperty(DavPropertyName.GETETAG); String etag = etagProp != null ? String.valueOf(etagProp.getValue()) : ""; if (request.matchesIfHeader(resource.getHref(), activeLock.getToken(), etag)) { lList.add(resource.refreshLock(lockInfo, activeLock.getToken())); } } if (lList.isEmpty()) { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); } ActiveLock[] refreshedLocks = lList.toArray(new ActiveLock[lList.size()]); response.sendRefreshLockResponse(refreshedLocks); } else { int status = HttpServletResponse.SC_OK; if (!resource.exists()) { // lock-empty requires status code 201 (Created) status = HttpServletResponse.SC_CREATED; } // create a new lock ActiveLock lock = resource.lock(lockInfo); CodedUrlHeader header = new CodedUrlHeader( DavConstants.HEADER_LOCK_TOKEN, lock.getToken()); response.setHeader(header.getHeaderName(), header.getHeaderValue()); DavPropertySet propSet = new DavPropertySet(); propSet.add(new LockDiscovery(lock)); response.sendXmlResponse(propSet, status); } } /** * The UNLOCK method * * @param request * @param response * @param resource * @throws DavException */ protected void doUnlock(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException { // get lock token from header String lockToken = request.getLockToken(); TransactionInfo tInfo = request.getTransactionInfo(); if (tInfo != null) { ((TransactionResource) resource).unlock(lockToken, tInfo); } else { resource.unlock(lockToken); } response.setStatus(DavServletResponse.SC_NO_CONTENT); } /** * The ORDERPATCH method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doOrderPatch(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!(resource instanceof OrderingResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } OrderPatch op = request.getOrderPatch(); if (op == null) { response.sendError(DavServletResponse.SC_BAD_REQUEST); return; } // perform reordering of internal members ((OrderingResource) resource).orderMembers(op); response.setStatus(DavServletResponse.SC_OK); } /** * The SUBSCRIBE method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doSubscribe(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!(resource instanceof ObservationResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } SubscriptionInfo info = request.getSubscriptionInfo(); if (info == null) { response.sendError(DavServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); return; } Subscription subs = ((ObservationResource) resource).subscribe(info, request.getSubscriptionId()); response.sendSubscriptionResponse(subs); } /** * The UNSUBSCRIBE method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doUnsubscribe(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!(resource instanceof ObservationResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } ((ObservationResource) resource).unsubscribe(request.getSubscriptionId()); response.setStatus(DavServletResponse.SC_NO_CONTENT); } /** * The POLL method * * @param request * @param response * @param resource * @throws IOException * @throws DavException */ protected void doPoll(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { if (!(resource instanceof ObservationResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } EventDiscovery ed = ((ObservationResource) resource).poll( request.getSubscriptionId(), request.getPollTimeout()); response.sendPollResponse(ed); } /** * The VERSION-CONTROL method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doVersionControl(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionableResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } ((VersionableResource) resource).addVersionControl(); } /** * The LABEL method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doLabel(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { LabelInfo labelInfo = request.getLabelInfo(); if (resource instanceof VersionResource) { ((VersionResource) resource).label(labelInfo); } else if (resource instanceof VersionControlledResource) { ((VersionControlledResource) resource).label(labelInfo); } else { // any other resource type that does not support a LABEL request response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); } } /** * The REPORT method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doReport(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { ReportInfo info = request.getReportInfo(); Report report; if (resource instanceof DeltaVResource) { report = ((DeltaVResource) resource).getReport(info); } else if (resource instanceof AclResource) { report = ((AclResource) resource).getReport(info); } else { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } int statusCode = (report.isMultiStatusReport()) ? DavServletResponse.SC_MULTI_STATUS : DavServletResponse.SC_OK; addHintAboutPotentialRequestEncodings(request, response); response.sendXmlResponse(report, statusCode, acceptsGzipEncoding(request) ? Collections.singletonList("gzip") : Collections.emptyList()); } /** * The CHECKIN method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doCheckin(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionControlledResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } String versionHref = ((VersionControlledResource) resource).checkin(); response.setHeader(DeltaVConstants.HEADER_LOCATION, versionHref); response.setStatus(DavServletResponse.SC_CREATED); } /** * The CHECKOUT method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doCheckout(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionControlledResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } ((VersionControlledResource) resource).checkout(); } /** * The UNCHECKOUT method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doUncheckout(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionControlledResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } ((VersionControlledResource) resource).uncheckout(); } /** * The MERGE method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doMerge(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionControlledResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } MergeInfo info = request.getMergeInfo(); MultiStatus ms = ((VersionControlledResource) resource).merge(info); response.sendMultiStatus(ms); } /** * The UPDATE method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doUpdate(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof VersionControlledResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } UpdateInfo info = request.getUpdateInfo(); MultiStatus ms = ((VersionControlledResource) resource).update(info); response.sendMultiStatus(ms); } /** * The MKWORKSPACE method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doMkWorkspace(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (resource.exists()) { AbstractWebdavServlet.log.warn("Cannot create a new workspace. Resource already exists."); response.sendError(DavServletResponse.SC_FORBIDDEN); return; } DavResource parentResource = resource.getCollection(); if (parentResource == null || !parentResource.exists() || !parentResource.isCollection()) { // parent does not exist or is not a collection response.sendError(DavServletResponse.SC_CONFLICT); return; } if (!(parentResource instanceof DeltaVResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } ((DeltaVResource) parentResource).addWorkspace(resource); response.setStatus(DavServletResponse.SC_CREATED); } /** * The MKACTIVITY method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doMkActivity(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (resource.exists()) { AbstractWebdavServlet.log.warn("Unable to create activity: A resource already exists at the request-URL " + request.getRequestURL()); response.sendError(DavServletResponse.SC_FORBIDDEN); return; } DavResource parentResource = resource.getCollection(); if (parentResource == null || !parentResource.exists() || !parentResource.isCollection()) { // parent does not exist or is not a collection response.sendError(DavServletResponse.SC_CONFLICT); return; } // TODO: improve. see http://issues.apache.org/jira/browse/JCR-394 if (!parentResource.getComplianceClass().contains(DavCompliance.ACTIVITY)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } if (!(resource instanceof ActivityResource)) { AbstractWebdavServlet.log.error("Unable to create activity: ActivityResource expected"); response.sendError(DavServletResponse.SC_INTERNAL_SERVER_ERROR); return; } // try to add the new activity resource parentResource.addMember(resource, getInputContext(request, request.getInputStream())); // Note: mandatory cache control header has already been set upon response creation. response.setStatus(DavServletResponse.SC_CREATED); } /** * The BASELINECONTROL method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doBaselineControl(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!resource.exists()) { AbstractWebdavServlet.log.warn("Unable to add baseline control. Resource does not exist " + resource.getHref()); response.sendError(DavServletResponse.SC_NOT_FOUND); return; } // TODO: improve. see http://issues.apache.org/jira/browse/JCR-394 if (!(resource instanceof VersionControlledResource) || !resource.isCollection()) { AbstractWebdavServlet.log.warn("BaselineControl is not supported by resource " + resource.getHref()); response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } // TODO : missing method on VersionControlledResource throw new DavException(DavServletResponse.SC_NOT_IMPLEMENTED); /* ((VersionControlledResource) resource).addBaselineControl(request.getRequestDocument()); // Note: mandatory cache control header has already been set upon response creation. response.setStatus(DavServletResponse.SC_OK); */ } /** * The SEARCH method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doSearch(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof SearchResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } Document doc = request.getRequestDocument(); if (doc != null) { SearchInfo sR = SearchInfo.createFromXml(doc.getDocumentElement()); response.sendMultiStatus(((SearchResource) resource).search(sR)); } else { // request without request body is valid if requested resource // is a 'query' resource. response.sendMultiStatus(((SearchResource) resource).search(null)); } } /** * The ACL method * * @param request * @param response * @param resource * @throws DavException * @throws IOException */ protected void doAcl(WebdavRequest request, WebdavResponse response, DavResource resource) throws DavException, IOException { if (!(resource instanceof AclResource)) { response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); return; } Document doc = request.getRequestDocument(); if (doc == null) { throw new DavException(DavServletResponse.SC_BAD_REQUEST, "ACL request requires a DAV:acl body."); } AclProperty acl = AclProperty.createFromXml(doc.getDocumentElement()); ((AclResource)resource).alterAcl(acl); } /** * Return a new InputContext used for adding resource members * * @param request * @param in * @return * @see #spoolResource(WebdavRequest, WebdavResponse, DavResource, boolean) */ protected InputContext getInputContext(DavServletRequest request, InputStream in) { return new InputContextImpl(request, in); } /** * Return a new OutputContext used for spooling resource properties and * the resource content * * @param response * @param out * @return * @see #doPut(WebdavRequest, WebdavResponse, DavResource) * @see #doMkCol(WebdavRequest, WebdavResponse, DavResource) */ protected OutputContext getOutputContext(DavServletResponse response, OutputStream out) { return new OutputContextImpl(response, out); } /** * Obtain the (ordered!) list of content codings that have been used in the * request */ public static List getContentCodings(HttpServletRequest request) { return getListElementsFromHeaderField(request, "Content-Encoding"); } /** * Check whether recipient accepts GZIP content coding */ private static boolean acceptsGzipEncoding(HttpServletRequest request) { List result = getListElementsFromHeaderField(request, "Accept-Encoding"); for (String s : result) { s = s.replace(" ", ""); int semi = s.indexOf(';'); if ("gzip".equals(s)) { return true; } else if (semi > 0) { String enc = s.substring(0, semi); String parm = s.substring(semi + 1); if ("gzip".equals(enc) && parm.startsWith("q=")) { float q = Float.valueOf(parm.substring(2)); return q > 0; } } } return false; } private static List getListElementsFromHeaderField(HttpServletRequest request, String fieldName) { List result = Collections.emptyList(); for (Enumeration ceh = request.getHeaders(fieldName); ceh.hasMoreElements();) { for (String h : ceh.nextElement().split(",")) { if (!h.trim().isEmpty()) { if (result.isEmpty()) { result = new ArrayList(); } result.add(h.trim().toLowerCase(Locale.ENGLISH)); } } } return result; } /** * Get field value of a singleton field * @param request HTTP request * @param fieldName field name * @return the field value (when there is indeed a single field line) or {@code null} when field not present * @throws IllegalArgumentException when multiple field lines present */ protected static String getSingletonField(HttpServletRequest request, String fieldName) { Enumeration lines = request.getHeaders(fieldName); if (!lines.hasMoreElements()) { return null; } else { String value = lines.nextElement(); if (!lines.hasMoreElements()) { return value; } else { List v = new ArrayList<>(); v.add(value); while (lines.hasMoreElements()) { v.add(lines.nextElement()); } throw new IllegalArgumentException("Multiple field lines for '" + fieldName + "' header field: " + v); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy