nl.ellipsis.webdav.server.methods.AbstractMethod.java Source code

Java tutorial

Introduction

Here is the source code for nl.ellipsis.webdav.server.methods.AbstractMethod.java

Source

/*
 * Copyright 1999,2004 The Apache Software Foundation.
 *
 * Licensed 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 nl.ellipsis.webdav.server.methods;

import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.TimeZone;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import nl.ellipsis.webdav.HttpHeaders;
import nl.ellipsis.webdav.server.IMethodExecutor;
import nl.ellipsis.webdav.server.ITransaction;
import nl.ellipsis.webdav.server.StoredObject;
import nl.ellipsis.webdav.server.WebDAVConstants;
import nl.ellipsis.webdav.server.exceptions.LockFailedException;
import nl.ellipsis.webdav.server.locking.IResourceLocks;
import nl.ellipsis.webdav.server.locking.LockedObject;
import nl.ellipsis.webdav.server.util.CharsetUtil;
import nl.ellipsis.webdav.server.util.URLEncoder;
import nl.ellipsis.webdav.server.util.URLUtil;
import nl.ellipsis.webdav.server.util.XMLHelper;
import nl.ellipsis.webdav.server.util.XMLWriter;

public abstract class AbstractMethod implements IMethodExecutor {

    private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(AbstractMethod.class);

    private static final ThreadLocal<DateFormat> thLastmodifiedDateFormat = new ThreadLocal<DateFormat>();
    private static final ThreadLocal<DateFormat> thCreationDateFormat = new ThreadLocal<DateFormat>();
    private static final ThreadLocal<DateFormat> thLocalDateFormat = new ThreadLocal<DateFormat>();

    /**
     * Array containing the safe characters set.
     */
    protected static URLEncoder URL_ENCODER;

    /**
     * Simple date format for the creation date ISO 8601 representation (partial).
     */
    protected static final String CREATION_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";

    /**
     * Simple date format for the last modified date. (RFC 822 updated by RFC 1123)
     */
    protected static final String LAST_MODIFIED_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";

    protected static final String LOCAL_DATE_FORMAT = "dd/MM/yy' 'HH:mm:ss";

    protected static final int DEPTH_RESOURCE = 0;
    protected static final int DEPTH_RESOURCE_WITH_CHLDREN = 1;
    /**
     * Default depth is infinite.
     */
    protected static final int DEPTH_INFINITY = -1;
    protected static final String S_DEPTH_RESOURCE = "0";
    protected static final String S_DEPTH_RESOURCE_WITH_CHLDREN = "1";
    protected static final String S_DEPTH_INFINITY = "infinity";

    protected static final String PARAM_LOCKTOKEN = "locktoken";

    protected static final String NS_DAV_FULLNAME = "DAV:";
    protected static final String NS_DAV_PREFIX = "D";

    private static final String PROTOCOL_HTTP = "http";

    /**
     * GMT timezone - all HTTP dates are on GMT
     */
    protected static final String TIMEZONE_GMT = "GMT";

    static {
        URL_ENCODER = new URLEncoder();
        URL_ENCODER.addSafeCharacter(CharsetUtil.CHAR_DASH);
        URL_ENCODER.addSafeCharacter(CharsetUtil.CHAR_UNDERSCORE);
        URL_ENCODER.addSafeCharacter(CharsetUtil.CHAR_DOT);
        URL_ENCODER.addSafeCharacter(CharsetUtil.CHAR_ASTERIX);
        URL_ENCODER.addSafeCharacter(CharsetUtil.CHAR_FORWARD_SLASH);
    }

    /**
     * size of the io-buffer
     */
    protected static int BUF_SIZE = 65536;

    /**
     * Default lock timeout value.
     */
    protected static final int DEFAULT_TIMEOUT = 3600;

    /**
     * Maximum lock timeout.
     */
    protected static final int MAX_TIMEOUT = 604800;

    /**
     * Boolean value to temporary lock resources (for method locks)
     */
    protected static final boolean TEMPORARY = true;

    /**
     * Timeout for temporary locks
     */
    protected static final int TEMP_TIMEOUT = 10;

    public static String lastModifiedDateFormat(final Date date) {
        DateFormat df = thLastmodifiedDateFormat.get();
        if (df == null) {
            df = new SimpleDateFormat(LAST_MODIFIED_DATE_FORMAT, Locale.US);
            df.setTimeZone(TimeZone.getTimeZone(TIMEZONE_GMT));
            thLastmodifiedDateFormat.set(df);
        }
        return df.format(date);
    }

    public static String creationDateFormat(final Date date) {
        DateFormat df = thCreationDateFormat.get();
        if (df == null) {
            df = new SimpleDateFormat(CREATION_DATE_FORMAT);
            df.setTimeZone(TimeZone.getTimeZone(TIMEZONE_GMT));
            thCreationDateFormat.set(df);
        }
        return df.format(date);
    }

    public static String getLocalDateFormat(final Date date, final Locale loc) {
        DateFormat df = thLocalDateFormat.get();
        if (df == null) {
            df = new SimpleDateFormat(LOCAL_DATE_FORMAT, loc);
        }
        return df.format(date);
    }

    /**
     * Return the relative path associated with this servlet.
     * 
     * @param request
     *            The servlet request we are processing
     */
    protected static String getRelativePath(HttpServletRequest request) {
        String path = null;
        // Are we being processed by a RequestDispatcher.include()?
        if (request.getAttribute(WebDAVConstants.HttpRequestParam.INCLUDE_PATH_INFO) != null) {
            path = (String) request.getAttribute(WebDAVConstants.HttpRequestParam.INCLUDE_PATH_INFO);
            //         if (StringUtils.isEmpty(path)) {
            //            path = (String) request.getAttribute(WebDAVConstants.HttpRequestParam.INCLUDE_SERVLET_PATH);
            //         }
        }
        if (StringUtils.isEmpty(path)) {
            // No, extract the desired path directly from the request
            path = request.getPathInfo();
            //         if (StringUtils.isEmpty(path)) {
            //            path = request.getServletPath();
            //         }
        }
        return URLUtil.getRelativePath(path);
    }

    /**
     * Return W3C document 
     * @throws IOException 
     * @throws SAXException 
     * @throws ServletException 
     * @throws ParserConfigurationException 
     */
    protected static Document getDocument(HttpServletRequest request)
            throws ServletException, SAXException, IOException, ParserConfigurationException {
        DocumentBuilder documentBuilder = XMLHelper.getDocumentBuilder();
        if (LOG.isDebugEnabled()) {
            String xml = IOUtils.toString(request.getInputStream(), java.nio.charset.StandardCharsets.UTF_8.name());
            LOG.debug(xml);
            return documentBuilder
                    .parse(IOUtils.toInputStream(xml, java.nio.charset.StandardCharsets.UTF_8.name()));
        } else {
            return documentBuilder.parse(new InputSource(request.getInputStream()));
        }
    }

    /**
     * reads the depth header from the request and returns it as a int
     * 
     * @param req
     * @return the depth from the depth header
     */
    protected static int getDepth(HttpServletRequest req) {
        int depth = DEPTH_INFINITY;
        String depthStr = req.getHeader(HttpHeaders.DEPTH);
        if (depthStr != null) {
            if (depthStr.equals(S_DEPTH_RESOURCE)) {
                depth = DEPTH_RESOURCE;
            } else if (depthStr.equals(S_DEPTH_RESOURCE_WITH_CHLDREN)) {
                depth = DEPTH_RESOURCE_WITH_CHLDREN;
            }
        }
        return depth;
    }

    /**
     * URL rewriter.
     * 
     * @param path
     *            Path which has to be rewiten
     * @return the rewritten path
     */
    protected static String rewriteUrl(String path) {
        return URL_ENCODER.encode(path);
    }

    /**
     * Get the ETag associated with a file.
     * 
     * @param StoredObject
     *            StoredObject to get resourceLength, lastModified and a hashCode of
     *            StoredObject
     * @return the ETag
     */
    protected static String getETag(StoredObject so) {
        String resourceLength = "";
        String lastModified = "";
        if (so != null && so.isResource()) {
            resourceLength = Long.toString(so.getResourceLength());
            lastModified = Long.toString(so.getLastModified().getTime());
        }
        return "W/" + CharsetUtil.DQUOTE + resourceLength + CharsetUtil.CHAR_DASH + lastModified
                + CharsetUtil.DQUOTE;
    }

    protected static String[] getLockIdFromIfHeader(HttpServletRequest req) {
        String[] ids = new String[2];
        String id = req.getHeader(HttpHeaders.IF);

        if (StringUtils.isNotEmpty(id)) {
            // only one locktoken between parenthesis
            if (id.indexOf(CharsetUtil.GREATER_THAN + CharsetUtil.RIGHT_PARENTHESIS) == id
                    .lastIndexOf(CharsetUtil.GREATER_THAN + CharsetUtil.RIGHT_PARENTHESIS)) {
                id = id.substring(id.indexOf(CharsetUtil.LEFT_PARENTHESIS + CharsetUtil.LESS_THAN),
                        id.indexOf(CharsetUtil.GREATER_THAN + CharsetUtil.RIGHT_PARENTHESIS));

                if (id.indexOf(PARAM_LOCKTOKEN + CharsetUtil.COLON) != -1) {
                    id = id.substring(id.indexOf(CharsetUtil.CHAR_COLON) + 1);
                }
                ids[0] = id;
            } else {
                String firstId = id.substring(id.indexOf(CharsetUtil.LEFT_PARENTHESIS + CharsetUtil.LESS_THAN),
                        id.indexOf(CharsetUtil.GREATER_THAN + CharsetUtil.RIGHT_PARENTHESIS));
                if (firstId.indexOf(PARAM_LOCKTOKEN + CharsetUtil.COLON) != -1) {
                    firstId = firstId.substring(firstId.indexOf(CharsetUtil.CHAR_COLON) + 1);
                }
                ids[0] = firstId;

                String secondId = id.substring(id.lastIndexOf(CharsetUtil.LEFT_PARENTHESIS + CharsetUtil.LESS_THAN),
                        id.lastIndexOf(CharsetUtil.GREATER_THAN + CharsetUtil.RIGHT_PARENTHESIS));
                if (secondId.indexOf(PARAM_LOCKTOKEN + CharsetUtil.COLON) != -1) {
                    secondId = secondId.substring(secondId.indexOf(CharsetUtil.CHAR_COLON) + 1);
                }
                ids[1] = secondId;
            }

        } else {
            ids = null;
        }
        return ids;
    }

    protected static String getLockIdFromLockTokenHeader(HttpServletRequest req) {
        String id = req.getHeader(HttpHeaders.LOCK_TOKEN);
        if (id != null) {
            id = id.substring(id.indexOf(CharsetUtil.CHAR_COLON) + 1, id.indexOf(CharsetUtil.CHAR_GREATER_THAN));

        }
        return id;
    }

    /**
     * Checks if locks on resources at the given path exists and if so checks the
     * If-Header to make sure the If-Header corresponds to the locked resource.
     * Returning true if no lock exists or the If-Header is corresponding to the
     * locked resource
     * 
     * @param req
     *            Servlet request
     * @param resp
     *            Servlet response
     * @param resourceLocks
     * @param path
     *            path to the resource
     * @param errorList
     *            List of error to be displayed
     * @return true if no lock on a resource with the given path exists or if the
     *         If-Header corresponds to the locked resource
     * @throws IOException
     * @throws LockFailedException
     */
    protected static boolean checkLocks(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp,
            IResourceLocks resourceLocks, String path) throws IOException, LockFailedException {

        LockedObject loByPath = resourceLocks.getLockedObjectByPath(transaction, path);
        if (loByPath != null) {

            if (loByPath.isShared()) {
                return true;
            }

            // the resource is locked
            String[] lockTokens = getLockIdFromIfHeader(req);
            String lockToken = null;
            if (lockTokens != null) {
                lockToken = lockTokens[0];
            } else {
                return false;
            }
            if (lockToken != null) {
                LockedObject loByIf = resourceLocks.getLockedObjectByID(transaction, lockToken);
                if (loByIf == null) {
                    // no locked resource to the given lockToken
                    return false;
                }
                if (!loByIf.equals(loByPath)) {
                    loByIf = null;
                    return false;
                }
                loByIf = null;
            }

        }
        loByPath = null;
        return true;
    }

    /**
     * Send a multistatus element containing a complete error report to the client.
     * If the errorList contains only one error, send the error directly without
     * wrapping it in a multistatus message.
     * 
     * @param req
     *            Servlet request
     * @param resp
     *            Servlet response
     * @param errorList
     *            List of error to be displayed
     */
    protected static void sendReport(HttpServletRequest req, HttpServletResponse resp,
            Hashtable<String, Integer> errorList) throws IOException {

        if (errorList.size() == 1) {
            int code = errorList.elements().nextElement();
            HttpStatus s = HttpStatus.valueOf(code);
            String status = s.getReasonPhrase();
            if (status != null && !status.isEmpty()) {
                resp.sendError(code, status);
            } else {
                resp.sendError(code);
            }
        } else {
            resp.setStatus(HttpStatus.MULTI_STATUS.value());

            String absoluteUri = req.getRequestURI();
            // String relativePath = getRelativePath(req);

            XMLWriter generatedXML = new XMLWriter();
            generatedXML.writeXMLHeader();

            generatedXML.writeElement(NS_DAV_PREFIX, NS_DAV_FULLNAME, WebDAVConstants.XMLTag.MULTISTATUS,
                    XMLWriter.OPENING);

            Enumeration<String> pathList = errorList.keys();
            while (pathList.hasMoreElements()) {

                String errorPath = (String) pathList.nextElement();
                int errorCode = ((Integer) errorList.get(errorPath)).intValue();

                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.RESPONSE, XMLWriter.OPENING);

                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.HREF, XMLWriter.OPENING);
                String toAppend = null;
                if (absoluteUri.endsWith(errorPath)) {
                    toAppend = absoluteUri;
                } else if (absoluteUri.contains(errorPath)) {
                    int endIndex = absoluteUri.indexOf(errorPath) + errorPath.length();
                    toAppend = absoluteUri.substring(0, endIndex);
                }
                if (StringUtils.isEmpty(toAppend)) {
                    toAppend = CharsetUtil.FORWARD_SLASH;
                } else if (!toAppend.startsWith(CharsetUtil.FORWARD_SLASH) && !toAppend.startsWith(PROTOCOL_HTTP)) {
                    toAppend = CharsetUtil.FORWARD_SLASH + toAppend;
                }
                generatedXML.writeText(errorPath);
                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.HREF, XMLWriter.CLOSING);
                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.STATUS, XMLWriter.OPENING);
                HttpStatus s = HttpStatus.valueOf(errorCode);
                generatedXML.writeText("HTTP/1.1 " + errorCode + " " + s.getReasonPhrase());
                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.STATUS, XMLWriter.CLOSING);

                generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.RESPONSE, XMLWriter.CLOSING);

            }

            generatedXML.writeElement(NS_DAV_PREFIX, WebDAVConstants.XMLTag.MULTISTATUS, XMLWriter.CLOSING);

            Writer writer = resp.getWriter();
            writer.write(generatedXML.toString());
            writer.close();
        }
    }

}