pathTokenList = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+ boolean escape = false;
+ for (char c : path.toCharArray()) {
+ if (c == '[') {
+ if (sb.length() > 0) {
+ pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
+ }
+ sb.setLength(0);
+ } else if (c == ']') {
+ if (sb.length() > 0) {
+ pathTokenList.add(new PathToken(PathType.NUMBER, sb.toString()));
+ }
+ sb.setLength(0);
+ } else if (c == '.') {
+ if (escape) {
+ sb.append(c);
+ } else {
+ if (sb.length() > 0) {
+ pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
+ }
+ sb.setLength(0);
+ }
+
+ } else if (c == '\\') {
+ escape = true;
+ } else {
+ sb.append(c);
+ }
+ }
+ if (sb.length() > 0) {
+ pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
+ }
+ return pathTokenList;
+ }
+
+
+
+
+ private static JsonNode getJsonNode(String responseBody) {
+ try {
+ return objectMapper.readTree(responseBody);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private enum PathType {
+ KEY, NUMBER;
+ }
+
+ private static class PathToken {
+ private PathType type; // 0 是key, 1 是索引数字
+ private String value;
+
+ public PathToken(PathType type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public PathType getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/net/sf/webdav/IMethodExecutor.java b/src/main/java/net/sf/webdav/IMethodExecutor.java
new file mode 100644
index 0000000..a913120
--- /dev/null
+++ b/src/main/java/net/sf/webdav/IMethodExecutor.java
@@ -0,0 +1,30 @@
+/*
+ * 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 net.sf.webdav;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.exceptions.LockFailedException;
+
+public interface IMethodExecutor {
+
+ void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException;
+
+}
diff --git a/src/main/java/net/sf/webdav/IMimeTyper.java b/src/main/java/net/sf/webdav/IMimeTyper.java
new file mode 100644
index 0000000..2403b48
--- /dev/null
+++ b/src/main/java/net/sf/webdav/IMimeTyper.java
@@ -0,0 +1,13 @@
+package net.sf.webdav;
+
+public interface IMimeTyper {
+
+ /**
+ * Detect the mime type of this object
+ *
+ * @param transaction
+ * @param path
+ * @return
+ */
+ String getMimeType(ITransaction transaction, String path);
+}
diff --git a/src/main/java/net/sf/webdav/ITransaction.java b/src/main/java/net/sf/webdav/ITransaction.java
new file mode 100644
index 0000000..6b505c3
--- /dev/null
+++ b/src/main/java/net/sf/webdav/ITransaction.java
@@ -0,0 +1,9 @@
+package net.sf.webdav;
+
+import java.security.Principal;
+
+public interface ITransaction {
+
+ Principal getPrincipal();
+
+}
diff --git a/src/main/java/net/sf/webdav/IWebdavStore.java b/src/main/java/net/sf/webdav/IWebdavStore.java
new file mode 100644
index 0000000..334d195
--- /dev/null
+++ b/src/main/java/net/sf/webdav/IWebdavStore.java
@@ -0,0 +1,223 @@
+/*
+ * $Header: /Users/ak/temp/cvs2svn/webdav-servlet/src/main/java/net/sf/webdav/IWebdavStore.java,v 1.1 2008-08-05 07:38:42 bauhardt Exp $
+ * $Revision: 1.1 $
+ * $Date: 2008-08-05 07:38:42 $
+ *
+ * ====================================================================
+ *
+ * Copyright 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 net.sf.webdav;
+
+import java.io.InputStream;
+import java.security.Principal;
+
+import net.sf.webdav.exceptions.WebdavException;
+
+/**
+ * Interface for simple implementation of any store for the WebdavServlet
+ *
+ * based on the BasicWebdavStore from Oliver Zeigermann, that was part of the
+ * Webdav Construcktion Kit from slide
+ *
+ */
+public interface IWebdavStore {
+
+ /**
+ * Life cycle method, called by WebdavServlet's destroy() method. Should be used to clean up resources.
+ */
+ void destroy();
+
+ /**
+ * Indicates that a new request or transaction with this store involved has
+ * been started. The request will be terminated by either {@link #commit()}
+ * or {@link #rollback()}. If only non-read methods have been called, the
+ * request will be terminated by a {@link #commit()}. This method will be
+ * called by (@link WebdavStoreAdapter} at the beginning of each request.
+ *
+ *
+ * @param principal
+ * the principal that started this request or null
if
+ * there is non available
+ *
+ * @throws WebdavException
+ */
+ ITransaction begin(Principal principal);
+
+ /**
+ * Checks if authentication information passed in is valid. If not throws an
+ * exception.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ */
+ void checkAuthentication(ITransaction transaction);
+
+ /**
+ * Indicates that all changes done inside this request shall be made
+ * permanent and any transactions, connections and other temporary resources
+ * shall be terminated.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ *
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ void commit(ITransaction transaction);
+
+ /**
+ * Indicates that all changes done inside this request shall be undone and
+ * any transactions, connections and other temporary resources shall be
+ * terminated.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ *
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ void rollback(ITransaction transaction);
+
+ /**
+ * Creates a folder at the position specified by folderUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param folderUri
+ * URI of the folder
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ void createFolder(ITransaction transaction, String folderUri);
+
+ /**
+ * Creates a content resource at the position specified by
+ * resourceUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param resourceUri
+ * URI of the content resource
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ void createResource(ITransaction transaction, String resourceUri);
+
+ /**
+ * Gets the content of the resource specified by resourceUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param resourceUri
+ * URI of the content resource
+ * @return input stream you can read the content of the resource from
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ InputStream getResourceContent(ITransaction transaction, String resourceUri);
+
+ /**
+ * Sets / stores the content of the resource specified by
+ * resourceUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param resourceUri
+ * URI of the resource where the content will be stored
+ * @param content
+ * input stream from which the content will be read from
+ * @param contentType
+ * content type of the resource or null
if unknown
+ * @param characterEncoding
+ * character encoding of the resource or null
if unknown
+ * or not applicable
+ * @return lenght of resource
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ long setResourceContent(ITransaction transaction, String resourceUri,
+ InputStream content, String contentType, String characterEncoding);
+
+ /**
+ * Gets the names of the children of the folder specified by
+ * folderUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param folderUri
+ * URI of the folder
+ * @return a (possibly empty) list of children, or null
if the
+ * uri points to a file
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ String[] getChildrenNames(ITransaction transaction, String folderUri);
+
+ /**
+ * Gets the length of the content resource specified by
+ * resourceUri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param resourceUri
+ * URI of the content resource
+ * @return length of the resource in bytes, -1
declares this
+ * value as invalid and asks the adapter to try to set it from the
+ * properties if possible
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ long getResourceLength(ITransaction transaction, String path);
+
+ /**
+ * Removes the object specified by uri
.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param uri
+ * URI of the object, i.e. content resource or folder
+ * @throws WebdavException
+ * if something goes wrong on the store level
+ */
+ void removeObject(ITransaction transaction, String uri);
+
+ boolean moveObject(ITransaction transaction, String destinationPath, String sourcePath);
+
+ /**
+ * Gets the storedObject specified by uri
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param uri
+ * URI
+ * @return StoredObject
+ */
+ StoredObject getStoredObject(ITransaction transaction, String uri);
+
+}
diff --git a/src/main/java/net/sf/webdav/LocalFileSystemStore.java b/src/main/java/net/sf/webdav/LocalFileSystemStore.java
new file mode 100644
index 0000000..2422ff9
--- /dev/null
+++ b/src/main/java/net/sf/webdav/LocalFileSystemStore.java
@@ -0,0 +1,228 @@
+/*
+ *
+ * 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 net.sf.webdav;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.webdav.exceptions.UnauthenticatedException;
+import net.sf.webdav.exceptions.WebdavException;
+
+/**
+ * Reference Implementation of WebdavStore
+ *
+ * @author joa
+ * @author re
+ */
+public class LocalFileSystemStore implements IWebdavStore {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(LocalFileSystemStore.class);
+
+ private static int BUF_SIZE = 65536;
+
+ private File _root = null;
+
+ public LocalFileSystemStore(File root) {
+ _root = root;
+ }
+
+ public void destroy() {
+ ;
+ }
+
+ public ITransaction begin(Principal principal) throws WebdavException {
+ LOG.info("LocalFileSystemStore.begin()");
+ if (!_root.exists()) {
+ if (!_root.mkdirs()) {
+ throw new WebdavException("root path: "
+ + _root.getAbsolutePath()
+ + " does not exist and could not be created");
+ }
+ }
+// if (principal == null) {
+// throw new UnauthenticatedException(WebdavStatus.SC_UNAUTHORIZED);
+// }
+ return null;
+ }
+
+ public void checkAuthentication(ITransaction transaction)
+ throws SecurityException {
+ LOG.info("LocalFileSystemStore.checkAuthentication()");
+ // do nothing
+// throw new UnauthenticatedException(WebdavStatus.SC_FORBIDDEN);
+ }
+
+ public void commit(ITransaction transaction) throws WebdavException {
+ // do nothing
+ LOG.info("LocalFileSystemStore.commit()");
+ }
+
+ public void rollback(ITransaction transaction) throws WebdavException {
+ // do nothing
+ LOG.info("LocalFileSystemStore.rollback()");
+
+ }
+
+ public void createFolder(ITransaction transaction, String uri)
+ throws WebdavException {
+ LOG.info("LocalFileSystemStore.createFolder(" + uri + ")");
+ File file = new File(_root, uri);
+ if (!file.mkdir())
+ throw new WebdavException("cannot create folder: " + uri);
+ }
+
+ public void createResource(ITransaction transaction, String uri)
+ throws WebdavException {
+ LOG.info("LocalFileSystemStore.createResource(" + uri + ")");
+ File file = new File(_root, uri);
+ try {
+ if (!file.createNewFile())
+ throw new WebdavException("cannot create file: " + uri);
+ } catch (IOException e) {
+ LOG
+ .error("LocalFileSystemStore.createResource(" + uri
+ + ") failed");
+ throw new WebdavException(e);
+ }
+ }
+
+ public long setResourceContent(ITransaction transaction, String uri,
+ InputStream is, String contentType, String characterEncoding)
+ throws WebdavException {
+
+ LOG.info("LocalFileSystemStore.setResourceContent(" + uri + ")");
+ File file = new File(_root, uri);
+ try {
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(
+ file), BUF_SIZE);
+ try {
+ int read;
+ byte[] copyBuffer = new byte[BUF_SIZE];
+
+ while ((read = is.read(copyBuffer, 0, copyBuffer.length)) != -1) {
+ os.write(copyBuffer, 0, read);
+ }
+ } finally {
+ try {
+ is.close();
+ } finally {
+ os.close();
+ }
+ }
+ } catch (IOException e) {
+ LOG.error("LocalFileSystemStore.setResourceContent(" + uri
+ + ") failed");
+ throw new WebdavException(e);
+ }
+ long length = -1;
+
+ try {
+ length = file.length();
+ } catch (SecurityException e) {
+ LOG.error("LocalFileSystemStore.setResourceContent(" + uri
+ + ") failed" + "\nCan't get file.length");
+ }
+
+ return length;
+ }
+
+ public String[] getChildrenNames(ITransaction transaction, String uri)
+ throws WebdavException {
+ LOG.info("LocalFileSystemStore.getChildrenNames(" + uri + ")");
+ File file = new File(_root, uri);
+ String[] childrenNames = null;
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ List childList = new ArrayList();
+ String name = null;
+ for (int i = 0; i < children.length; i++) {
+ name = children[i].getName();
+ childList.add(name);
+ LOG.info("Child " + i + ": " + name);
+ }
+ childrenNames = new String[childList.size()];
+ childrenNames = (String[]) childList.toArray(childrenNames);
+ }
+ return childrenNames;
+ }
+
+ public void removeObject(ITransaction transaction, String uri)
+ throws WebdavException {
+ File file = new File(_root, uri);
+ boolean success = file.delete();
+ LOG.info("LocalFileSystemStore.removeObject(" + uri + ")=" + success);
+ if (!success) {
+ throw new WebdavException("cannot delete object: " + uri);
+ }
+
+ }
+
+ @Override
+ public boolean moveObject(ITransaction transaction, String destinationPath, String path) {
+ return false;
+ }
+
+ public InputStream getResourceContent(ITransaction transaction, String uri)
+ throws WebdavException {
+ LOG.info("LocalFileSystemStore.getResourceContent(" + uri + ")");
+ File file = new File(_root, uri);
+
+ InputStream in;
+ try {
+ in = new BufferedInputStream(new FileInputStream(file));
+ } catch (IOException e) {
+ LOG.error("LocalFileSystemStore.getResourceContent(" + uri
+ + ") failed");
+ throw new WebdavException(e);
+ }
+ return in;
+ }
+
+ public long getResourceLength(ITransaction transaction, String uri)
+ throws WebdavException {
+ LOG.info("LocalFileSystemStore.getResourceLength(" + uri + ")");
+ File file = new File(_root, uri);
+ return file.length();
+ }
+
+ public StoredObject getStoredObject(ITransaction transaction, String uri) {
+
+ StoredObject so = null;
+
+ File file = new File(_root, uri);
+ if (file.exists()) {
+ so = new StoredObject();
+ so.setFolder(file.isDirectory());
+ so.setLastModified(new Date(file.lastModified()));
+ so.setCreationDate(new Date(file.lastModified()));
+ so.setResourceLength(getResourceLength(transaction, uri));
+ }
+
+ return so;
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/StoredObject.java b/src/main/java/net/sf/webdav/StoredObject.java
new file mode 100644
index 0000000..443e149
--- /dev/null
+++ b/src/main/java/net/sf/webdav/StoredObject.java
@@ -0,0 +1,165 @@
+/*
+ * ====================================================================
+ *
+ * Copyright 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 net.sf.webdav;
+
+import java.util.Date;
+
+public class StoredObject {
+
+ private boolean isFolder;
+ private Date lastModified;
+ private Date creationDate;
+ private long contentLength;
+ private String mimeType;
+
+ private boolean isNullRessource;
+
+ /**
+ * Determines whether the StoredObject is a folder or a resource
+ *
+ * @return true if the StoredObject is a collection
+ */
+ public boolean isFolder() {
+ return (isFolder);
+ }
+
+ /**
+ * Determines whether the StoredObject is a folder or a resource
+ *
+ * @return true if the StoredObject is a resource
+ */
+ public boolean isResource() {
+ return (!isFolder);
+ }
+
+ /**
+ * Sets a new StoredObject as a collection or resource
+ *
+ * @param f
+ * true - collection ; false - resource
+ */
+ public void setFolder(boolean f) {
+ this.isFolder = f;
+ }
+
+ /**
+ * Gets the date of the last modification
+ *
+ * @return last modification Date
+ */
+ public Date getLastModified() {
+ return (lastModified);
+ }
+
+ /**
+ * Sets the date of the last modification
+ *
+ * @param d
+ * date of the last modification
+ */
+ public void setLastModified(Date d) {
+ this.lastModified = d;
+ }
+
+ /**
+ * Gets the date of the creation
+ *
+ * @return creation Date
+ */
+ public Date getCreationDate() {
+ return (creationDate);
+ }
+
+ /**
+ * Sets the date of the creation
+ *
+ * @param d
+ * date of the creation
+ */
+ public void setCreationDate(Date c) {
+ this.creationDate = c;
+ }
+
+ /**
+ * Gets the length of the resource content
+ *
+ * @return length of the resource content
+ */
+ public long getResourceLength() {
+ return (contentLength);
+ }
+
+ /**
+ * Sets the length of the resource content
+ *
+ * @param l
+ * the length of the resource content
+ */
+ public void setResourceLength(long l) {
+ this.contentLength = l;
+ }
+
+ /**
+ * Gets the state of the resource
+ *
+ * @return true if the resource is in lock-null state
+ */
+ public boolean isNullResource() {
+ return isNullRessource;
+ }
+
+ /**
+ * Sets a StoredObject as a lock-null resource
+ *
+ * @param f
+ * true to set the resource as lock-null resource
+ */
+ public void setNullResource(boolean f) {
+ this.isNullRessource = f;
+ this.isFolder = false;
+ this.creationDate = null;
+ this.lastModified = null;
+ // this.content = null;
+ this.contentLength = 0;
+ this.mimeType= null;
+ }
+
+ /**
+ * Retrieve the myme type from the store object.
+ * Can also return NULL if the store does not handle
+ * mime type stuff.
+ * In that case the mime type is determined by the servletcontext
+ *
+ * @return the mimeType
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * Set the mime type of this object
+ *
+ * @param mimeType the mimeType to set
+ */
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/WebDavServletBean.java b/src/main/java/net/sf/webdav/WebDavServletBean.java
new file mode 100644
index 0000000..146710f
--- /dev/null
+++ b/src/main/java/net/sf/webdav/WebDavServletBean.java
@@ -0,0 +1,219 @@
+package net.sf.webdav;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.exceptions.UnauthenticatedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.fromcatalina.MD5Encoder;
+import net.sf.webdav.locking.ResourceLocks;
+import net.sf.webdav.methods.DoCopy;
+import net.sf.webdav.methods.DoDelete;
+import net.sf.webdav.methods.DoGet;
+import net.sf.webdav.methods.DoHead;
+import net.sf.webdav.methods.DoLock;
+import net.sf.webdav.methods.DoMkcol;
+import net.sf.webdav.methods.DoMove;
+import net.sf.webdav.methods.DoNotImplemented;
+import net.sf.webdav.methods.DoOptions;
+import net.sf.webdav.methods.DoPropfind;
+import net.sf.webdav.methods.DoProppatch;
+import net.sf.webdav.methods.DoPut;
+import net.sf.webdav.methods.DoUnlock;
+
+public class WebDavServletBean extends HttpServlet {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(WebDavServletBean.class);
+
+ /**
+ * MD5 message digest provider.
+ */
+ protected static MessageDigest MD5_HELPER;
+
+ /**
+ * The MD5 helper object for this class.
+ */
+ protected static final MD5Encoder MD5_ENCODER = new MD5Encoder();
+
+ private static final boolean READ_ONLY = false;
+ protected ResourceLocks _resLocks;
+ protected IWebdavStore _store;
+ private HashMap _methodMap = new HashMap();
+
+ public WebDavServletBean() {
+ _resLocks = new ResourceLocks();
+
+ try {
+ MD5_HELPER = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException();
+ }
+ }
+
+ public void init(IWebdavStore store, String dftIndexFile,
+ String insteadOf404, int nocontentLenghHeaders,
+ boolean lazyFolderCreationOnPut) throws ServletException {
+
+ _store = store;
+
+ IMimeTyper mimeTyper = new IMimeTyper() {
+ public String getMimeType(ITransaction transaction, String path) {
+ String retVal= _store.getStoredObject(transaction, path).getMimeType();
+ if ( retVal== null) {
+ retVal= getServletContext().getMimeType( path);
+ }
+ return retVal;
+ }
+ };
+
+ register("GET", new DoGet(store, dftIndexFile, insteadOf404, _resLocks,
+ mimeTyper, nocontentLenghHeaders));
+ register("HEAD", new DoHead(store, dftIndexFile, insteadOf404,
+ _resLocks, mimeTyper, nocontentLenghHeaders));
+ DoDelete doDelete = (DoDelete) register("DELETE", new DoDelete(store,
+ _resLocks, READ_ONLY));
+ DoCopy doCopy = (DoCopy) register("COPY", new DoCopy(store, _resLocks,
+ doDelete, READ_ONLY));
+ register("LOCK", new DoLock(store, _resLocks, READ_ONLY));
+ register("UNLOCK", new DoUnlock(store, _resLocks, READ_ONLY));
+ register("MOVE", new DoMove(store, _resLocks, doDelete, doCopy, READ_ONLY));
+ register("MKCOL", new DoMkcol(store, _resLocks, READ_ONLY));
+ register("OPTIONS", new DoOptions(store, _resLocks));
+ register("PUT", new DoPut(store, _resLocks, READ_ONLY,
+ lazyFolderCreationOnPut));
+ register("PROPFIND", new DoPropfind(store, _resLocks, mimeTyper));
+ register("PROPPATCH", new DoProppatch(store, _resLocks, READ_ONLY));
+ register("*NO*IMPL*", new DoNotImplemented(READ_ONLY));
+ }
+
+ @Override
+ public void destroy() {
+ if(_store != null)
+ _store.destroy();
+ super.destroy();
+ }
+
+ protected IMethodExecutor register(String methodName, IMethodExecutor method) {
+ _methodMap.put(methodName, method);
+ return method;
+ }
+
+ /**
+ * Handles the special WebDAV methods.
+ */
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ String methodName = req.getMethod();
+ ITransaction transaction = null;
+ boolean needRollback = false;
+
+ if (LOG.isTraceEnabled())
+ debugRequest(methodName, req);
+
+ try {
+ Principal userPrincipal = getUserPrincipal(req);
+ transaction = _store.begin(userPrincipal);
+ needRollback = true;
+ _store.checkAuthentication(transaction);
+ resp.setStatus(WebdavStatus.SC_OK);
+
+ try {
+ IMethodExecutor methodExecutor = (IMethodExecutor) _methodMap
+ .get(methodName);
+ if (methodExecutor == null) {
+ methodExecutor = (IMethodExecutor) _methodMap
+ .get("*NO*IMPL*");
+ }
+
+ methodExecutor.execute(transaction, req, resp);
+
+ _store.commit(transaction);
+ /** Clear not consumed data
+ *
+ * Clear input stream if available otherwise later access
+ * include current input. These cases occure if the client
+ * sends a request with body to an not existing resource.
+ */
+ if (req.getContentLength() != 0 && req.getInputStream().available() > 0) {
+ if (LOG.isTraceEnabled()) { LOG.trace("Clear not consumed data!"); }
+ while (req.getInputStream().available() > 0) {
+ req.getInputStream().read();
+ }
+ }
+ needRollback = false;
+ } catch (IOException e) {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ java.io.PrintWriter pw = new java.io.PrintWriter(sw);
+ e.printStackTrace(pw);
+ LOG.error("IOException: " + sw.toString());
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ _store.rollback(transaction);
+ throw new ServletException(e);
+ }
+
+ } catch (UnauthenticatedException e) {
+ resp.sendError(e.getCode());
+ } catch (WebdavException e) {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ java.io.PrintWriter pw = new java.io.PrintWriter(sw);
+ e.printStackTrace(pw);
+ LOG.error("WebdavException: " + sw.toString());
+ throw new ServletException(e);
+ } catch (Exception e) {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ java.io.PrintWriter pw = new java.io.PrintWriter(sw);
+ e.printStackTrace(pw);
+ LOG.error("Exception: " + sw.toString());
+ } finally {
+ if (needRollback)
+ _store.rollback(transaction);
+ }
+
+ }
+
+ /**
+ * Method that permit to customize the way
+ * user information are extracted from the request, default use JAAS
+ * @param req
+ * @return
+ */
+ protected Principal getUserPrincipal(HttpServletRequest req) {
+ return req.getUserPrincipal();
+ }
+
+ private void debugRequest(String methodName, HttpServletRequest req) {
+ LOG.trace("-----------");
+ LOG.trace("WebdavServlet\n request: methodName = " + methodName);
+ LOG.trace("time: " + System.currentTimeMillis());
+ LOG.trace("path: " + req.getRequestURI());
+ LOG.trace("-----------");
+ Enumeration> e = req.getHeaderNames();
+ while (e.hasMoreElements()) {
+ String s = (String) e.nextElement();
+ LOG.trace("header: " + s + " " + req.getHeader(s));
+ }
+ e = req.getAttributeNames();
+ while (e.hasMoreElements()) {
+ String s = (String) e.nextElement();
+ LOG.trace("attribute: " + s + " " + req.getAttribute(s));
+ }
+ e = req.getParameterNames();
+ while (e.hasMoreElements()) {
+ String s = (String) e.nextElement();
+ LOG.trace("parameter: " + s + " " + req.getParameter(s));
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/WebdavServlet.java b/src/main/java/net/sf/webdav/WebdavServlet.java
new file mode 100644
index 0000000..c9540be
--- /dev/null
+++ b/src/main/java/net/sf/webdav/WebdavServlet.java
@@ -0,0 +1,114 @@
+/*
+ * 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 net.sf.webdav;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+import javax.servlet.ServletException;
+
+import net.sf.webdav.exceptions.WebdavException;
+
+/**
+ * Servlet which provides support for WebDAV level 2.
+ *
+ * the original class is org.apache.catalina.servlets.WebdavServlet by Remy
+ * Maucherat, which was heavily changed
+ *
+ * @author Remy Maucherat
+ */
+
+public class WebdavServlet extends WebDavServletBean {
+
+ private static final String ROOTPATH_PARAMETER = "rootpath";
+
+ public void init() throws ServletException {
+
+ // Parameters from web.xml
+ String clazzName = getServletConfig().getInitParameter(
+ "ResourceHandlerImplementation");
+ if (clazzName == null || clazzName.equals("")) {
+ clazzName = LocalFileSystemStore.class.getName();
+ }
+
+ File root = getFileRoot();
+
+ IWebdavStore webdavStore = constructStore(clazzName, root);
+
+ boolean lazyFolderCreationOnPut = getInitParameter("lazyFolderCreationOnPut") != null
+ && getInitParameter("lazyFolderCreationOnPut").equals("1");
+
+ String dftIndexFile = getInitParameter("default-index-file");
+ String insteadOf404 = getInitParameter("instead-of-404");
+
+ int noContentLengthHeader = getIntInitParameter("no-content-length-headers");
+
+ super.init(webdavStore, dftIndexFile, insteadOf404,
+ noContentLengthHeader, lazyFolderCreationOnPut);
+ }
+
+ private int getIntInitParameter(String key) {
+ return getInitParameter(key) == null ? -1 : Integer
+ .parseInt(getInitParameter(key));
+ }
+
+ protected IWebdavStore constructStore(String clazzName, File root) {
+ IWebdavStore webdavStore;
+ try {
+ Class> clazz = WebdavServlet.class.getClassLoader().loadClass(
+ clazzName);
+
+ Constructor> ctor = clazz
+ .getConstructor(new Class[] { File.class });
+
+ webdavStore = (IWebdavStore) ctor
+ .newInstance(new Object[] { root });
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("some problem making store component", e);
+ }
+ return webdavStore;
+ }
+
+ private File getFileRoot() {
+ String rootPath = getInitParameter(ROOTPATH_PARAMETER);
+ if (rootPath == null) {
+ throw new WebdavException("missing parameter: "
+ + ROOTPATH_PARAMETER);
+ }
+ if (rootPath.equals("*WAR-FILE-ROOT*")) {
+ String file = LocalFileSystemStore.class.getProtectionDomain()
+ .getCodeSource().getLocation().getFile().replace('\\', '/');
+ if (file.charAt(0) == '/'
+ && System.getProperty("os.name").indexOf("Windows") != -1) {
+ file = file.substring(1, file.length());
+ }
+
+ int ix = file.indexOf("/WEB-INF/");
+ if (ix != -1) {
+ rootPath = file.substring(0, ix).replace('/',
+ File.separatorChar);
+ } else {
+ throw new WebdavException(
+ "Could not determine root of war file. Can't extract from path '"
+ + file + "' for this web container");
+ }
+ }
+ return new File(rootPath);
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/WebdavStatus.java b/src/main/java/net/sf/webdav/WebdavStatus.java
new file mode 100644
index 0000000..ae206cc
--- /dev/null
+++ b/src/main/java/net/sf/webdav/WebdavStatus.java
@@ -0,0 +1,273 @@
+package net.sf.webdav;
+
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Wraps the HttpServletResponse class to abstract the specific protocol used.
+ * To support other protocols we would only need to modify this class and the
+ * WebDavRetCode classes.
+ *
+ * @author Marc Eaddy
+ * @version 1.0, 16 Nov 1997
+ */
+public class WebdavStatus {
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * This Hashtable contains the mapping of HTTP and WebDAV status codes to
+ * descriptive text. This is a static variable.
+ */
+ private static Hashtable _mapStatusCodes = new Hashtable();
+
+ // ------------------------------------------------------ HTTP Status Codes
+
+ /**
+ * Status code (200) indicating the request succeeded normally.
+ */
+ public static final int SC_OK = HttpServletResponse.SC_OK;
+
+ /**
+ * Status code (201) indicating the request succeeded and created a new
+ * resource on the server.
+ */
+ public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
+
+ /**
+ * Status code (202) indicating that a request was accepted for processing,
+ * but was not completed.
+ */
+ public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
+
+ /**
+ * Status code (204) indicating that the request succeeded but that there
+ * was no new information to return.
+ */
+ public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
+
+ /**
+ * Status code (301) indicating that the resource has permanently moved to a
+ * new location, and that future references should use a new URI with their
+ * requests.
+ */
+ public static final int SC_MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY;
+
+ /**
+ * Status code (302) indicating that the resource has temporarily moved to
+ * another location, but that future references should still use the
+ * original URI to access the resource.
+ */
+ public static final int SC_MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY;
+
+ /**
+ * Status code (304) indicating that a conditional GET operation found that
+ * the resource was available and not modified.
+ */
+ public static final int SC_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED;
+
+ /**
+ * Status code (400) indicating the request sent by the client was
+ * syntactically incorrect.
+ */
+ public static final int SC_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST;
+
+ /**
+ * Status code (401) indicating that the request requires HTTP
+ * authentication.
+ */
+ public static final int SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
+
+ /**
+ * Status code (403) indicating the server understood the request but
+ * refused to fulfill it.
+ */
+ public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
+
+ /**
+ * Status code (404) indicating that the requested resource is not
+ * available.
+ */
+ public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
+
+ /**
+ * Status code (500) indicating an error inside the HTTP service which
+ * prevented it from fulfilling the request.
+ */
+ public static final int SC_INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+ /**
+ * Status code (501) indicating the HTTP service does not support the
+ * functionality needed to fulfill the request.
+ */
+ public static final int SC_NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED;
+
+ /**
+ * Status code (502) indicating that the HTTP server received an invalid
+ * response from a server it consulted when acting as a proxy or gateway.
+ */
+ public static final int SC_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY;
+
+ /**
+ * Status code (503) indicating that the HTTP service is temporarily
+ * overloaded, and unable to handle the request.
+ */
+ public static final int SC_SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+ /**
+ * Status code (100) indicating the client may continue with its request.
+ * This interim response is used to inform the client that the initial part
+ * of the request has been received and has not yet been rejected by the
+ * server.
+ */
+ public static final int SC_CONTINUE = 100;
+
+ /**
+ * Status code (405) indicating the method specified is not allowed for the
+ * resource.
+ */
+ public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+ /**
+ * Status code (409) indicating that the request could not be completed due
+ * to a conflict with the current state of the resource.
+ */
+ public static final int SC_CONFLICT = 409;
+
+ /**
+ * Status code (412) indicating the precondition given in one or more of the
+ * request-header fields evaluated to false when it was tested on the
+ * server.
+ */
+ public static final int SC_PRECONDITION_FAILED = 412;
+
+ /**
+ * Status code (413) indicating the server is refusing to process a request
+ * because the request entity is larger than the server is willing or able
+ * to process.
+ */
+ public static final int SC_REQUEST_TOO_LONG = 413;
+
+ /**
+ * Status code (415) indicating the server is refusing to service the
+ * request because the entity of the request is in a format not supported by
+ * the requested resource for the requested method.
+ */
+ public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+ // -------------------------------------------- Extended WebDav status code
+
+ /**
+ * Status code (207) indicating that the response requires providing status
+ * for multiple independent operations.
+ */
+ public static final int SC_MULTI_STATUS = 207;
+
+ // This one colides with HTTP 1.1
+ // "207 Parital Update OK"
+
+ /**
+ * Status code (418) indicating the entity body submitted with the PATCH
+ * method was not understood by the resource.
+ */
+ public static final int SC_UNPROCESSABLE_ENTITY = 418;
+
+ // This one colides with HTTP 1.1
+ // "418 Reauthentication Required"
+
+ /**
+ * Status code (419) indicating that the resource does not have sufficient
+ * space to record the state of the resource after the execution of this
+ * method.
+ */
+ public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
+
+ // This one colides with HTTP 1.1
+ // "419 Proxy Reauthentication Required"
+
+ /**
+ * Status code (420) indicating the method was not executed on a particular
+ * resource within its scope because some part of the method's execution
+ * failed causing the entire method to be aborted.
+ */
+ public static final int SC_METHOD_FAILURE = 420;
+
+ /**
+ * Status code (423) indicating the destination resource of a method is
+ * locked, and either the request did not contain a valid Lock-Info header,
+ * or the Lock-Info header identifies a lock held by another principal.
+ */
+ public static final int SC_LOCKED = 423;
+
+ // ------------------------------------------------------------ Initializer
+
+ static {
+ // HTTP 1.0 Status Code
+ addStatusCodeMap(SC_OK, "OK");
+ addStatusCodeMap(SC_CREATED, "Created");
+ addStatusCodeMap(SC_ACCEPTED, "Accepted");
+ addStatusCodeMap(SC_NO_CONTENT, "No Content");
+ addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
+ addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
+ addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
+ addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
+ addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
+ addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
+ addStatusCodeMap(SC_NOT_FOUND, "Not Found");
+ addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
+ addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
+ addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
+ addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+ addStatusCodeMap(SC_CONTINUE, "Continue");
+ addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
+ addStatusCodeMap(SC_CONFLICT, "Conflict");
+ addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
+ addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
+ addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
+ // WebDav Status Codes
+ addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
+ addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
+ addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
+ "Insufficient Space On Resource");
+ addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
+ addStatusCodeMap(SC_LOCKED, "Locked");
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Returns the HTTP status text for the HTTP or WebDav status code specified
+ * by looking it up in the static mapping. This is a static function.
+ *
+ * @param nHttpStatusCode
+ * [IN] HTTP or WebDAV status code
+ * @return A string with a short descriptive phrase for the HTTP status code
+ * (e.g., "OK").
+ */
+ public static String getStatusText(int nHttpStatusCode) {
+ Integer intKey = new Integer(nHttpStatusCode);
+
+ if (!_mapStatusCodes.containsKey(intKey)) {
+ return "";
+ } else {
+ return (String) _mapStatusCodes.get(intKey);
+ }
+ }
+
+ // -------------------------------------------------------- Private Methods
+
+ /**
+ * Adds a new status code -> status text mapping. This is a static method
+ * because the mapping is a static variable.
+ *
+ * @param nKey
+ * [IN] HTTP or WebDAV status code
+ * @param strVal
+ * [IN] HTTP status text
+ */
+ private static void addStatusCodeMap(int nKey, String strVal) {
+ _mapStatusCodes.put(new Integer(nKey), strVal);
+ }
+
+};
diff --git a/src/main/java/net/sf/webdav/exceptions/AccessDeniedException.java b/src/main/java/net/sf/webdav/exceptions/AccessDeniedException.java
new file mode 100644
index 0000000..667a528
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/AccessDeniedException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 net.sf.webdav.exceptions;
+
+public class AccessDeniedException extends WebdavException {
+
+ public AccessDeniedException() {
+ super();
+ }
+
+ public AccessDeniedException(String message) {
+ super(message);
+ }
+
+ public AccessDeniedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AccessDeniedException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/exceptions/LockFailedException.java b/src/main/java/net/sf/webdav/exceptions/LockFailedException.java
new file mode 100644
index 0000000..b6e4c0f
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/LockFailedException.java
@@ -0,0 +1,20 @@
+package net.sf.webdav.exceptions;
+
+public class LockFailedException extends WebdavException {
+
+ public LockFailedException() {
+ super();
+ }
+
+ public LockFailedException(String message) {
+ super(message);
+ }
+
+ public LockFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public LockFailedException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/exceptions/ObjectAlreadyExistsException.java b/src/main/java/net/sf/webdav/exceptions/ObjectAlreadyExistsException.java
new file mode 100644
index 0000000..d093800
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/ObjectAlreadyExistsException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 net.sf.webdav.exceptions;
+
+public class ObjectAlreadyExistsException extends WebdavException {
+
+ public ObjectAlreadyExistsException() {
+ super();
+ }
+
+ public ObjectAlreadyExistsException(String message) {
+ super(message);
+ }
+
+ public ObjectAlreadyExistsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ObjectAlreadyExistsException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/exceptions/ObjectNotFoundException.java b/src/main/java/net/sf/webdav/exceptions/ObjectNotFoundException.java
new file mode 100644
index 0000000..ae0a736
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/ObjectNotFoundException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 net.sf.webdav.exceptions;
+
+public class ObjectNotFoundException extends WebdavException {
+
+ public ObjectNotFoundException() {
+ super();
+ }
+
+ public ObjectNotFoundException(String message) {
+ super(message);
+ }
+
+ public ObjectNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ObjectNotFoundException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/exceptions/UnauthenticatedException.java b/src/main/java/net/sf/webdav/exceptions/UnauthenticatedException.java
new file mode 100644
index 0000000..462df69
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/UnauthenticatedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 net.sf.webdav.exceptions;
+
+public class UnauthenticatedException extends WebdavException {
+ private final int code;
+
+ public UnauthenticatedException(int code) {
+ super();
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/net/sf/webdav/exceptions/WebdavException.java b/src/main/java/net/sf/webdav/exceptions/WebdavException.java
new file mode 100644
index 0000000..6d8a620
--- /dev/null
+++ b/src/main/java/net/sf/webdav/exceptions/WebdavException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 net.sf.webdav.exceptions;
+
+public class WebdavException extends RuntimeException {
+
+ public WebdavException() {
+ super();
+ }
+
+ public WebdavException(String message) {
+ super(message);
+ }
+
+ public WebdavException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public WebdavException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/fromcatalina/MD5Encoder.java b/src/main/java/net/sf/webdav/fromcatalina/MD5Encoder.java
new file mode 100644
index 0000000..1e955c6
--- /dev/null
+++ b/src/main/java/net/sf/webdav/fromcatalina/MD5Encoder.java
@@ -0,0 +1,64 @@
+/*
+ * 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 net.sf.webdav.fromcatalina;
+
+/**
+ * Encode an MD5 digest into a String.
+ *
+ * The 128 bit MD5 hash is converted into a 32 character long String. Each
+ * character of the String is the hexadecimal representation of 4 bits of the
+ * digest.
+ *
+ * @author Remy Maucherat
+ * @version $Revision: 1.2 $ $Date: 2008-08-05 07:38:45 $
+ */
+
+public final class MD5Encoder {
+
+ // ----------------------------------------------------- Instance Variables
+
+ private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
+ *
+ * @param binaryData
+ * Array containing the digest
+ * @return Encoded MD5, or null if encoding failed
+ */
+ public String encode(byte[] binaryData) {
+
+ if (binaryData.length != 16)
+ return null;
+
+ char[] buffer = new char[32];
+
+ for (int i = 0; i < 16; i++) {
+ int low = (int) (binaryData[i] & 0x0f);
+ int high = (int) ((binaryData[i] & 0xf0) >> 4);
+ buffer[i * 2] = HEXADECIMAL[high];
+ buffer[i * 2 + 1] = HEXADECIMAL[low];
+ }
+
+ return new String(buffer);
+
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/fromcatalina/RequestUtil.java b/src/main/java/net/sf/webdav/fromcatalina/RequestUtil.java
new file mode 100644
index 0000000..d247e4d
--- /dev/null
+++ b/src/main/java/net/sf/webdav/fromcatalina/RequestUtil.java
@@ -0,0 +1,510 @@
+/*
+ * 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 net.sf.webdav.fromcatalina;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+
+/**
+ * General purpose request parsing and encoding utility methods.
+ *
+ * @author Craig R. McClanahan
+ * @author Tim Tye
+ * @version $Revision: 1.2 $ $Date: 2008-08-05 07:38:45 $
+ */
+
+public final class RequestUtil {
+
+ /**
+ * Encode a cookie as per RFC 2109. The resulting string can be used as the
+ * value for a Set-Cookie
header.
+ *
+ * @param cookie
+ * The cookie to encode.
+ * @return A string following RFC 2109.
+ */
+ public static String encodeCookie(Cookie cookie) {
+
+ StringBuffer buf = new StringBuffer(cookie.getName());
+ buf.append("=");
+ buf.append(cookie.getValue());
+
+ String comment = cookie.getComment();
+ if (comment != null) {
+ buf.append("; Comment=\"");
+ buf.append(comment);
+ buf.append("\"");
+ }
+
+ String domain = cookie.getDomain();
+ if (domain != null) {
+ buf.append("; Domain=\"");
+ buf.append(domain);
+ buf.append("\"");
+ }
+
+ int age = cookie.getMaxAge();
+ if (age >= 0) {
+ buf.append("; Max-Age=\"");
+ buf.append(age);
+ buf.append("\"");
+ }
+
+ String path = cookie.getPath();
+ if (path != null) {
+ buf.append("; Path=\"");
+ buf.append(path);
+ buf.append("\"");
+ }
+
+ if (cookie.getSecure()) {
+ buf.append("; Secure");
+ }
+
+ int version = cookie.getVersion();
+ if (version > 0) {
+ buf.append("; Version=\"");
+ buf.append(version);
+ buf.append("\"");
+ }
+
+ return (buf.toString());
+ }
+
+ /**
+ * Filter the specified message string for characters that are sensitive in
+ * HTML. This avoids potential attacks caused by including JavaScript codes
+ * in the request URL that is often reported in error messages.
+ *
+ * @param message
+ * The message string to be filtered
+ */
+ public static String filter(String message) {
+
+ if (message == null)
+ return (null);
+
+ char content[] = new char[message.length()];
+ message.getChars(0, message.length(), content, 0);
+ StringBuffer result = new StringBuffer(content.length + 50);
+ for (int i = 0; i < content.length; i++) {
+ switch (content[i]) {
+ case '<':
+ result.append("<");
+ break;
+ case '>':
+ result.append(">");
+ break;
+ case '&':
+ result.append("&");
+ break;
+ case '"':
+ result.append(""");
+ break;
+ default:
+ result.append(content[i]);
+ }
+ }
+ return (result.toString());
+
+ }
+
+ /**
+ * Normalize a relative URI path that may have relative values ("/./",
+ * "/../", and so on ) it it. WARNING - This method is
+ * useful only for normalizing application-generated paths. It does not try
+ * to perform security checks for malicious input.
+ *
+ * @param path
+ * Relative path to be normalized
+ */
+ public static String normalize(String path) {
+
+ if (path == null)
+ return null;
+
+ // Create a place for the normalized path
+ String normalized = path;
+
+ if (normalized.equals("/."))
+ return "/";
+
+ // Add a leading "/" if necessary
+ if (!normalized.startsWith("/"))
+ normalized = "/" + normalized;
+
+ // Resolve occurrences of "//" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("//");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index)
+ + normalized.substring(index + 1);
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/./");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index)
+ + normalized.substring(index + 2);
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/../");
+ if (index < 0)
+ break;
+ if (index == 0)
+ return (null); // Trying to go outside our context
+ int index2 = normalized.lastIndexOf('/', index - 1);
+ normalized = normalized.substring(0, index2)
+ + normalized.substring(index + 3);
+ }
+
+ // Return the normalized path that we have completed
+ return (normalized);
+
+ }
+
+ /**
+ * Parse the character encoding from the specified content type header. If
+ * the content type is null, or there is no explicit character encoding,
+ * null
is returned.
+ *
+ * @param contentType
+ * a content type header
+ */
+ public static String parseCharacterEncoding(String contentType) {
+
+ if (contentType == null)
+ return (null);
+ int start = contentType.indexOf("charset=");
+ if (start < 0)
+ return (null);
+ String encoding = contentType.substring(start + 8);
+ int end = encoding.indexOf(';');
+ if (end >= 0)
+ encoding = encoding.substring(0, end);
+ encoding = encoding.trim();
+ if ((encoding.length() > 2) && (encoding.startsWith("\""))
+ && (encoding.endsWith("\"")))
+ encoding = encoding.substring(1, encoding.length() - 1);
+ return (encoding.trim());
+
+ }
+
+ /**
+ * Parse a cookie header into an array of cookies according to RFC 2109.
+ *
+ * @param header
+ * Value of an HTTP "Cookie" header
+ */
+ public static Cookie[] parseCookieHeader(String header) {
+
+ if ((header == null) || (header.length() < 1))
+ return (new Cookie[0]);
+
+ ArrayList cookies = new ArrayList();
+ while (header.length() > 0) {
+ int semicolon = header.indexOf(';');
+ if (semicolon < 0)
+ semicolon = header.length();
+ if (semicolon == 0)
+ break;
+ String token = header.substring(0, semicolon);
+ if (semicolon < header.length())
+ header = header.substring(semicolon + 1);
+ else
+ header = "";
+ try {
+ int equals = token.indexOf('=');
+ if (equals > 0) {
+ String name = token.substring(0, equals).trim();
+ String value = token.substring(equals + 1).trim();
+ cookies.add(new Cookie(name, value));
+ }
+ } catch (Throwable e) {
+ ;
+ }
+ }
+
+ return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
+
+ }
+
+ /**
+ * Append request parameters from the specified String to the specified Map.
+ * It is presumed that the specified Map is not accessed from any other
+ * thread, so no synchronization is performed.
+ *
+ * IMPLEMENTATION NOTE: URL decoding is performed
+ * individually on the parsed name and value elements, rather than on the
+ * entire query string ahead of time, to properly deal with the case where
+ * the name or value includes an encoded "=" or "&" character that would
+ * otherwise be interpreted as a delimiter.
+ *
+ * @param map
+ * Map that accumulates the resulting parameters
+ * @param data
+ * Input string containing request parameters
+ *
+ * @exception IllegalArgumentException
+ * if the data is malformed
+ */
+ public static void parseParameters(Map map, String data,
+ String encoding) throws UnsupportedEncodingException {
+
+ if ((data != null) && (data.length() > 0)) {
+
+ // use the specified encoding to extract bytes out of the
+ // given string so that the encoding is not lost. If an
+ // encoding is not specified, let it use platform default
+ byte[] bytes = null;
+ try {
+ if (encoding == null) {
+ bytes = data.getBytes();
+ } else {
+ bytes = data.getBytes(encoding);
+ }
+ } catch (UnsupportedEncodingException uee) {
+ }
+
+ parseParameters(map, bytes, encoding);
+ }
+
+ }
+
+ /**
+ * Decode and return the specified URL-encoded String. When the byte array
+ * is converted to a string, the system default character encoding is
+ * used... This may be different than some other servers.
+ *
+ * @param str
+ * The url-encoded string
+ *
+ * @exception IllegalArgumentException
+ * if a '%' character is not followed by a valid 2-digit hexadecimal
+ * number
+ */
+ public static String URLDecode(String str) {
+
+ return URLDecode(str, null);
+
+ }
+
+ /**
+ * Decode and return the specified URL-encoded String.
+ *
+ * @param str
+ * The url-encoded string
+ * @param enc
+ * The encoding to use; if null, the default encoding is used
+ * @exception IllegalArgumentException
+ * if a '%' character is not followed by a valid 2-digit hexadecimal
+ * number
+ */
+ public static String URLDecode(String str, String enc) {
+
+ if (str == null)
+ return (null);
+
+ // use the specified encoding to extract bytes out of the
+ // given string so that the encoding is not lost. If an
+ // encoding is not specified, let it use platform default
+ byte[] bytes = null;
+ try {
+ if (enc == null) {
+ bytes = str.getBytes();
+ } else {
+ bytes = str.getBytes(enc);
+ }
+ } catch (UnsupportedEncodingException uee) {
+ }
+
+ return URLDecode(bytes, enc);
+
+ }
+
+ /**
+ * Decode and return the specified URL-encoded byte array.
+ *
+ * @param bytes
+ * The url-encoded byte array
+ * @exception IllegalArgumentException
+ * if a '%' character is not followed by a valid 2-digit hexadecimal
+ * number
+ */
+ public static String URLDecode(byte[] bytes) {
+ return URLDecode(bytes, null);
+ }
+
+ /**
+ * Decode and return the specified URL-encoded byte array.
+ *
+ * @param bytes
+ * The url-encoded byte array
+ * @param enc
+ * The encoding to use; if null, the default encoding is used
+ * @exception IllegalArgumentException
+ * if a '%' character is not followed by a valid 2-digit hexadecimal
+ * number
+ */
+ public static String URLDecode(byte[] bytes, String enc) {
+
+ if (bytes == null)
+ return (null);
+
+ int len = bytes.length;
+ int ix = 0;
+ int ox = 0;
+ while (ix < len) {
+ byte b = bytes[ix++]; // Get byte to test
+ if (b == '+') {
+ b = (byte) ' ';
+ } else if (b == '%') {
+ b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + convertHexDigit(bytes[ix++]));
+ }
+ bytes[ox++] = b;
+ }
+ if (enc != null) {
+ try {
+ return new String(bytes, 0, ox, enc);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return new String(bytes, 0, ox);
+
+ }
+
+ /**
+ * Convert a byte character value to hexidecimal digit value.
+ *
+ * @param b
+ * the character value byte
+ */
+ private static byte convertHexDigit(byte b) {
+ if ((b >= '0') && (b <= '9'))
+ return (byte) (b - '0');
+ if ((b >= 'a') && (b <= 'f'))
+ return (byte) (b - 'a' + 10);
+ if ((b >= 'A') && (b <= 'F'))
+ return (byte) (b - 'A' + 10);
+ return 0;
+ }
+
+ /**
+ * Put name and value pair in map. When name already exist, add value to
+ * array of values.
+ *
+ * @param map
+ * The map to populate
+ * @param name
+ * The parameter name
+ * @param value
+ * The parameter value
+ */
+ private static void putMapEntry(Map map, String name,
+ String value) {
+ String[] newValues = null;
+ String[] oldValues = (String[]) map.get(name);
+ if (oldValues == null) {
+ newValues = new String[1];
+ newValues[0] = value;
+ } else {
+ newValues = new String[oldValues.length + 1];
+ System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
+ newValues[oldValues.length] = value;
+ }
+ map.put(name, newValues);
+ }
+
+ /**
+ * Append request parameters from the specified String to the specified Map.
+ * It is presumed that the specified Map is not accessed from any other
+ * thread, so no synchronization is performed.
+ *
+ * IMPLEMENTATION NOTE: URL decoding is performed
+ * individually on the parsed name and value elements, rather than on the
+ * entire query string ahead of time, to properly deal with the case where
+ * the name or value includes an encoded "=" or "&" character that would
+ * otherwise be interpreted as a delimiter. NOTE: byte array data is
+ * modified by this method. Caller beware.
+ *
+ * @param map
+ * Map that accumulates the resulting parameters
+ * @param data
+ * Input string containing request parameters
+ * @param encoding
+ * Encoding to use for converting hex
+ *
+ * @exception UnsupportedEncodingException
+ * if the data is malformed
+ */
+ public static void parseParameters(Map map, byte[] data,
+ String encoding) throws UnsupportedEncodingException {
+
+ if (data != null && data.length > 0) {
+ int ix = 0;
+ int ox = 0;
+ String key = null;
+ String value = null;
+ while (ix < data.length) {
+ byte c = data[ix++];
+ switch ((char) c) {
+ case '&':
+ value = new String(data, 0, ox, encoding);
+ if (key != null) {
+ putMapEntry(map, key, value);
+ key = null;
+ }
+ ox = 0;
+ break;
+ case '=':
+ if (key == null) {
+ key = new String(data, 0, ox, encoding);
+ ox = 0;
+ } else {
+ data[ox++] = c;
+ }
+ break;
+ case '+':
+ data[ox++] = (byte) ' ';
+ break;
+ case '%':
+ data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++]));
+ break;
+ default:
+ data[ox++] = c;
+ }
+ }
+ // The last value does not end in '&'. So save it now.
+ if (key != null) {
+ value = new String(data, 0, ox, encoding);
+ putMapEntry(map, key, value);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/fromcatalina/URLEncoder.java b/src/main/java/net/sf/webdav/fromcatalina/URLEncoder.java
new file mode 100644
index 0000000..2047b01
--- /dev/null
+++ b/src/main/java/net/sf/webdav/fromcatalina/URLEncoder.java
@@ -0,0 +1,107 @@
+/*
+ * 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 net.sf.webdav.fromcatalina;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.BitSet;
+
+/**
+ *
+ * This class is very similar to the java.net.URLEncoder class.
+ *
+ * Unfortunately, with java.net.URLEncoder there is no way to specify to the
+ * java.net.URLEncoder which characters should NOT be encoded.
+ *
+ * This code was moved from DefaultServlet.java
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class URLEncoder
+{
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(URLEncoder.class);
+
+
+ protected static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ // Array containing the safe characters set.
+ protected BitSet _safeCharacters = new BitSet(256);
+
+ public URLEncoder() {
+ for (char i = 'a'; i <= 'z'; i++) {
+ addSafeCharacter(i);
+ }
+ for (char i = 'A'; i <= 'Z'; i++) {
+ addSafeCharacter(i);
+ }
+ for (char i = '0'; i <= '9'; i++) {
+ addSafeCharacter(i);
+ }
+ for(char c : "$-_.+!*'(),".toCharArray()){
+ addSafeCharacter(c);
+ }
+ }
+
+ public void addSafeCharacter(char c) {
+ _safeCharacters.set(c);
+ }
+
+ public String encode(String path) {
+ int maxBytesPerChar = 10;
+ // int caseDiff = ('a' - 'A');
+ StringBuffer rewrittenPath = new StringBuffer(path.length());
+ ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+ OutputStreamWriter writer = null;
+ try {
+ writer = new OutputStreamWriter(buf, "UTF8");
+ } catch (Exception e) {
+ LOG.error("Error in encode <"+path+">", e);
+ writer = new OutputStreamWriter(buf);
+ }
+
+ for (int i = 0; i < path.length(); i++) {
+ int c = (int) path.charAt(i);
+ if (_safeCharacters.get(c)) {
+ rewrittenPath.append((char) c);
+ } else {
+ // convert to external encoding before hex conversion
+ try {
+ writer.write((char) c);
+ writer.flush();
+ } catch (IOException e) {
+ buf.reset();
+ continue;
+ }
+ byte[] ba = buf.toByteArray();
+ for (int j = 0; j < ba.length; j++) {
+ // Converting each byte in the buffer
+ byte toEncode = ba[j];
+ rewrittenPath.append('%');
+ int low = (int) (toEncode & 0x0f);
+ int high = (int) ((toEncode & 0xf0) >> 4);
+ rewrittenPath.append(HEXADECIMAL[high]);
+ rewrittenPath.append(HEXADECIMAL[low]);
+ }
+ buf.reset();
+ }
+ }
+ return rewrittenPath.toString();
+ }
+}
diff --git a/src/main/java/net/sf/webdav/fromcatalina/XMLHelper.java b/src/main/java/net/sf/webdav/fromcatalina/XMLHelper.java
new file mode 100644
index 0000000..0ff6159
--- /dev/null
+++ b/src/main/java/net/sf/webdav/fromcatalina/XMLHelper.java
@@ -0,0 +1,42 @@
+package net.sf.webdav.fromcatalina;
+
+import java.util.Vector;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class XMLHelper {
+
+ public static Node findSubElement(Node parent, String localName) {
+ if (parent == null) {
+ return null;
+ }
+ Node child = parent.getFirstChild();
+ while (child != null) {
+ if ((child.getNodeType() == Node.ELEMENT_NODE)
+ && (child.getLocalName().equals(localName))) {
+ return child;
+ }
+ child = child.getNextSibling();
+ }
+ return null;
+ }
+
+ public static Vector getPropertiesFromXML(Node propNode) {
+ Vector properties;
+ properties = new Vector();
+ NodeList childList = propNode.getChildNodes();
+
+ for (int i = 0; i < childList.getLength(); i++) {
+ Node currentNode = childList.item(i);
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = currentNode.getLocalName();
+ String namespace = currentNode.getNamespaceURI();
+ // href is a live property which is handled differently
+ properties.addElement(namespace + ":" + nodeName);
+ }
+ }
+ return properties;
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/fromcatalina/XMLWriter.java b/src/main/java/net/sf/webdav/fromcatalina/XMLWriter.java
new file mode 100644
index 0000000..1558fcc
--- /dev/null
+++ b/src/main/java/net/sf/webdav/fromcatalina/XMLWriter.java
@@ -0,0 +1,214 @@
+/*
+ * 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 net.sf.webdav.fromcatalina;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * XMLWriter helper class.
+ *
+ * @author Remy Maucherat
+ */
+public class XMLWriter {
+
+ // -------------------------------------------------------------- Constants
+
+ /**
+ * Opening tag.
+ */
+ public static final int OPENING = 0;
+
+ /**
+ * Closing tag.
+ */
+ public static final int CLOSING = 1;
+
+ /**
+ * Element with no content.
+ */
+ public static final int NO_CONTENT = 2;
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * Buffer.
+ */
+ protected StringBuffer _buffer = new StringBuffer();
+
+ /**
+ * Writer.
+ */
+ protected Writer _writer = null;
+
+ /**
+ * Namespaces to be declared in the root element
+ */
+ protected Map _namespaces;
+
+ /**
+ * Is true until the root element is written
+ */
+ protected boolean _isRootElement = true;
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Constructor.
+ */
+ public XMLWriter(Map namespaces) {
+ _namespaces = namespaces;
+ }
+
+ /**
+ * Constructor.
+ */
+ public XMLWriter(Writer writer, Map namespaces) {
+ _writer = writer;
+ _namespaces = namespaces;
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Retrieve generated XML.
+ *
+ * @return String containing the generated XML
+ */
+ public String toString() {
+ return _buffer.toString();
+ }
+
+ /**
+ * Write property to the XML.
+ *
+ * @param name
+ * Property name
+ * @param value
+ * Property value
+ */
+ public void writeProperty(String name, String value) {
+ writeElement(name, OPENING);
+ _buffer.append(value);
+ writeElement(name, CLOSING);
+ }
+
+ /**
+ * Write property to the XML.
+ *
+ * @param name
+ * Property name
+ */
+ public void writeProperty(String name) {
+ writeElement(name, NO_CONTENT);
+ }
+
+ /**
+ * Write an element.
+ *
+ * @param name
+ * Element name
+ * @param type
+ * Element type
+ */
+ public void writeElement(String name, int type) {
+ StringBuffer nsdecl = new StringBuffer();
+
+ if (_isRootElement) {
+ for (Iterator iter = _namespaces.keySet().iterator(); iter
+ .hasNext();) {
+ String fullName = (String) iter.next();
+ String abbrev = (String) _namespaces.get(fullName);
+ nsdecl.append(" xmlns:").append(abbrev).append("=\"").append(
+ fullName).append("\"");
+ }
+ _isRootElement = false;
+ }
+
+ int pos = name.lastIndexOf(':');
+ if (pos >= 0) {
+ // lookup prefix for namespace
+ String fullns = name.substring(0, pos);
+ String prefix = (String) _namespaces.get(fullns);
+ if (prefix == null) {
+ // there is no prefix for this namespace
+ name = name.substring(pos + 1);
+ nsdecl.append(" xmlns=\"").append(fullns).append("\"");
+ } else {
+ // there is a prefix
+ name = prefix + ":" + name.substring(pos + 1);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "All XML elements must have a namespace");
+ }
+
+ switch (type) {
+ case OPENING:
+ _buffer.append("<" + name + nsdecl + ">");
+ break;
+ case CLOSING:
+ _buffer.append("" + name + ">\n");
+ break;
+ case NO_CONTENT:
+ default:
+ _buffer.append("<" + name + nsdecl + "/>");
+ break;
+ }
+ }
+
+ /**
+ * Write text.
+ *
+ * @param text
+ * Text to append
+ */
+ public void writeText(String text) {
+ _buffer.append(text);
+ }
+
+ /**
+ * Write data.
+ *
+ * @param data
+ * Data to append
+ */
+ public void writeData(String data) {
+ _buffer.append("");
+ }
+
+ /**
+ * Write XML Header.
+ */
+ public void writeXMLHeader() {
+ _buffer.append("\n");
+ }
+
+ /**
+ * Send data and reinitializes buffer.
+ */
+ public void sendData() throws IOException {
+ if (_writer != null) {
+ _writer.write(_buffer.toString());
+ _writer.flush();
+ _buffer = new StringBuffer();
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/locking/IResourceLocks.java b/src/main/java/net/sf/webdav/locking/IResourceLocks.java
new file mode 100644
index 0000000..24743b0
--- /dev/null
+++ b/src/main/java/net/sf/webdav/locking/IResourceLocks.java
@@ -0,0 +1,144 @@
+package net.sf.webdav.locking;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.exceptions.LockFailedException;
+
+public interface IResourceLocks {
+
+ /**
+ * Tries to lock the resource at "path".
+ *
+ * @param transaction
+ * @param path
+ * what resource to lock
+ * @param owner
+ * the owner of the lock
+ * @param exclusive
+ * if the lock should be exclusive (or shared)
+ * @param depth
+ * depth
+ * @param timeout
+ * Lock Duration in seconds.
+ * @return true if the resource at path was successfully locked, false if an
+ * existing lock prevented this
+ * @throws LockFailedException
+ */
+ boolean lock(ITransaction transaction, String path, String owner,
+ boolean exclusive, int depth, int timeout, boolean temporary)
+ throws LockFailedException;
+
+ /**
+ * Unlocks all resources at "path" (and all subfolders if existing) that
+ * have the same owner.
+ *
+ * @param transaction
+ * @param id
+ * id to the resource to unlock
+ * @param owner
+ * who wants to unlock
+ */
+ boolean unlock(ITransaction transaction, String id, String owner);
+
+ /**
+ * Unlocks all resources at "path" (and all subfolders if existing) that
+ * have the same owner.
+ *
+ * @param transaction
+ * @param path
+ * what resource to unlock
+ * @param owner
+ * who wants to unlock
+ */
+ void unlockTemporaryLockedObjects(ITransaction transaction, String path,
+ String owner);
+
+ /**
+ * Deletes LockedObjects, where timeout has reached.
+ *
+ * @param transaction
+ * @param temporary
+ * Check timeout on temporary or real locks
+ */
+ void checkTimeouts(ITransaction transaction, boolean temporary);
+
+ /**
+ * Tries to lock the resource at "path" exclusively.
+ *
+ * @param transaction
+ * Transaction
+ * @param path
+ * what resource to lock
+ * @param owner
+ * the owner of the lock
+ * @param depth
+ * depth
+ * @param timeout
+ * Lock Duration in seconds.
+ * @return true if the resource at path was successfully locked, false if an
+ * existing lock prevented this
+ * @throws LockFailedException
+ */
+ boolean exclusiveLock(ITransaction transaction, String path, String owner,
+ int depth, int timeout) throws LockFailedException;
+
+ /**
+ * Tries to lock the resource at "path" shared.
+ *
+ * @param transaction
+ * Transaction
+ * @param path
+ * what resource to lock
+ * @param owner
+ * the owner of the lock
+ * @param depth
+ * depth
+ * @param timeout
+ * Lock Duration in seconds.
+ * @return true if the resource at path was successfully locked, false if an
+ * existing lock prevented this
+ * @throws LockFailedException
+ */
+ boolean sharedLock(ITransaction transaction, String path, String owner,
+ int depth, int timeout) throws LockFailedException;
+
+ /**
+ * Gets the LockedObject corresponding to specified id.
+ *
+ * @param transaction
+ * @param id
+ * LockToken to requested resource
+ * @return LockedObject or null if no LockedObject on specified path exists
+ */
+ LockedObject getLockedObjectByID(ITransaction transaction, String id);
+
+ /**
+ * Gets the LockedObject on specified path.
+ *
+ * @param transaction
+ * @param path
+ * Path to requested resource
+ * @return LockedObject or null if no LockedObject on specified path exists
+ */
+ LockedObject getLockedObjectByPath(ITransaction transaction, String path);
+
+ /**
+ * Gets the LockedObject corresponding to specified id (locktoken).
+ *
+ * @param transaction
+ * @param id
+ * LockToken to requested resource
+ * @return LockedObject or null if no LockedObject on specified path exists
+ */
+ LockedObject getTempLockedObjectByID(ITransaction transaction, String id);
+
+ /**
+ * Gets the LockedObject on specified path.
+ *
+ * @param transaction
+ * @param path
+ * Path to requested resource
+ * @return LockedObject or null if no LockedObject on specified path exists
+ */
+ LockedObject getTempLockedObjectByPath(ITransaction transaction, String path);
+
+}
diff --git a/src/main/java/net/sf/webdav/locking/LockedObject.java b/src/main/java/net/sf/webdav/locking/LockedObject.java
new file mode 100644
index 0000000..2dda386
--- /dev/null
+++ b/src/main/java/net/sf/webdav/locking/LockedObject.java
@@ -0,0 +1,422 @@
+package net.sf.webdav.locking;
+
+import java.util.UUID;
+
+/**
+ * a helper class for ResourceLocks, represents the Locks
+ *
+ * @author re
+ *
+ */
+public class LockedObject {
+
+ private ResourceLocks _resourceLocks;
+
+ private String _path;
+
+ private String _id;
+
+ /**
+ * Describing the depth of a locked collection. If the locked resource is
+ * not a collection, depth is 0 / doesn't matter.
+ */
+ protected int _lockDepth;
+
+ /**
+ * Describing the timeout of a locked object (ms)
+ */
+ protected long _expiresAt;
+
+ /**
+ * owner of the lock. shared locks can have multiple owners. is null if no
+ * owner is present
+ */
+ // protected String[] _owner = null;
+ protected String[] _owner = null;
+
+ /**
+ * children of that lock
+ */
+ protected LockedObject[] _children = null;
+
+ protected LockedObject _parent = null;
+
+ /**
+ * weather the lock is exclusive or not. if owner=null the exclusive value
+ * doesn't matter
+ */
+ protected boolean _exclusive = false;
+
+ /**
+ * weather the lock is a write or read lock
+ */
+ protected String _type = null;
+
+ /**
+ * @param _resourceLocks
+ * the resourceLocks where locks are stored
+ * @param path
+ * the path to the locked object
+ * @param temporary
+ * indicates if the LockedObject should be temporary or not
+ */
+ public LockedObject(ResourceLocks resLocks, String path, boolean temporary) {
+ _path = path;
+ _id = UUID.randomUUID().toString();
+ _resourceLocks = resLocks;
+
+ if (!temporary) {
+ _resourceLocks._locks.put(path, this);
+ _resourceLocks._locksByID.put(_id, this);
+ } else {
+ _resourceLocks._tempLocks.put(path, this);
+ _resourceLocks._tempLocksByID.put(_id, this);
+ }
+ _resourceLocks._cleanupCounter++;
+ }
+
+ /**
+ * adds a new owner to a lock
+ *
+ * @param owner
+ * string that represents the owner
+ * @return true if the owner was added, false otherwise
+ */
+ public boolean addLockedObjectOwner(String owner) {
+
+ if (_owner == null) {
+ _owner = new String[1];
+ } else {
+
+ int size = _owner.length;
+ String[] newLockObjectOwner = new String[size + 1];
+
+ // check if the owner is already here (that should actually not
+ // happen)
+ for (int i = 0; i < size; i++) {
+ if (_owner[i].equals(owner)) {
+ return false;
+ }
+ }
+
+ System.arraycopy(_owner, 0, newLockObjectOwner, 0, size);
+ _owner = newLockObjectOwner;
+ }
+
+ _owner[_owner.length - 1] = owner;
+ return true;
+ }
+
+ /**
+ * tries to remove the owner from the lock
+ *
+ * @param owner
+ * string that represents the owner
+ */
+ public void removeLockedObjectOwner(String owner) {
+
+ try {
+ if (_owner != null) {
+ int size = _owner.length;
+ for (int i = 0; i < size; i++) {
+ // check every owner if it is the requested one
+ if (_owner[i].equals(owner)) {
+ // remove the owner
+ size -= 1;
+ String[] newLockedObjectOwner = new String[size];
+ for (int j = 0; j < size; j++) {
+ if (j < i) {
+ newLockedObjectOwner[j] = _owner[j];
+ } else {
+ newLockedObjectOwner[j] = _owner[j + 1];
+ }
+ }
+ _owner = newLockedObjectOwner;
+
+ }
+ }
+ if (_owner.length == 0) {
+ _owner = null;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("LockedObject.removeLockedObjectOwner()");
+ System.out.println(e.toString());
+ }
+ }
+
+ /**
+ * adds a new child lock to this lock
+ *
+ * @param newChild
+ * new child
+ */
+ public void addChild(LockedObject newChild) {
+ if (_children == null) {
+ _children = new LockedObject[0];
+ }
+ int size = _children.length;
+ LockedObject[] newChildren = new LockedObject[size + 1];
+ System.arraycopy(_children, 0, newChildren, 0, size);
+ newChildren[size] = newChild;
+ _children = newChildren;
+ }
+
+ /**
+ * deletes this Lock object. assumes that it has no children and no owners
+ * (does not check this itself)
+ *
+ */
+ public void removeLockedObject() {
+ if (this != _resourceLocks._root && !this.getPath().equals("/")) {
+
+ int size = _parent._children.length;
+ for (int i = 0; i < size; i++) {
+ if (_parent._children[i].equals(this)) {
+ LockedObject[] newChildren = new LockedObject[size - 1];
+ for (int i2 = 0; i2 < (size - 1); i2++) {
+ if (i2 < i) {
+ newChildren[i2] = _parent._children[i2];
+ } else {
+ newChildren[i2] = _parent._children[i2 + 1];
+ }
+ }
+ if (newChildren.length != 0) {
+ _parent._children = newChildren;
+ } else {
+ _parent._children = null;
+ }
+ break;
+ }
+ }
+
+ // removing from hashtable
+ _resourceLocks._locksByID.remove(getID());
+ _resourceLocks._locks.remove(getPath());
+
+ // now the garbage collector has some work to do
+ }
+ }
+
+ /**
+ * deletes this Lock object. assumes that it has no children and no owners
+ * (does not check this itself)
+ *
+ */
+ public void removeTempLockedObject() {
+ if (this != _resourceLocks._tempRoot) {
+ // removing from tree
+ if (_parent != null && _parent._children != null) {
+ int size = _parent._children.length;
+ for (int i = 0; i < size; i++) {
+ if (_parent._children[i].equals(this)) {
+ LockedObject[] newChildren = new LockedObject[size - 1];
+ for (int i2 = 0; i2 < (size - 1); i2++) {
+ if (i2 < i) {
+ newChildren[i2] = _parent._children[i2];
+ } else {
+ newChildren[i2] = _parent._children[i2 + 1];
+ }
+ }
+ if (newChildren.length != 0) {
+ _parent._children = newChildren;
+ } else {
+ _parent._children = null;
+ }
+ break;
+ }
+ }
+
+ // removing from hashtable
+ _resourceLocks._tempLocksByID.remove(getID());
+ _resourceLocks._tempLocks.remove(getPath());
+
+ // now the garbage collector has some work to do
+ }
+ }
+ }
+
+ /**
+ * checks if a lock of the given exclusivity can be placed, only considering
+ * children up to "depth"
+ *
+ * @param exclusive
+ * wheather the new lock should be exclusive
+ * @param depth
+ * the depth to which should be checked
+ * @return true if the lock can be placed
+ */
+ public boolean checkLocks(boolean exclusive, int depth) {
+ if (checkParents(exclusive) && checkChildren(exclusive, depth)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * helper of checkLocks(). looks if the parents are locked
+ *
+ * @param exclusive
+ * wheather the new lock should be exclusive
+ * @return true if no locks at the parent path are forbidding a new lock
+ */
+ private boolean checkParents(boolean exclusive) {
+ if (_path.equals("/")) {
+ return true;
+ } else {
+ if (_owner == null) {
+ // no owner, checking parents
+ return _parent != null && _parent.checkParents(exclusive);
+ } else {
+ // there already is a owner
+ return !(_exclusive || exclusive)
+ && _parent.checkParents(exclusive);
+ }
+ }
+ }
+
+ /**
+ * helper of checkLocks(). looks if the children are locked
+ *
+ * @param exclusive
+ * wheather the new lock should be exclusive
+ * @return true if no locks at the children paths are forbidding a new lock
+ * @param depth
+ * depth
+ */
+ private boolean checkChildren(boolean exclusive, int depth) {
+ if (_children == null) {
+ // a file
+
+ return _owner == null || !(_exclusive || exclusive);
+ } else {
+ // a folder
+
+ if (_owner == null) {
+ // no owner, checking children
+
+ if (depth != 0) {
+ boolean canLock = true;
+ int limit = _children.length;
+ for (int i = 0; i < limit; i++) {
+ if (!_children[i].checkChildren(exclusive, depth - 1)) {
+ canLock = false;
+ }
+ }
+ return canLock;
+ } else {
+ // depth == 0 -> we don't care for children
+ return true;
+ }
+ } else {
+ // there already is a owner
+ return !(_exclusive || exclusive);
+ }
+ }
+
+ }
+
+ /**
+ * Sets a new timeout for the LockedObject
+ *
+ * @param timeout
+ */
+ public void refreshTimeout(int timeout) {
+ _expiresAt = System.currentTimeMillis() + (timeout * 1000);
+ }
+
+ /**
+ * Gets the timeout for the LockedObject
+ *
+ * @return timeout
+ */
+ public long getTimeoutMillis() {
+ return (_expiresAt - System.currentTimeMillis());
+ }
+
+ /**
+ * Return true if the lock has expired.
+ *
+ * @return true if timeout has passed
+ */
+ public boolean hasExpired() {
+ if (_expiresAt != 0) {
+ return (System.currentTimeMillis() > _expiresAt);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gets the LockID (locktoken) for the LockedObject
+ *
+ * @return locktoken
+ */
+ public String getID() {
+ return _id;
+ }
+
+ /**
+ * Gets the owners for the LockedObject
+ *
+ * @return owners
+ */
+ public String[] getOwner() {
+ return _owner;
+ }
+
+ /**
+ * Gets the path for the LockedObject
+ *
+ * @return path
+ */
+ public String getPath() {
+ return _path;
+ }
+
+ /**
+ * Sets the exclusivity for the LockedObject
+ *
+ * @param exclusive
+ */
+ public void setExclusive(boolean exclusive) {
+ _exclusive = exclusive;
+ }
+
+ /**
+ * Gets the exclusivity for the LockedObject
+ *
+ * @return exclusivity
+ */
+ public boolean isExclusive() {
+ return _exclusive;
+ }
+
+ /**
+ * Gets the exclusivity for the LockedObject
+ *
+ * @return exclusivity
+ */
+ public boolean isShared() {
+ return !_exclusive;
+ }
+
+ /**
+ * Gets the type of the lock
+ *
+ * @return type
+ */
+ public String getType() {
+ return _type;
+ }
+
+ /**
+ * Gets the depth of the lock
+ *
+ * @return depth
+ */
+ public int getLockDepth() {
+ return _lockDepth;
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/locking/ResourceLocks.java b/src/main/java/net/sf/webdav/locking/ResourceLocks.java
new file mode 100644
index 0000000..1463403
--- /dev/null
+++ b/src/main/java/net/sf/webdav/locking/ResourceLocks.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2005-2006 webdav-servlet group.
+ *
+ * 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 net.sf.webdav.locking;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.exceptions.LockFailedException;
+
+/**
+ * simple locking management for concurrent data access, NOT the webdav locking.
+ * ( could that be used instead? )
+ *
+ * IT IS ACTUALLY USED FOR DOLOCK
+ *
+ * @author re
+ */
+public class ResourceLocks implements IResourceLocks {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(ResourceLocks.class);
+
+ /**
+ * after creating this much LockedObjects, a cleanup deletes unused
+ * LockedObjects
+ */
+ private final int _cleanupLimit = 100000;
+
+ protected int _cleanupCounter = 0;
+
+ /**
+ * keys: path value: LockedObject from that path
+ */
+ protected Hashtable _locks = new Hashtable();
+
+ /**
+ * keys: id value: LockedObject from that id
+ */
+ protected Hashtable _locksByID = new Hashtable();
+
+ /**
+ * keys: path value: Temporary LockedObject from that path
+ */
+ protected Hashtable _tempLocks = new Hashtable();
+
+ /**
+ * keys: id value: Temporary LockedObject from that id
+ */
+ protected Hashtable _tempLocksByID = new Hashtable();
+
+ // REMEMBER TO REMOVE UNUSED LOCKS FROM THE HASHTABLE AS WELL
+
+ protected LockedObject _root = null;
+
+ protected LockedObject _tempRoot = null;
+
+ private boolean _temporary = true;
+
+ public ResourceLocks() {
+ _root = new LockedObject(this, "/", true);
+ _tempRoot = new LockedObject(this, "/", false);
+ }
+
+ public synchronized boolean lock(ITransaction transaction, String path,
+ String owner, boolean exclusive, int depth, int timeout,
+ boolean temporary) throws LockFailedException {
+
+ LockedObject lo = null;
+
+ if (temporary) {
+ lo = generateTempLockedObjects(transaction, path);
+ lo._type = "read";
+ } else {
+ lo = generateLockedObjects(transaction, path);
+ lo._type = "write";
+ }
+
+ if (lo.checkLocks(exclusive, depth)) {
+
+ lo._exclusive = exclusive;
+ lo._lockDepth = depth;
+ lo._expiresAt = System.currentTimeMillis() + (timeout * 1000);
+ if (lo._parent != null) {
+ lo._parent._expiresAt = lo._expiresAt;
+ if (lo._parent.equals(_root)) {
+ LockedObject rootLo = getLockedObjectByPath(transaction,
+ _root.getPath());
+ rootLo._expiresAt = lo._expiresAt;
+ } else if (lo._parent.equals(_tempRoot)) {
+ LockedObject tempRootLo = getTempLockedObjectByPath(
+ transaction, _tempRoot.getPath());
+ tempRootLo._expiresAt = lo._expiresAt;
+ }
+ }
+ if (lo.addLockedObjectOwner(owner)) {
+ return true;
+ } else {
+ LOG.trace("Couldn't set owner \"" + owner
+ + "\" to resource at '" + path + "'");
+ return false;
+ }
+ } else {
+ // can not lock
+ LOG.trace("Lock resource at " + path + " failed because"
+ + "\na parent or child resource is currently locked");
+ return false;
+ }
+ }
+
+ public synchronized boolean unlock(ITransaction transaction, String id,
+ String owner) {
+
+ if (_locksByID.containsKey(id)) {
+ String path = _locksByID.get(id).getPath();
+ if (_locks.containsKey(path)) {
+ LockedObject lo = _locks.get(path);
+ lo.removeLockedObjectOwner(owner);
+
+ if (lo._children == null && lo._owner == null)
+ lo.removeLockedObject();
+
+ } else {
+ // there is no lock at that path. someone tried to unlock it
+ // anyway. could point to a problem
+ LOG
+ .trace("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path "
+ + path);
+ return false;
+ }
+
+ if (_cleanupCounter > _cleanupLimit) {
+ _cleanupCounter = 0;
+ cleanLockedObjects(transaction, _root, !_temporary);
+ }
+ }
+ checkTimeouts(transaction, !_temporary);
+
+ return true;
+
+ }
+
+ public synchronized void unlockTemporaryLockedObjects(
+ ITransaction transaction, String path, String owner) {
+ if (_tempLocks.containsKey(path)) {
+ LockedObject lo = _tempLocks.get(path);
+ lo.removeLockedObjectOwner(owner);
+
+ } else {
+ // there is no lock at that path. someone tried to unlock it
+ // anyway. could point to a problem
+ LOG
+ .trace("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path "
+ + path);
+ }
+
+ if (_cleanupCounter > _cleanupLimit) {
+ _cleanupCounter = 0;
+ cleanLockedObjects(transaction, _tempRoot, _temporary);
+ }
+
+ checkTimeouts(transaction, _temporary);
+
+ }
+
+ public void checkTimeouts(ITransaction transaction, boolean temporary) {
+ if (!temporary) {
+ Enumeration lockedObjects = _locks.elements();
+ while (lockedObjects.hasMoreElements()) {
+ LockedObject currentLockedObject = lockedObjects.nextElement();
+
+ if (currentLockedObject._expiresAt < System.currentTimeMillis()) {
+ currentLockedObject.removeLockedObject();
+ }
+ }
+ } else {
+ Enumeration lockedObjects = _tempLocks.elements();
+ while (lockedObjects.hasMoreElements()) {
+ LockedObject currentLockedObject = lockedObjects.nextElement();
+
+ if (currentLockedObject._expiresAt < System.currentTimeMillis()) {
+ currentLockedObject.removeTempLockedObject();
+ }
+ }
+ }
+
+ }
+
+ public boolean exclusiveLock(ITransaction transaction, String path,
+ String owner, int depth, int timeout) throws LockFailedException {
+ return lock(transaction, path, owner, true, depth, timeout, false);
+ }
+
+ public boolean sharedLock(ITransaction transaction, String path,
+ String owner, int depth, int timeout) throws LockFailedException {
+ return lock(transaction, path, owner, false, depth, timeout, false);
+ }
+
+ public LockedObject getLockedObjectByID(ITransaction transaction, String id) {
+ if (_locksByID.containsKey(id)) {
+ return _locksByID.get(id);
+ } else {
+ return null;
+ }
+ }
+
+ public LockedObject getLockedObjectByPath(ITransaction transaction,
+ String path) {
+ if (_locks.containsKey(path)) {
+ return (LockedObject) this._locks.get(path);
+ } else {
+ return null;
+ }
+ }
+
+ public LockedObject getTempLockedObjectByID(ITransaction transaction,
+ String id) {
+ if (_tempLocksByID.containsKey(id)) {
+ return _tempLocksByID.get(id);
+ } else {
+ return null;
+ }
+ }
+
+ public LockedObject getTempLockedObjectByPath(ITransaction transaction,
+ String path) {
+ if (_tempLocks.containsKey(path)) {
+ return (LockedObject) this._tempLocks.get(path);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * generates real LockedObjects for the resource at path and its parent
+ * folders. does not create new LockedObjects if they already exist
+ *
+ * @param transaction
+ * @param path
+ * path to the (new) LockedObject
+ * @return the LockedObject for path.
+ */
+ private LockedObject generateLockedObjects(ITransaction transaction,
+ String path) {
+ if (!_locks.containsKey(path)) {
+ LockedObject returnObject = new LockedObject(this, path,
+ !_temporary);
+ String parentPath = getParentPath(path);
+ if (parentPath != null) {
+ LockedObject parentLockedObject = generateLockedObjects(
+ transaction, parentPath);
+ parentLockedObject.addChild(returnObject);
+ returnObject._parent = parentLockedObject;
+ }
+ return returnObject;
+ } else {
+ // there is already a LockedObject on the specified path
+ return (LockedObject) this._locks.get(path);
+ }
+
+ }
+
+ /**
+ * generates temporary LockedObjects for the resource at path and its parent
+ * folders. does not create new LockedObjects if they already exist
+ *
+ * @param transaction
+ * @param path
+ * path to the (new) LockedObject
+ * @return the LockedObject for path.
+ */
+ private LockedObject generateTempLockedObjects(ITransaction transaction,
+ String path) {
+ if (!_tempLocks.containsKey(path)) {
+ LockedObject returnObject = new LockedObject(this, path, _temporary);
+ String parentPath = getParentPath(path);
+ if (parentPath != null) {
+ LockedObject parentLockedObject = generateTempLockedObjects(
+ transaction, parentPath);
+ parentLockedObject.addChild(returnObject);
+ returnObject._parent = parentLockedObject;
+ }
+ return returnObject;
+ } else {
+ // there is already a LockedObject on the specified path
+ return (LockedObject) this._tempLocks.get(path);
+ }
+
+ }
+
+ /**
+ * deletes unused LockedObjects and resets the counter. works recursively
+ * starting at the given LockedObject
+ *
+ * @param transaction
+ * @param lo
+ * LockedObject
+ * @param temporary
+ * Clean temporary or real locks
+ *
+ * @return if cleaned
+ */
+ private boolean cleanLockedObjects(ITransaction transaction,
+ LockedObject lo, boolean temporary) {
+
+ if (lo._children == null) {
+ if (lo._owner == null) {
+ if (temporary) {
+ lo.removeTempLockedObject();
+ } else {
+ lo.removeLockedObject();
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ boolean canDelete = true;
+ int limit = lo._children.length;
+ for (int i = 0; i < limit; i++) {
+ if (!cleanLockedObjects(transaction, lo._children[i], temporary)) {
+ canDelete = false;
+ } else {
+
+ // because the deleting shifts the array
+ i--;
+ limit--;
+ }
+ }
+ if (canDelete) {
+ if (lo._owner == null) {
+ if (temporary) {
+ lo.removeTempLockedObject();
+ } else {
+ lo.removeLockedObject();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * creates the parent path from the given path by removing the last '/' and
+ * everything after that
+ *
+ * @param path
+ * the path
+ * @return parent path
+ */
+ private String getParentPath(String path) {
+ int slash = path.lastIndexOf('/');
+ if (slash == -1) {
+ return null;
+ } else {
+ if (slash == 0) {
+ // return "root" if parent path is empty string
+ return "/";
+ } else {
+ return path.substring(0, slash);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/AbstractMethod.java b/src/main/java/net/sf/webdav/methods/AbstractMethod.java
new file mode 100644
index 0000000..7a44bb0
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/AbstractMethod.java
@@ -0,0 +1,596 @@
+/*
+ * 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 net.sf.webdav.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.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import net.sf.webdav.IMethodExecutor;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.fromcatalina.RequestUtil;
+import net.sf.webdav.fromcatalina.URLEncoder;
+import net.sf.webdav.fromcatalina.XMLWriter;
+import net.sf.webdav.locking.IResourceLocks;
+import net.sf.webdav.locking.LockedObject;
+
+public abstract class AbstractMethod implements IMethodExecutor {
+
+ private static final ThreadLocal thLastmodifiedDateFormat = new ThreadLocal();
+ private static final ThreadLocal thCreationDateFormat = new ThreadLocal();
+ private static final ThreadLocal thLocalDateFormat = new ThreadLocal();
+
+ /**
+ * Array containing the safe characters set.
+ */
+ protected static URLEncoder URL_ENCODER;
+
+ /**
+ * Default depth is infite.
+ */
+ protected static final int INFINITY = 3;
+
+ /**
+ * 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";
+
+ static {
+ /**
+ * GMT timezone - all HTTP dates are on GMT
+ */
+ URL_ENCODER = new URLEncoder();
+ URL_ENCODER.addSafeCharacter('-');
+ URL_ENCODER.addSafeCharacter('_');
+ URL_ENCODER.addSafeCharacter('.');
+ URL_ENCODER.addSafeCharacter('*');
+ URL_ENCODER.addSafeCharacter('/');
+ }
+
+ /**
+ * 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("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("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);
+ }
+
+
+ /**
+ * Parses and normalizes the destination header.
+ *
+ * @param req
+ * Servlet request
+ * @param resp
+ * Servlet response
+ * @return destinationPath
+ * @throws IOException
+ * if an error occurs while sending response
+ */
+ protected String parseDestinationHeader(HttpServletRequest req,
+ HttpServletResponse resp) throws IOException {
+ String destinationPath = req.getHeader("Destination");
+
+ if (destinationPath == null) {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return null;
+ }
+
+ // Remove url encoding from destination
+ destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
+
+ int protocolIndex = destinationPath.indexOf("://");
+ if (protocolIndex >= 0) {
+ // if the Destination URL contains the protocol, we can safely
+ // trim everything upto the first "/" character after "://"
+ int firstSeparator = destinationPath
+ .indexOf("/", protocolIndex + 4);
+ if (firstSeparator < 0) {
+ destinationPath = "/";
+ } else {
+ destinationPath = destinationPath.substring(firstSeparator);
+ }
+ } else {
+ String hostName = req.getServerName();
+ if ((hostName != null) && (destinationPath.startsWith(hostName))) {
+ destinationPath = destinationPath.substring(hostName.length());
+ }
+
+ int portIndex = destinationPath.indexOf(":");
+ if (portIndex >= 0) {
+ destinationPath = destinationPath.substring(portIndex);
+ }
+
+ if (destinationPath.startsWith(":")) {
+ int firstSeparator = destinationPath.indexOf("/");
+ if (firstSeparator < 0) {
+ destinationPath = "/";
+ } else {
+ destinationPath = destinationPath.substring(firstSeparator);
+ }
+ }
+ }
+
+ // Normalize destination path (remove '.' and' ..')
+ destinationPath = normalize(destinationPath);
+
+ String contextPath = req.getContextPath();
+ if ((contextPath != null) && (destinationPath.startsWith(contextPath))) {
+ destinationPath = destinationPath.substring(contextPath.length());
+ }
+
+ String pathInfo = req.getPathInfo();
+ if (pathInfo != null) {
+ String servletPath = req.getServletPath();
+ if ((servletPath != null)
+ && (destinationPath.startsWith(servletPath))) {
+ destinationPath = destinationPath.substring(servletPath
+ .length());
+ }
+ }
+
+ return destinationPath;
+ }
+
+ /**
+ * Return a context-relative path, beginning with a "/", that represents the
+ * canonical version of the specified path after ".." and "." elements are
+ * resolved out. If the specified path attempts to go outside the boundaries
+ * of the current context (i.e. too many ".." path elements are present),
+ * return null
instead.
+ *
+ * @param path
+ * Path to be normalized
+ * @return normalized path
+ */
+ protected String normalize(String path) {
+
+ if (path == null)
+ return null;
+
+ // Create a place for the normalized path
+ String normalized = path;
+
+ if (normalized.equals("/."))
+ return "/";
+
+ // Normalize the slashes and add leading slash if necessary
+ if (normalized.indexOf('\\') >= 0)
+ normalized = normalized.replace('\\', '/');
+ if (!normalized.startsWith("/"))
+ normalized = "/" + normalized;
+
+ // Resolve occurrences of "//" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("//");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index)
+ + normalized.substring(index + 1);
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/./");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index)
+ + normalized.substring(index + 2);
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/../");
+ if (index < 0)
+ break;
+ if (index == 0)
+ return (null); // Trying to go outside our context
+ int index2 = normalized.lastIndexOf('/', index - 1);
+ normalized = normalized.substring(0, index2)
+ + normalized.substring(index + 3);
+ }
+
+ // Return the normalized path that we have completed
+ return (normalized);
+
+ }
+
+ /**
+ * Return the relative path associated with this servlet.
+ *
+ * @param request
+ * The servlet request we are processing
+ */
+ protected String getRelativePath(HttpServletRequest request) {
+
+ // Are we being processed by a RequestDispatcher.include()?
+ if (request.getAttribute("javax.servlet.include.request_uri") != null) {
+ String result = (String) request
+ .getAttribute("javax.servlet.include.path_info");
+ // if (result == null)
+ // result = (String) request
+ // .getAttribute("javax.servlet.include.servlet_path");
+ if ((result == null) || (result.equals("")))
+ result = "/";
+ return (result);
+ }
+
+ // No, extract the desired path directly from the request
+ String result = request.getPathInfo();
+ // if (result == null) {
+ // result = request.getServletPath();
+ // }
+ if ((result == null) || (result.equals(""))) {
+ result = "/";
+ }
+ return (result);
+
+ }
+
+ /**
+ * creates the parent path from the given path by removing the last '/' and
+ * everything after that
+ *
+ * @param path
+ * the path
+ * @return parent path
+ */
+ protected String getParentPath(String path) {
+ int slash = path.lastIndexOf('/');
+ if (slash != -1) {
+ return path.substring(0, slash);
+ }
+ return null;
+ }
+
+ /**
+ * removes a / at the end of the path string, if present
+ *
+ * @param path
+ * the path
+ * @return the path without trailing /
+ */
+ protected String getCleanPath(String path) {
+
+ if (path.endsWith("/") && path.length() > 1)
+ path = path.substring(0, path.length() - 1);
+ return path;
+ }
+
+ /**
+ * Return JAXP document builder instance.
+ */
+ protected DocumentBuilder getDocumentBuilder() throws ServletException {
+ DocumentBuilder documentBuilder = null;
+ DocumentBuilderFactory documentBuilderFactory = null;
+ try {
+ documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new ServletException("jaxp failed");
+ }
+ return documentBuilder;
+ }
+
+ /**
+ * reads the depth header from the request and returns it as a int
+ *
+ * @param req
+ * @return the depth from the depth header
+ */
+ protected int getDepth(HttpServletRequest req) {
+ int depth = INFINITY;
+ String depthStr = req.getHeader("Depth");
+ if (depthStr != null) {
+ if (depthStr.equals("0")) {
+ depth = 0;
+ } else if (depthStr.equals("1")) {
+ depth = 1;
+ }
+ }
+ return depth;
+ }
+
+ /**
+ * URL rewriter.
+ *
+ * @param path
+ * Path which has to be rewiten
+ * @return the rewritten path
+ */
+ protected 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 String getETag(StoredObject so) {
+
+ String resourceLength = "";
+ String lastModified = "";
+
+ if (so != null && so.isResource()) {
+ resourceLength = new Long(so.getResourceLength()).toString();
+ lastModified = new Long(so.getLastModified().getTime()).toString();
+ }
+
+ return "W/\"" + resourceLength + "-" + lastModified + "\"";
+
+ }
+
+ protected String[] getLockIdFromIfHeader(HttpServletRequest req) {
+ String[] ids = new String[2];
+ String id = req.getHeader("If");
+
+ if (id != null && !id.equals("")) {
+ if (id.indexOf(">)") == id.lastIndexOf(">)")) {
+ id = id.substring(id.indexOf("(<"), id.indexOf(">)"));
+
+ if (id.indexOf("locktoken:") != -1) {
+ id = id.substring(id.indexOf(':') + 1);
+ }
+ ids[0] = id;
+ } else {
+ String firstId = id.substring(id.indexOf("(<"), id
+ .indexOf(">)"));
+ if (firstId.indexOf("locktoken:") != -1) {
+ firstId = firstId.substring(firstId.indexOf(':') + 1);
+ }
+ ids[0] = firstId;
+
+ String secondId = id.substring(id.lastIndexOf("(<"), id
+ .lastIndexOf(">)"));
+ if (secondId.indexOf("locktoken:") != -1) {
+ secondId = secondId.substring(secondId.indexOf(':') + 1);
+ }
+ ids[1] = secondId;
+ }
+
+ } else {
+ ids = null;
+ }
+ return ids;
+ }
+
+ protected String getLockIdFromLockTokenHeader(HttpServletRequest req) {
+ String id = req.getHeader("Lock-Token");
+
+ if (id != null) {
+ id = id.substring(id.indexOf(":") + 1, id.indexOf(">"));
+
+ }
+
+ 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 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 void sendReport(HttpServletRequest req, HttpServletResponse resp,
+ Hashtable errorList) throws IOException {
+
+ if (errorList.size() == 1) {
+ int code = errorList.elements().nextElement();
+ if (WebdavStatus.getStatusText(code) != "") {
+ resp.sendError(code, WebdavStatus.getStatusText(code));
+ } else {
+ resp.sendError(code);
+ }
+ }
+ else
+ {
+ resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+
+ String absoluteUri = req.getRequestURI();
+ // String relativePath = getRelativePath(req);
+
+ HashMap namespaces = new HashMap();
+ namespaces.put("DAV:", "D");
+
+ XMLWriter generatedXML = new XMLWriter(namespaces);
+ generatedXML.writeXMLHeader();
+
+ generatedXML.writeElement("DAV::multistatus", XMLWriter.OPENING);
+
+ Enumeration pathList = errorList.keys();
+ while (pathList.hasMoreElements()) {
+
+ String errorPath = (String) pathList.nextElement();
+ int errorCode = ((Integer) errorList.get(errorPath)).intValue();
+
+ generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::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 (!toAppend.startsWith("/") && !toAppend.startsWith("http:"))
+ toAppend = "/" + toAppend;
+ generatedXML.writeText(errorPath);
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText("HTTP/1.1 " + errorCode + " "
+ + WebdavStatus.getStatusText(errorCode));
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
+
+ }
+
+ generatedXML.writeElement("DAV::multistatus", XMLWriter.CLOSING);
+
+ Writer writer = resp.getWriter();
+ writer.write(generatedXML.toString());
+ writer.close();
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DeterminableMethod.java b/src/main/java/net/sf/webdav/methods/DeterminableMethod.java
new file mode 100644
index 0000000..8b7fffa
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DeterminableMethod.java
@@ -0,0 +1,58 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import net.sf.webdav.StoredObject;
+
+public abstract class DeterminableMethod extends AbstractMethod {
+
+ private static final String NULL_RESOURCE_METHODS_ALLOWED = "OPTIONS, MKCOL, PUT, PROPFIND, LOCK, UNLOCK";
+
+ private static final String RESOURCE_METHODS_ALLOWED = "OPTIONS, GET, HEAD, POST, DELETE, TRACE"
+ + ", PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND";
+
+ private static final String FOLDER_METHOD_ALLOWED = ", PUT";
+
+ private static final String LESS_ALLOWED_METHODS = "OPTIONS, MKCOL, PUT";
+
+ /**
+ * Determines the methods normally allowed for the resource.
+ *
+ * @param so
+ * StoredObject representing the resource
+ * @return all allowed methods, separated by commas
+ */
+ protected static String determineMethodsAllowed(StoredObject so) {
+
+ try {
+ if (so != null) {
+ if (so.isNullResource()) {
+
+ return NULL_RESOURCE_METHODS_ALLOWED;
+
+ } else if (so.isFolder()) {
+ return RESOURCE_METHODS_ALLOWED + FOLDER_METHOD_ALLOWED;
+ }
+ // else resource
+ return RESOURCE_METHODS_ALLOWED;
+ }
+ } catch (Exception e) {
+ // we do nothing, just return less allowed methods
+ }
+
+ return LESS_ALLOWED_METHODS;
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoCopy.java b/src/main/java/net/sf/webdav/methods/DoCopy.java
new file mode 100644
index 0000000..9b84118
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoCopy.java
@@ -0,0 +1,348 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
+import net.sf.webdav.exceptions.ObjectNotFoundException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.fromcatalina.RequestUtil;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoCopy extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoCopy.class);
+
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+ private DoDelete _doDelete;
+ private boolean _readOnly;
+
+ public DoCopy(IWebdavStore store, ResourceLocks resourceLocks,
+ DoDelete doDelete, boolean readOnly) {
+ _store = store;
+ _resourceLocks = resourceLocks;
+ _doDelete = doDelete;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ String path = getRelativePath(req);
+ if (!_readOnly) {
+
+ String tempLockOwner = "doCopy" + System.currentTimeMillis()
+ + req.toString();
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ try {
+ if (!copyResource(transaction, req, resp))
+ return;
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (ObjectAlreadyExistsException e) {
+ resp.sendError(WebdavStatus.SC_CONFLICT, req
+ .getRequestURI());
+ } catch (ObjectNotFoundException e) {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND, req
+ .getRequestURI());
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ path, tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+
+ }
+
+ /**
+ * Copy a resource.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param req
+ * Servlet request
+ * @param resp
+ * Servlet response
+ * @return true if the copy is successful
+ * @throws WebdavException
+ * if an error in the underlying store occurs
+ * @throws IOException
+ * when an error occurs while sending the response
+ * @throws LockFailedException
+ */
+ public boolean copyResource(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws WebdavException, IOException, LockFailedException {
+
+ // Parsing destination header
+ String destinationPath = parseDestinationHeader(req, resp);
+
+ if (destinationPath == null)
+ return false;
+
+ String path = getRelativePath(req);
+
+ if (path.equals(destinationPath)) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return false;
+ }
+
+ Hashtable errorList = new Hashtable();
+ String parentDestinationPath = getParentPath(getCleanPath(destinationPath));
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks,
+ parentDestinationPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return false; // parentDestination is locked
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, destinationPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return false; // destination is locked
+ }
+
+ // Parsing overwrite header
+
+ boolean overwrite = true;
+ String overwriteHeader = req.getHeader("Overwrite");
+
+ if (overwriteHeader != null) {
+ overwrite = overwriteHeader.equalsIgnoreCase("T");
+ }
+
+ // Overwriting the destination
+ String lockOwner = "copyResource" + System.currentTimeMillis()
+ + req.toString();
+
+ if (_resourceLocks.lock(transaction, destinationPath, lockOwner, false,
+ 0, TEMP_TIMEOUT, TEMPORARY)) {
+ StoredObject copySo, destinationSo = null;
+ try {
+ copySo = _store.getStoredObject(transaction, path);
+ // Retrieve the resources
+ if (copySo == null) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return false;
+ }
+
+ if (copySo.isNullResource()) {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(copySo);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ return false;
+ }
+
+ errorList = new Hashtable();
+
+ destinationSo = _store.getStoredObject(transaction,
+ destinationPath);
+
+ if (overwrite) {
+
+ // Delete destination resource, if it exists
+ if (destinationSo != null) {
+ _doDelete.deleteResource(transaction, destinationPath,
+ errorList, req, resp);
+
+ } else {
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ }
+ } else {
+
+ // If the destination exists, then it's a conflict
+ if (destinationSo != null) {
+ resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+ return false;
+ } else {
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ }
+
+ }
+ copy(transaction, path, destinationPath, errorList, req, resp);
+
+ if (!errorList.isEmpty()) {
+ sendReport(req, resp, errorList);
+ }
+
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ destinationPath, lockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return false;
+ }
+ return true;
+
+ }
+
+ /**
+ * copies the specified resource(s) to the specified destination.
+ * preconditions must be handled by the caller. Standard status codes must
+ * be handled by the caller. a multi status report in case of errors is
+ * created here.
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param sourcePath
+ * path from where to read
+ * @param destinationPath
+ * path where to write
+ * @param req
+ * HttpServletRequest
+ * @param resp
+ * HttpServletResponse
+ * @throws WebdavException
+ * if an error in the underlying store occurs
+ * @throws IOException
+ */
+ private void copy(ITransaction transaction, String sourcePath,
+ String destinationPath, Hashtable errorList,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws WebdavException, IOException {
+
+ StoredObject sourceSo = _store.getStoredObject(transaction, sourcePath);
+ if (sourceSo.isResource()) {
+ _store.createResource(transaction, destinationPath);
+ long resourceLength = _store.setResourceContent(transaction,
+ destinationPath, _store.getResourceContent(transaction,
+ sourcePath), null, null);
+
+ if (resourceLength != -1) {
+ StoredObject destinationSo = _store.getStoredObject(
+ transaction, destinationPath);
+ destinationSo.setResourceLength(resourceLength);
+ }
+
+ } else {
+
+ if (sourceSo.isFolder()) {
+ copyFolder(transaction, sourcePath, destinationPath, errorList,
+ req, resp);
+ } else {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND);
+ }
+ }
+ }
+
+ /**
+ * helper method of copy() recursively copies the FOLDER at source path to
+ * destination path
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param sourcePath
+ * where to read
+ * @param destinationPath
+ * where to write
+ * @param errorList
+ * all errors that ocurred
+ * @param req
+ * HttpServletRequest
+ * @param resp
+ * HttpServletResponse
+ * @throws WebdavException
+ * if an error in the underlying store occurs
+ */
+ private void copyFolder(ITransaction transaction, String sourcePath,
+ String destinationPath, Hashtable errorList,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws WebdavException {
+
+ _store.createFolder(transaction, destinationPath);
+ boolean infiniteDepth = true;
+ String depth = req.getHeader("Depth");
+ if (depth != null) {
+ if (depth.equals("0")) {
+ infiniteDepth = false;
+ }
+ }
+ if (infiniteDepth) {
+ String[] children = _store
+ .getChildrenNames(transaction, sourcePath);
+ children = children == null ? new String[] {} : children;
+
+ StoredObject childSo;
+ for (int i = children.length - 1; i >= 0; i--) {
+ children[i] = "/" + children[i];
+ try {
+ childSo = _store.getStoredObject(transaction,
+ (sourcePath + children[i]));
+ if (childSo.isResource()) {
+ _store.createResource(transaction, destinationPath
+ + children[i]);
+ long resourceLength = _store.setResourceContent(
+ transaction, destinationPath + children[i],
+ _store.getResourceContent(transaction,
+ sourcePath + children[i]), null, null);
+
+ if (resourceLength != -1) {
+ StoredObject destinationSo = _store
+ .getStoredObject(transaction,
+ destinationPath + children[i]);
+ destinationSo.setResourceLength(resourceLength);
+ }
+
+ } else {
+ copyFolder(transaction, sourcePath + children[i],
+ destinationPath + children[i], errorList, req,
+ resp);
+ }
+ } catch (AccessDeniedException e) {
+ errorList.put(destinationPath + children[i], new Integer(
+ WebdavStatus.SC_FORBIDDEN));
+ } catch (ObjectNotFoundException e) {
+ errorList.put(destinationPath + children[i], new Integer(
+ WebdavStatus.SC_NOT_FOUND));
+ } catch (ObjectAlreadyExistsException e) {
+ errorList.put(destinationPath + children[i], new Integer(
+ WebdavStatus.SC_CONFLICT));
+ } catch (WebdavException e) {
+ errorList.put(destinationPath + children[i], new Integer(
+ WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoDelete.java b/src/main/java/net/sf/webdav/methods/DoDelete.java
new file mode 100644
index 0000000..b1f2484
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoDelete.java
@@ -0,0 +1,206 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
+import net.sf.webdav.exceptions.ObjectNotFoundException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoDelete extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoDelete.class);
+
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+ private boolean _readOnly;
+
+ public DoDelete(IWebdavStore store, ResourceLocks resourceLocks,
+ boolean readOnly) {
+ _store = store;
+ _resourceLocks = resourceLocks;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (!_readOnly) {
+ String path = getRelativePath(req);
+ String parentPath = getParentPath(getCleanPath(path));
+
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // parent is locked
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // resource is locked
+ }
+
+ String tempLockOwner = "doDelete" + System.currentTimeMillis()
+ + req.toString();
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ try {
+ errorList = new Hashtable();
+ deleteResource(transaction, path, errorList, req, resp);
+ if (!errorList.isEmpty()) {
+ sendReport(req, resp, errorList);
+ }
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (ObjectAlreadyExistsException e) {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND, req
+ .getRequestURI());
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ path, tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+
+ }
+
+ /**
+ * deletes the recources at "path"
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param path
+ * the folder to be deleted
+ * @param errorList
+ * all errors that ocurred
+ * @param req
+ * HttpServletRequest
+ * @param resp
+ * HttpServletResponse
+ * @throws WebdavException
+ * if an error in the underlying store occurs
+ * @throws IOException
+ * when an error occurs while sending the response
+ */
+ public void deleteResource(ITransaction transaction, String path,
+ Hashtable errorList, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, WebdavException {
+
+ resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+
+ if (!_readOnly) {
+
+ StoredObject so = _store.getStoredObject(transaction, path);
+ if (so != null) {
+
+ if (so.isResource()) {
+ _store.removeObject(transaction, path);
+ } else {
+ if (so.isFolder()) {
+ deleteFolder(transaction, path, errorList, req, resp);
+ _store.removeObject(transaction, path);
+ } else {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND);
+ }
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND);
+ }
+ so = null;
+
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+ }
+
+ /**
+ *
+ * helper method of deleteResource() deletes the folder and all of its
+ * contents
+ *
+ * @param transaction
+ * indicates that the method is within the scope of a WebDAV
+ * transaction
+ * @param path
+ * the folder to be deleted
+ * @param errorList
+ * all errors that ocurred
+ * @param req
+ * HttpServletRequest
+ * @param resp
+ * HttpServletResponse
+ * @throws WebdavException
+ * if an error in the underlying store occurs
+ */
+ private void deleteFolder(ITransaction transaction, String path,
+ Hashtable errorList, HttpServletRequest req,
+ HttpServletResponse resp) throws WebdavException {
+
+ String[] children = _store.getChildrenNames(transaction, path);
+ children = children == null ? new String[] {} : children;
+ StoredObject so = null;
+ for (int i = children.length - 1; i >= 0; i--) {
+ children[i] = "/" + children[i];
+ try {
+ so = _store.getStoredObject(transaction, path + children[i]);
+ if (so.isResource()) {
+ _store.removeObject(transaction, path + children[i]);
+
+ } else {
+ deleteFolder(transaction, path + children[i], errorList,
+ req, resp);
+
+ _store.removeObject(transaction, path + children[i]);
+
+ }
+ } catch (AccessDeniedException e) {
+ errorList.put(path + children[i], new Integer(
+ WebdavStatus.SC_FORBIDDEN));
+ } catch (ObjectNotFoundException e) {
+ errorList.put(path + children[i], new Integer(
+ WebdavStatus.SC_NOT_FOUND));
+ } catch (WebdavException e) {
+ errorList.put(path + children[i], new Integer(
+ WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+ }
+ }
+ so = null;
+
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoGet.java b/src/main/java/net/sf/webdav/methods/DoGet.java
new file mode 100644
index 0000000..dd75a9c
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoGet.java
@@ -0,0 +1,307 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.IMimeTyper;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoGet extends DoHead {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoGet.class);
+
+ public DoGet(IWebdavStore store, String dftIndexFile, String insteadOf404,
+ ResourceLocks resourceLocks, IMimeTyper mimeTyper,
+ int contentLengthHeader) {
+ super(store, dftIndexFile, insteadOf404, resourceLocks, mimeTyper,
+ contentLengthHeader);
+
+ }
+
+ protected void doBody(ITransaction transaction, HttpServletResponse resp,
+ String path) {
+
+ try {
+ StoredObject so = _store.getStoredObject(transaction, path);
+ if (so.isNullResource()) {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+ OutputStream out = resp.getOutputStream();
+ InputStream in = _store.getResourceContent(transaction, path);
+ try {
+ int read = -1;
+ byte[] copyBuffer = new byte[BUF_SIZE];
+
+ while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
+ out.write(copyBuffer, 0, read);
+ }
+ } finally {
+ // flushing causes a IOE if a file is opened on the webserver
+ // client disconnected before server finished sending response
+ try {
+ in.close();
+ } catch (Exception e) {
+ LOG.warn("Closing InputStream causes Exception!\n"
+ + e.toString());
+ }
+ try {
+ out.flush();
+ out.close();
+ } catch (Exception e) {
+ LOG.warn("Flushing OutputStream causes Exception!\n"
+ + e.toString());
+ }
+ }
+ } catch (Exception e) {
+ LOG.trace(e.toString());
+ }
+ }
+
+ protected void folderBody(ITransaction transaction, String path,
+ HttpServletResponse resp, HttpServletRequest req)
+ throws IOException {
+
+ StoredObject so = _store.getStoredObject(transaction, path);
+ if (so == null) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, req
+ .getRequestURI());
+ } else {
+
+ if (so.isNullResource()) {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ if (so.isFolder()) {
+ // TODO some folder response (for browsers, DAV tools
+ // use propfind) in html?
+ Locale locale = req.getLocale();
+ DateFormat shortDF= getDateTimeFormat(req.getLocale());
+ resp.setContentType("text/html");
+ resp.setCharacterEncoding("UTF8");
+ OutputStream out = resp.getOutputStream();
+ String[] children = _store.getChildrenNames(transaction, path);
+ // Make sure it's not null
+ children = children == null ? new String[] {} : children;
+ // Sort by name
+ Arrays.sort(children);
+ StringBuilder childrenTemp = new StringBuilder();
+ childrenTemp.append("Content of folder");
+ childrenTemp.append(path);
+ childrenTemp.append("");
+ childrenTemp.append("");
+ childrenTemp.append(getHeader(transaction, path, resp, req));
+ childrenTemp.append("");
+ childrenTemp.append("Name | Size | Created | Modified |
");
+ childrenTemp.append("");
+ childrenTemp.append("Parent |
");
+ boolean isEven= false;
+ for (String child : children)
+ {
+ isEven= !isEven;
+ childrenTemp.append("");
+ childrenTemp.append("");
+ childrenTemp.append("");
+ childrenTemp.append(child);
+ childrenTemp.append(" | ");
+ if (obj != null && obj.isFolder())
+ {
+ childrenTemp.append("Folder | ");
+ }
+ else
+ {
+ childrenTemp.append("");
+ if (obj != null )
+ {
+ childrenTemp.append(obj.getResourceLength());
+ }
+ else
+ {
+ childrenTemp.append("Unknown");
+ }
+ childrenTemp.append(" Bytes | ");
+ }
+ if (obj != null && obj.getCreationDate() != null)
+ {
+ childrenTemp.append("");
+ childrenTemp.append(shortDF.format(obj.getCreationDate()));
+ childrenTemp.append(" | ");
+ }
+ else
+ {
+ childrenTemp.append(" | ");
+ }
+ if (obj != null && obj.getLastModified() != null)
+ {
+ childrenTemp.append("");
+ childrenTemp.append(shortDF.format(obj.getLastModified()));
+ childrenTemp.append(" | ");
+ }
+ else
+ {
+ childrenTemp.append(" | ");
+ }
+ childrenTemp.append("
");
+ }
+ childrenTemp.append("
");
+ childrenTemp.append(getFooter(transaction, path, resp, req));
+ childrenTemp.append("");
+ out.write(childrenTemp.toString().getBytes("UTF-8"));
+ }
+ }
+ }
+
+ /**
+ * Return the CSS styles used to display the HTML representation
+ * of the webdav content.
+ *
+ * @return
+ */
+ protected String getCSS()
+ {
+ // The default styles to use
+ String retVal= "body {\n"+
+ " font-family: Arial, Helvetica, sans-serif;\n"+
+ "}\n"+
+ "h1 {\n"+
+ " font-size: 1.5em;\n"+
+ "}\n"+
+ "th {\n"+
+ " background-color: #9DACBF;\n"+
+ "}\n"+
+ "table {\n"+
+ " border-top-style: solid;\n"+
+ " border-right-style: solid;\n"+
+ " border-bottom-style: solid;\n"+
+ " border-left-style: solid;\n"+
+ "}\n"+
+ "td {\n"+
+ " margin: 0px;\n"+
+ " padding-top: 2px;\n"+
+ " padding-right: 5px;\n"+
+ " padding-bottom: 2px;\n"+
+ " padding-left: 5px;\n"+
+ "}\n"+
+ "tr.even {\n"+
+ " background-color: #CCCCCC;\n"+
+ "}\n"+
+ "tr.odd {\n"+
+ " background-color: #FFFFFF;\n"+
+ "}\n"+
+ "";
+ try
+ {
+ // Try loading one via class loader and use that one instead
+ ClassLoader cl = getClass().getClassLoader();
+ InputStream iStream = cl.getResourceAsStream("webdav.css");
+ if(iStream != null)
+ {
+ // Found css via class loader, use that one
+ StringBuffer out = new StringBuffer();
+ byte[] b = new byte[4096];
+ for (int n; (n = iStream.read(b)) != -1;)
+ {
+ out.append(new String(b, 0, n));
+}
+ retVal= out.toString();
+ }
+ }
+ catch (Exception ex)
+ {
+ LOG.error("Error in reading webdav.css", ex);
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Return the header to be displayed in front of the folder content
+ *
+ * @param transaction
+ * @param path
+ * @param resp
+ * @param req
+ * @return
+ */
+ protected String getHeader(ITransaction transaction, String path,
+ HttpServletResponse resp, HttpServletRequest req)
+ {
+ return "Content of folder "+path+"
";
+ }
+
+ /**
+ * Return the footer to be displayed after the folder content
+ *
+ * @param transaction
+ * @param path
+ * @param resp
+ * @param req
+ * @return
+ */
+ protected String getFooter(ITransaction transaction, String path,
+ HttpServletResponse resp, HttpServletRequest req)
+ {
+ return "";
+ }
+
+ /**
+ * Return this as the Date/Time format for displaying Creation + Modification dates
+ *
+ * @param browserLocale
+ * @return DateFormat used to display creation and modification dates
+ */
+ protected DateFormat getDateTimeFormat(Locale browserLocale)
+ {
+ return SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.MEDIUM, browserLocale);
+ }
+ }
diff --git a/src/main/java/net/sf/webdav/methods/DoHead.java b/src/main/java/net/sf/webdav/methods/DoHead.java
new file mode 100644
index 0000000..7775cd6
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoHead.java
@@ -0,0 +1,194 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.IMimeTyper;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoHead extends AbstractMethod {
+
+ protected String _dftIndexFile;
+ protected IWebdavStore _store;
+ protected String _insteadOf404;
+ protected ResourceLocks _resourceLocks;
+ protected IMimeTyper _mimeTyper;
+ protected int _contentLength;
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoHead.class);
+
+ public DoHead(IWebdavStore store, String dftIndexFile, String insteadOf404,
+ ResourceLocks resourceLocks, IMimeTyper mimeTyper,
+ int contentLengthHeader) {
+ _store = store;
+ _dftIndexFile = dftIndexFile;
+ _insteadOf404 = insteadOf404;
+ _resourceLocks = resourceLocks;
+ _mimeTyper = mimeTyper;
+ _contentLength = contentLengthHeader;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+
+ // determines if the uri exists.
+
+ boolean bUriExists = false;
+
+ String path = getRelativePath(req);
+ LOG.trace("-- " + this.getClass().getName());
+
+ StoredObject so;
+ try {
+ so = _store.getStoredObject(transaction, path);
+ if (so == null) {
+ if (this._insteadOf404 != null && !_insteadOf404.trim().equals("")) {
+ path = this._insteadOf404;
+ so = _store.getStoredObject(transaction, this._insteadOf404);
+ }
+ } else
+ bUriExists = true;
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+ }
+
+ if (so != null) {
+ if (so.isFolder()) {
+ if (_dftIndexFile != null && !_dftIndexFile.trim().equals("")) {
+ resp.sendRedirect(resp.encodeRedirectURL(req
+ .getRequestURI()
+ + this._dftIndexFile));
+ return;
+ }
+ } else if (so.isNullResource()) {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ String tempLockOwner = "doGet" + System.currentTimeMillis()
+ + req.toString();
+
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ try {
+
+ String eTagMatch = req.getHeader("If-None-Match");
+ if (eTagMatch != null) {
+ if (eTagMatch.equals(getETag(so))) {
+ resp.setStatus(WebdavStatus.SC_NOT_MODIFIED);
+ return;
+ }
+ }
+
+ if (so.isResource()) {
+ // path points to a file but ends with / or \
+ if (path.endsWith("/") || (path.endsWith("\\"))) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND,
+ req.getRequestURI());
+ } else {
+
+ // setting headers
+ long lastModified = so.getLastModified().getTime();
+ resp.setDateHeader("last-modified", lastModified);
+
+ String eTag = getETag(so);
+ resp.addHeader("ETag", eTag);
+
+ long resourceLength = so.getResourceLength();
+
+ if (_contentLength == 1) {
+ if (resourceLength > 0) {
+ if (resourceLength <= Integer.MAX_VALUE) {
+ resp
+ .setContentLength((int) resourceLength);
+ } else {
+ resp.setHeader("content-length", ""
+ + resourceLength);
+ // is "content-length" the right header?
+ // is long a valid format?
+ }
+ }
+ }
+
+ String mimeType = _mimeTyper.getMimeType(transaction, path);
+ if (mimeType != null) {
+ resp.setContentType(mimeType);
+ } else {
+ int lastSlash = path.replace('\\', '/')
+ .lastIndexOf('/');
+ int lastDot = path.indexOf(".", lastSlash);
+ if (lastDot == -1) {
+ resp.setContentType("text/html");
+ }
+ }
+
+ doBody(transaction, resp, path);
+ }
+ } else {
+ folderBody(transaction, path, resp, req);
+ }
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (ObjectAlreadyExistsException e) {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND, req
+ .getRequestURI());
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ path, tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ folderBody(transaction, path, resp, req);
+ }
+
+ if (!bUriExists)
+ resp.setStatus(WebdavStatus.SC_NOT_FOUND);
+
+ }
+
+ protected void folderBody(ITransaction transaction, String path,
+ HttpServletResponse resp, HttpServletRequest req)
+ throws IOException {
+ // no body for HEAD
+ }
+
+ protected void doBody(ITransaction transaction, HttpServletResponse resp,
+ String path) throws IOException {
+ // no body for HEAD
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoLock.java b/src/main/java/net/sf/webdav/methods/DoLock.java
new file mode 100644
index 0000000..d5af92f
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoLock.java
@@ -0,0 +1,593 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Hashtable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.fromcatalina.XMLWriter;
+import net.sf.webdav.locking.IResourceLocks;
+import net.sf.webdav.locking.LockedObject;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class DoLock extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoLock.class);
+
+ private IWebdavStore _store;
+ private IResourceLocks _resourceLocks;
+ private boolean _readOnly;
+
+ private boolean _macLockRequest = false;
+
+ private boolean _exclusive = false;
+ private String _type = null;
+ private String _lockOwner = null;
+
+ private String _path = null;
+ private String _parentPath = null;
+
+ private String _userAgent = null;
+
+ public DoLock(IWebdavStore store, IResourceLocks resourceLocks,
+ boolean readOnly) {
+ _store = store;
+ _resourceLocks = resourceLocks;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (_readOnly) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+ } else {
+ _path = getRelativePath(req);
+ _parentPath = getParentPath(getCleanPath(_path));
+
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, _path)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // resource is locked
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, _parentPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // parent is locked
+ }
+
+ // Mac OS Finder (whether 10.4.x or 10.5) can't store files
+ // because executing a LOCK without lock information causes a
+ // SC_BAD_REQUEST
+ _userAgent = req.getHeader("User-Agent");
+ if (_userAgent != null && _userAgent.indexOf("Darwin") != -1) {
+ _macLockRequest = true;
+
+ String timeString = new Long(System.currentTimeMillis())
+ .toString();
+ _lockOwner = _userAgent.concat(timeString);
+ }
+
+ String tempLockOwner = "doLock" + System.currentTimeMillis()
+ + req.toString();
+ if (_resourceLocks.lock(transaction, _path, tempLockOwner, false,
+ 0, TEMP_TIMEOUT, TEMPORARY)) {
+ try {
+ if (req.getHeader("If") != null) {
+ doRefreshLock(transaction, req, resp);
+ } else {
+ doLock(transaction, req, resp);
+ }
+ } catch (LockFailedException e) {
+ resp.sendError(WebdavStatus.SC_LOCKED);
+ LOG.error("Lockfailed exception", e);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ _path, tempLockOwner);
+ }
+ }
+ }
+ }
+
+ private void doLock(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+
+ StoredObject so = _store.getStoredObject(transaction, _path);
+
+ if (so != null) {
+ doLocking(transaction, req, resp);
+ } else {
+ // resource doesn't exist, null-resource lock
+ doNullResourceLock(transaction, req, resp);
+ }
+
+ so = null;
+ _exclusive = false;
+ _type = null;
+ _lockOwner = null;
+
+ }
+
+ private void doLocking(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException {
+
+ // Tests if LockObject on requested path exists, and if so, tests
+ // exclusivity
+ LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
+ _path);
+ if (lo != null) {
+ if (lo.isExclusive()) {
+ sendLockFailError(transaction, req, resp);
+ return;
+ }
+ }
+ try {
+ // Thats the locking itself
+ executeLock(transaction, req, resp);
+
+ } catch (ServletException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ LOG.trace(e.toString());
+ } catch (LockFailedException e) {
+ sendLockFailError(transaction, req, resp);
+ } finally {
+ lo = null;
+ }
+
+ }
+
+ private void doNullResourceLock(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+
+ StoredObject parentSo, nullSo = null;
+
+ try {
+ parentSo = _store.getStoredObject(transaction, _parentPath);
+ if (_parentPath != null && parentSo == null) {
+ _store.createFolder(transaction, _parentPath);
+ } else if (_parentPath != null && parentSo != null
+ && parentSo.isResource()) {
+ resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+ nullSo = _store.getStoredObject(transaction, _path);
+ if (nullSo == null) {
+ // resource doesn't exist
+ _store.createResource(transaction, _path);
+
+ // Transmit expects 204 response-code, not 201
+ if (_userAgent != null && _userAgent.indexOf("Transmit") != -1) {
+ LOG
+ .trace("DoLock.execute() : do workaround for user agent '"
+ + _userAgent + "'");
+ resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+ } else {
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ }
+
+ } else {
+ // resource already exists, could not execute null-resource lock
+ sendLockFailError(transaction, req, resp);
+ return;
+ }
+ nullSo = _store.getStoredObject(transaction, _path);
+ // define the newly created resource as null-resource
+ nullSo.setNullResource(true);
+
+ // Thats the locking itself
+ executeLock(transaction, req, resp);
+
+ } catch (LockFailedException e) {
+ sendLockFailError(transaction, req, resp);
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ LOG.error("Webdav exception", e);
+ } catch (ServletException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ LOG.error("Servlet exception", e);
+ } finally {
+ parentSo = null;
+ nullSo = null;
+ }
+ }
+
+ private void doRefreshLock(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, LockFailedException {
+
+ String[] lockTokens = getLockIdFromIfHeader(req);
+ String lockToken = null;
+ if (lockTokens != null)
+ lockToken = lockTokens[0];
+
+ if (lockToken != null) {
+ // Getting LockObject of specified lockToken in If header
+ LockedObject refreshLo = _resourceLocks.getLockedObjectByID(
+ transaction, lockToken);
+ if (refreshLo != null) {
+ int timeout = getTimeout(transaction, req);
+
+ refreshLo.refreshTimeout(timeout);
+ // sending success response
+ generateXMLReport(transaction, resp, refreshLo);
+
+ refreshLo = null;
+ } else {
+ // no LockObject to given lockToken
+ resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+ }
+
+ } else {
+ resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+ }
+ }
+
+ // ------------------------------------------------- helper methods
+
+ /**
+ * Executes the LOCK
+ */
+ private void executeLock(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws LockFailedException, IOException,
+ ServletException {
+
+ // Mac OS lock request workaround
+ if (_macLockRequest) {
+ LOG.trace("DoLock.execute() : do workaround for user agent '"
+ + _userAgent + "'");
+
+ doMacLockRequestWorkaround(transaction, req, resp);
+ } else {
+ // Getting LockInformation from request
+ if (getLockInformation(transaction, req, resp)) {
+ int depth = getDepth(req);
+ int lockDuration = getTimeout(transaction, req);
+
+ boolean lockSuccess = false;
+ if (_exclusive) {
+ lockSuccess = _resourceLocks.exclusiveLock(transaction,
+ _path, _lockOwner, depth, lockDuration);
+ } else {
+ lockSuccess = _resourceLocks.sharedLock(transaction, _path,
+ _lockOwner, depth, lockDuration);
+ }
+
+ if (lockSuccess) {
+ // Locks successfully placed - return information about
+ LockedObject lo = _resourceLocks.getLockedObjectByPath(
+ transaction, _path);
+ if (lo != null) {
+ generateXMLReport(transaction, resp, lo);
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ sendLockFailError(transaction, req, resp);
+
+ throw new LockFailedException();
+ }
+ } else {
+ // information for LOCK could not be read successfully
+ resp.setContentType("text/xml; charset=UTF-8");
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ }
+ }
+ }
+
+ /**
+ * Tries to get the LockInformation from LOCK request
+ */
+ private boolean getLockInformation(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ Node lockInfoNode = null;
+ DocumentBuilder documentBuilder = null;
+
+ documentBuilder = getDocumentBuilder();
+ try {
+ Document document = documentBuilder.parse(new InputSource(req
+ .getInputStream()));
+
+ // Get the root element of the document
+ Element rootElement = document.getDocumentElement();
+
+ lockInfoNode = rootElement;
+
+ if (lockInfoNode != null) {
+ NodeList childList = lockInfoNode.getChildNodes();
+ Node lockScopeNode = null;
+ Node lockTypeNode = null;
+ Node lockOwnerNode = null;
+
+ Node currentNode = null;
+ String nodeName = null;
+
+ for (int i = 0; i < childList.getLength(); i++) {
+ currentNode = childList.item(i);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE
+ || currentNode.getNodeType() == Node.TEXT_NODE) {
+
+ nodeName = currentNode.getNodeName();
+
+ if (nodeName.endsWith("locktype")) {
+ lockTypeNode = currentNode;
+ }
+ if (nodeName.endsWith("lockscope")) {
+ lockScopeNode = currentNode;
+ }
+ if (nodeName.endsWith("owner")) {
+ lockOwnerNode = currentNode;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (lockScopeNode != null) {
+ String scope = null;
+ childList = lockScopeNode.getChildNodes();
+ for (int i = 0; i < childList.getLength(); i++) {
+ currentNode = childList.item(i);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ scope = currentNode.getNodeName();
+
+ if (scope.endsWith("exclusive")) {
+ _exclusive = true;
+ } else if (scope.equals("shared")) {
+ _exclusive = false;
+ }
+ }
+ }
+ if (scope == null) {
+ return false;
+ }
+
+ } else {
+ return false;
+ }
+
+ if (lockTypeNode != null) {
+ childList = lockTypeNode.getChildNodes();
+ for (int i = 0; i < childList.getLength(); i++) {
+ currentNode = childList.item(i);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ _type = currentNode.getNodeName();
+
+ if (_type.endsWith("write")) {
+ _type = "write";
+ } else if (_type.equals("read")) {
+ _type = "read";
+ }
+ }
+ }
+ if (_type == null) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ if (lockOwnerNode != null) {
+ childList = lockOwnerNode.getChildNodes();
+ for (int i = 0; i < childList.getLength(); i++) {
+ currentNode = childList.item(i);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE
+ || currentNode.getNodeType() == Node.TEXT_NODE) {
+ _lockOwner = currentNode.getFirstChild()
+ .getNodeValue();
+ }
+ }
+ }
+ if (_lockOwner == null) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ } catch (DOMException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ LOG.error("DOM exception", e);
+ return false;
+ } catch (SAXException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ LOG.error("SAX exception", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Ties to read the timeout from request
+ */
+ private int getTimeout(ITransaction transaction, HttpServletRequest req) {
+
+ int lockDuration = DEFAULT_TIMEOUT;
+ String lockDurationStr = req.getHeader("Timeout");
+
+ if (lockDurationStr == null) {
+ lockDuration = DEFAULT_TIMEOUT;
+ } else {
+ int commaPos = lockDurationStr.indexOf(',');
+ // if multiple timeouts, just use the first one
+ if (commaPos != -1) {
+ lockDurationStr = lockDurationStr.substring(0, commaPos);
+ }
+ if (lockDurationStr.startsWith("Second-")) {
+ lockDuration = new Integer(lockDurationStr.substring(7))
+ .intValue();
+ } else {
+ if (lockDurationStr.equalsIgnoreCase("infinity")) {
+ lockDuration = MAX_TIMEOUT;
+ } else {
+ try {
+ lockDuration = new Integer(lockDurationStr).intValue();
+ } catch (NumberFormatException e) {
+ lockDuration = MAX_TIMEOUT;
+ }
+ }
+ }
+ if (lockDuration <= 0) {
+ lockDuration = DEFAULT_TIMEOUT;
+ }
+ if (lockDuration > MAX_TIMEOUT) {
+ lockDuration = MAX_TIMEOUT;
+ }
+ }
+ return lockDuration;
+ }
+
+ /**
+ * Generates the response XML with all lock information
+ */
+ private void generateXMLReport(ITransaction transaction,
+ HttpServletResponse resp, LockedObject lo) throws IOException {
+
+ HashMap namespaces = new HashMap();
+ namespaces.put("DAV:", "D");
+
+ resp.setStatus(WebdavStatus.SC_OK);
+ resp.setContentType("text/xml; charset=UTF-8");
+
+ XMLWriter generatedXML = new XMLWriter(resp.getWriter(), namespaces);
+ generatedXML.writeXMLHeader();
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::activelock", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
+ generatedXML.writeProperty("DAV::" + _type);
+ generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
+ if (_exclusive) {
+ generatedXML.writeProperty("DAV::exclusive");
+ } else {
+ generatedXML.writeProperty("DAV::shared");
+ }
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
+
+ int depth = lo.getLockDepth();
+
+ generatedXML.writeElement("DAV::depth", XMLWriter.OPENING);
+ if (depth == INFINITY) {
+ generatedXML.writeText("Infinity");
+ } else {
+ generatedXML.writeText(String.valueOf(depth));
+ }
+ generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::owner", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+ generatedXML.writeText(_lockOwner);
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING);
+
+ long timeout = lo.getTimeoutMillis();
+ generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING);
+ generatedXML.writeText("Second-" + timeout / 1000);
+ generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING);
+
+ String lockToken = lo.getID();
+ generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+ generatedXML.writeText("opaquelocktoken:" + lockToken);
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+
+ resp.addHeader("Lock-Token", "");
+
+ generatedXML.sendData();
+
+ }
+
+ /**
+ * Executes the lock for a Mac OS Finder client
+ */
+ private void doMacLockRequestWorkaround(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws LockFailedException, IOException {
+ LockedObject lo;
+ int depth = getDepth(req);
+ int lockDuration = getTimeout(transaction, req);
+ if (lockDuration < 0 || lockDuration > MAX_TIMEOUT)
+ lockDuration = DEFAULT_TIMEOUT;
+
+ boolean lockSuccess = false;
+ lockSuccess = _resourceLocks.exclusiveLock(transaction, _path,
+ _lockOwner, depth, lockDuration);
+
+ if (lockSuccess) {
+ // Locks successfully placed - return information about
+ lo = _resourceLocks.getLockedObjectByPath(transaction, _path);
+ if (lo != null) {
+ generateXMLReport(transaction, resp, lo);
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ // Locking was not successful
+ sendLockFailError(transaction, req, resp);
+ }
+ }
+
+ /**
+ * Sends an error report to the client
+ */
+ private void sendLockFailError(ITransaction transaction,
+ HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+ Hashtable errorList = new Hashtable();
+ errorList.put(_path, WebdavStatus.SC_LOCKED);
+ sendReport(req, resp, errorList);
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoMkcol.java b/src/main/java/net/sf/webdav/methods/DoMkcol.java
new file mode 100644
index 0000000..8eca0ff
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoMkcol.java
@@ -0,0 +1,178 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.IResourceLocks;
+import net.sf.webdav.locking.LockedObject;
+
+public class DoMkcol extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoMkcol.class);
+
+ private IWebdavStore _store;
+ private IResourceLocks _resourceLocks;
+ private boolean _readOnly;
+
+ public DoMkcol(IWebdavStore store, IResourceLocks resourceLocks,
+ boolean readOnly) {
+ _store = store;
+ _resourceLocks = resourceLocks;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (!_readOnly) {
+ String path = getRelativePath(req);
+ String parentPath = getParentPath(getCleanPath(path));
+
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
+ // TODO remove
+ LOG
+ .trace("MkCol on locked resource (parentPath) not executable!"
+ + "\n Sending SC_FORBIDDEN (403) error response!");
+
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+ }
+
+ String tempLockOwner = "doMkcol" + System.currentTimeMillis()
+ + req.toString();
+
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ StoredObject parentSo, so = null;
+ try {
+ parentSo = _store.getStoredObject(transaction, parentPath);
+ if (parentSo == null) {
+ // parent not exists
+ resp.sendError(WebdavStatus.SC_CONFLICT);
+ return;
+ }
+ if (parentPath != null && parentSo.isFolder()) {
+ so = _store.getStoredObject(transaction, path);
+ if (so == null) {
+ _store.createFolder(transaction, path);
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ } else {
+ // object already exists
+ if (so.isNullResource()) {
+
+ LockedObject nullResourceLo = _resourceLocks
+ .getLockedObjectByPath(transaction,
+ path);
+ if (nullResourceLo == null) {
+ resp
+ .sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ String nullResourceLockToken = nullResourceLo
+ .getID();
+ String[] lockTokens = getLockIdFromIfHeader(req);
+ String lockToken = null;
+ if (lockTokens != null)
+ lockToken = lockTokens[0];
+ else {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return;
+ }
+ if (lockToken.equals(nullResourceLockToken)) {
+ so.setNullResource(false);
+ so.setFolder(true);
+
+ String[] nullResourceLockOwners = nullResourceLo
+ .getOwner();
+ String owner = null;
+ if (nullResourceLockOwners != null)
+ owner = nullResourceLockOwners[0];
+
+ if (_resourceLocks.unlock(transaction,
+ lockToken, owner)) {
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ } else {
+ resp
+ .sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ } else {
+ // TODO remove
+ LOG
+ .trace("MkCol on lock-null-resource with wrong lock-token!"
+ + "\n Sending multistatus error report!");
+
+ errorList.put(path, WebdavStatus.SC_LOCKED);
+ sendReport(req, resp, errorList);
+ }
+
+ } else {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp
+ .sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ }
+ }
+
+ } else if (parentPath != null && parentSo.isResource()) {
+ // TODO remove
+ LOG
+ .trace("MkCol on resource is not executable"
+ + "\n Sending SC_METHOD_NOT_ALLOWED (405) error response!");
+
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(parentSo);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ path, tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoMove.java b/src/main/java/net/sf/webdav/methods/DoMove.java
new file mode 100644
index 0000000..188da22
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoMove.java
@@ -0,0 +1,121 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoMove extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoMove.class);
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+ private DoDelete _doDelete;
+ private DoCopy _doCopy;
+ private boolean _readOnly;
+
+ public DoMove(IWebdavStore _store, ResourceLocks resourceLocks, DoDelete doDelete,
+ DoCopy doCopy, boolean readOnly) {
+ this._store = _store;
+ _resourceLocks = resourceLocks;
+ _doDelete = doDelete;
+ _doCopy = doCopy;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+
+ if (!_readOnly) {
+ LOG.trace("-- " + this.getClass().getName());
+
+ String sourcePath = getRelativePath(req);
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, sourcePath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return;
+ }
+
+ String destinationPath = parseDestinationHeader(req, resp);
+ if (destinationPath == null) {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks,
+ destinationPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return;
+ }
+
+ String tempLockOwner = "doMove" + System.currentTimeMillis()
+ + req.toString();
+
+ if (_resourceLocks.lock(transaction, sourcePath, tempLockOwner,
+ false, 0, TEMP_TIMEOUT, TEMPORARY)) {
+ try {
+ boolean ok = _store.moveObject(transaction, destinationPath, sourcePath);
+ if (!ok) {
+ if (_doCopy.copyResource(transaction, req, resp)) {
+
+ errorList = new Hashtable();
+ _doDelete.deleteResource(transaction, sourcePath,
+ errorList, req, resp);
+ if (!errorList.isEmpty()) {
+ sendReport(req, resp, errorList);
+ }
+ }
+ }
+
+
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (ObjectAlreadyExistsException e) {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND, req
+ .getRequestURI());
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ sourcePath, tempLockOwner);
+ }
+ } else {
+ errorList.put(req.getHeader("Destination"),
+ WebdavStatus.SC_LOCKED);
+ sendReport(req, resp, errorList);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoNotImplemented.java b/src/main/java/net/sf/webdav/methods/DoNotImplemented.java
new file mode 100644
index 0000000..e8b8a99
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoNotImplemented.java
@@ -0,0 +1,31 @@
+package net.sf.webdav.methods;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.IMethodExecutor;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.WebdavStatus;
+
+public class DoNotImplemented implements IMethodExecutor {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoNotImplemented.class);
+ private boolean _readOnly;
+
+ public DoNotImplemented(boolean readOnly) {
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException {
+ LOG.trace("-- " + req.getMethod());
+
+ if (_readOnly) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } else
+ resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoOptions.java b/src/main/java/net/sf/webdav/methods/DoOptions.java
new file mode 100644
index 0000000..7df08bf
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoOptions.java
@@ -0,0 +1,75 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.ResourceLocks;
+
+public class DoOptions extends DeterminableMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoOptions.class);
+
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+
+ public DoOptions(IWebdavStore store, ResourceLocks resLocks) {
+ _store = store;
+ _resourceLocks = resLocks;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+
+ LOG.trace("-- " + this.getClass().getName());
+
+ String tempLockOwner = "doOptions" + System.currentTimeMillis()
+ + req.toString();
+ String path = getRelativePath(req);
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ StoredObject so = null;
+ try {
+ resp.addHeader("DAV", "1, 2");
+
+ so = _store.getStoredObject(transaction, path);
+ String methodsAllowed = determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.addHeader("MS-Author-Via", "DAV");
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction, path,
+ tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoPropfind.java b/src/main/java/net/sf/webdav/methods/DoPropfind.java
new file mode 100644
index 0000000..e279a92
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoPropfind.java
@@ -0,0 +1,627 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+
+import net.sf.webdav.IMimeTyper;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.fromcatalina.URLEncoder;
+import net.sf.webdav.fromcatalina.XMLHelper;
+import net.sf.webdav.fromcatalina.XMLWriter;
+import net.sf.webdav.locking.LockedObject;
+import net.sf.webdav.locking.ResourceLocks;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+public class DoPropfind extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoPropfind.class);
+
+ /**
+ * Array containing the safe characters set.
+ */
+ protected static URLEncoder URL_ENCODER;
+
+ /**
+ * PROPFIND - Specify a property mask.
+ */
+ private static final int FIND_BY_PROPERTY = 0;
+
+ /**
+ * PROPFIND - Display all properties.
+ */
+ private static final int FIND_ALL_PROP = 1;
+
+ /**
+ * PROPFIND - Return property names.
+ */
+ private static final int FIND_PROPERTY_NAMES = 2;
+
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+ private IMimeTyper _mimeTyper;
+
+ private int _depth;
+
+ public DoPropfind(IWebdavStore store, ResourceLocks resLocks,
+ IMimeTyper mimeTyper) {
+ _store = store;
+ _resourceLocks = resLocks;
+ _mimeTyper = mimeTyper;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ // Retrieve the resources
+ String path = getCleanPath(getRelativePath(req));
+ String tempLockOwner = "doPropfind" + System.currentTimeMillis()
+ + req.toString();
+ _depth = getDepth(req);
+
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false,
+ _depth, TEMP_TIMEOUT, TEMPORARY)) {
+
+ StoredObject so = null;
+ try {
+ so = _store.getStoredObject(transaction, path);
+ if (so == null) {
+ resp.setContentType("text/xml; charset=UTF-8");
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, req
+ .getRequestURI());
+ return;
+ }
+
+ Vector properties = null;
+ path = getCleanPath(getRelativePath(req));
+
+ int propertyFindType = FIND_ALL_PROP;
+ Node propNode = null;
+
+ if (req.getContentLength() > 0) {
+ DocumentBuilder documentBuilder = getDocumentBuilder();
+ try {
+ Document document = documentBuilder
+ .parse(new InputSource(req.getInputStream()));
+ // Get the root element of the document
+ Element rootElement = document.getDocumentElement();
+
+ propNode = XMLHelper
+ .findSubElement(rootElement, "prop");
+ if (propNode != null) {
+ propertyFindType = FIND_BY_PROPERTY;
+ } else if (XMLHelper.findSubElement(rootElement,
+ "propname") != null) {
+ propertyFindType = FIND_PROPERTY_NAMES;
+ } else if (XMLHelper.findSubElement(rootElement,
+ "allprop") != null) {
+ propertyFindType = FIND_ALL_PROP;
+ }
+ } catch (Exception e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ } else {
+ // no content, which means it is a allprop request
+ propertyFindType = FIND_ALL_PROP;
+ }
+
+ HashMap namespaces = new HashMap();
+ namespaces.put("DAV:", "D");
+
+ if (propertyFindType == FIND_BY_PROPERTY) {
+ propertyFindType = 0;
+ properties = XMLHelper.getPropertiesFromXML(propNode);
+ }
+
+ resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+ resp.setContentType("text/xml; charset=UTF-8");
+
+ // Create multistatus object
+ XMLWriter generatedXML = new XMLWriter(resp.getWriter(),
+ namespaces);
+ generatedXML.writeXMLHeader();
+ generatedXML
+ .writeElement("DAV::multistatus", XMLWriter.OPENING);
+ if (_depth == 0) {
+ parseProperties(transaction, req, generatedXML, path,
+ propertyFindType, properties, _mimeTyper
+ .getMimeType(transaction, path));
+ } else {
+ recursiveParseProperties(transaction, path, req,
+ generatedXML, propertyFindType, properties, _depth,
+ _mimeTyper.getMimeType(transaction, path));
+ }
+ generatedXML
+ .writeElement("DAV::multistatus", XMLWriter.CLOSING);
+
+ generatedXML.sendData();
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (WebdavException e) {
+ LOG.warn("Sending internal error!");
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } catch (ServletException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction, path,
+ tempLockOwner);
+ }
+ } else {
+ Hashtable errorList = new Hashtable();
+ errorList.put(path, WebdavStatus.SC_LOCKED);
+ sendReport(req, resp, errorList);
+ }
+ }
+
+ /**
+ * goes recursive through all folders. used by propfind
+ *
+ * @param currentPath
+ * the current path
+ * @param req
+ * HttpServletRequest
+ * @param generatedXML
+ * @param propertyFindType
+ * @param properties
+ * @param depth
+ * depth of the propfind
+ * @throws IOException
+ * if an error in the underlying store occurs
+ */
+ private void recursiveParseProperties(ITransaction transaction,
+ String currentPath, HttpServletRequest req, XMLWriter generatedXML,
+ int propertyFindType, Vector properties, int depth,
+ String mimeType) throws WebdavException {
+
+ parseProperties(transaction, req, generatedXML, currentPath,
+ propertyFindType, properties, mimeType);
+
+ if (depth > 0) {
+ // no need to get name if depth is already zero
+ String[] names = _store.getChildrenNames(transaction, currentPath);
+ names = names == null ? new String[] {} : names;
+ String newPath = null;
+
+ for (String name : names) {
+ newPath = currentPath;
+ if (!(newPath.endsWith("/"))) {
+ newPath += "/";
+ }
+ newPath += name;
+ recursiveParseProperties(transaction, newPath, req,
+ generatedXML, propertyFindType, properties, depth - 1,
+ mimeType);
+ }
+ }
+ }
+
+ /**
+ * Propfind helper method.
+ *
+ * @param req
+ * The servlet request
+ * @param generatedXML
+ * XML response to the Propfind request
+ * @param path
+ * Path of the current resource
+ * @param type
+ * Propfind type
+ * @param propertiesVector
+ * If the propfind type is find properties by name, then this Vector
+ * contains those properties
+ */
+ private void parseProperties(ITransaction transaction,
+ HttpServletRequest req, XMLWriter generatedXML, String path,
+ int type, Vector propertiesVector, String mimeType)
+ throws WebdavException {
+
+ StoredObject so = _store.getStoredObject(transaction, path);
+
+ boolean isFolder = so.isFolder();
+ final String creationdate = creationDateFormat(so.getCreationDate());
+ final String lastModified = lastModifiedDateFormat(so.getLastModified());
+ String resourceLength = String.valueOf(so.getResourceLength());
+
+ // ResourceInfo resourceInfo = new ResourceInfo(path, resources);
+
+ generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
+ String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " "
+ + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
+
+ // Generating href element
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+
+ String href = req.getContextPath();
+ String servletPath = req.getServletPath();
+ if (servletPath != null) {
+ if ((href.endsWith("/")) && (servletPath.startsWith("/")))
+ href += servletPath.substring(1);
+ else
+ href += servletPath;
+ }
+ if ((href.endsWith("/")) && (path.startsWith("/")))
+ href += path.substring(1);
+ else
+ href += path;
+ if ((isFolder) && (!href.endsWith("/")))
+ href += "/";
+
+ generatedXML.writeText(rewriteUrl(href));
+
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+
+ String resourceName = path;
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash != -1)
+ resourceName = resourceName.substring(lastSlash + 1);
+
+ switch (type) {
+
+ case FIND_ALL_PROP:
+
+ generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+
+ generatedXML.writeProperty("DAV::creationdate", creationdate);
+ generatedXML.writeElement("DAV::displayname", XMLWriter.OPENING);
+ generatedXML.writeData(resourceName);
+ generatedXML.writeElement("DAV::displayname", XMLWriter.CLOSING);
+ if (!isFolder) {
+ generatedXML
+ .writeProperty("DAV::getlastmodified", lastModified);
+ generatedXML.writeProperty("DAV::getcontentlength",
+ resourceLength);
+ String contentType = mimeType;
+ if (contentType != null) {
+ generatedXML.writeProperty("DAV::getcontenttype",
+ contentType);
+ }
+ generatedXML.writeProperty("DAV::getetag", getETag(so));
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.NO_CONTENT);
+ } else {
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::collection",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.CLOSING);
+ }
+
+ writeSupportedLockElements(transaction, generatedXML, path);
+
+ writeLockDiscoveryElements(transaction, generatedXML, path);
+
+ generatedXML.writeProperty("DAV::source", "");
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText(status);
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
+
+ break;
+
+ case FIND_PROPERTY_NAMES:
+
+ generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+
+ generatedXML
+ .writeElement("DAV::creationdate", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::displayname", XMLWriter.NO_CONTENT);
+ if (!isFolder) {
+ generatedXML.writeElement("DAV::getcontentlanguage",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::getcontentlength",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::getcontenttype",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::getetag", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::getlastmodified",
+ XMLWriter.NO_CONTENT);
+ }
+ generatedXML
+ .writeElement("DAV::resourcetype", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::supportedlock",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::source", XMLWriter.NO_CONTENT);
+
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText(status);
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
+
+ break;
+
+ case FIND_BY_PROPERTY:
+
+ Vector propertiesNotFound = new Vector();
+
+ // Parse the list of properties
+
+ generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+
+ Enumeration properties = propertiesVector.elements();
+
+ while (properties.hasMoreElements()) {
+
+ String property = (String) properties.nextElement();
+
+ if (property.equals("DAV::creationdate")) {
+ generatedXML.writeProperty("DAV::creationdate",
+ creationdate);
+ } else if (property.equals("DAV::displayname")) {
+ generatedXML.writeElement("DAV::displayname",
+ XMLWriter.OPENING);
+ generatedXML.writeData(resourceName);
+ generatedXML.writeElement("DAV::displayname",
+ XMLWriter.CLOSING);
+ } else if (property.equals("DAV::getcontentlanguage")) {
+ if (isFolder) {
+ propertiesNotFound.addElement(property);
+ } else {
+ generatedXML.writeElement("DAV::getcontentlanguage",
+ XMLWriter.NO_CONTENT);
+ }
+ } else if (property.equals("DAV::getcontentlength")) {
+ if (isFolder) {
+ propertiesNotFound.addElement(property);
+ } else {
+ generatedXML.writeProperty("DAV::getcontentlength",
+ resourceLength);
+ }
+ } else if (property.equals("DAV::getcontenttype")) {
+ if (isFolder) {
+ propertiesNotFound.addElement(property);
+ } else {
+ generatedXML.writeProperty("DAV::getcontenttype",
+ mimeType);
+ }
+ } else if (property.equals("DAV::getetag")) {
+ if (isFolder || so.isNullResource()) {
+ propertiesNotFound.addElement(property);
+ } else {
+ generatedXML.writeProperty("DAV::getetag", getETag(so));
+ }
+ } else if (property.equals("DAV::getlastmodified")) {
+ if (isFolder) {
+ propertiesNotFound.addElement(property);
+ } else {
+ generatedXML.writeProperty("DAV::getlastmodified",
+ lastModified);
+ }
+ } else if (property.equals("DAV::resourcetype")) {
+ if (isFolder) {
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::collection",
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.CLOSING);
+ } else {
+ generatedXML.writeElement("DAV::resourcetype",
+ XMLWriter.NO_CONTENT);
+ }
+ } else if (property.equals("DAV::source")) {
+ generatedXML.writeProperty("DAV::source", "");
+ } else if (property.equals("DAV::supportedlock")) {
+
+ writeSupportedLockElements(transaction, generatedXML, path);
+
+ } else if (property.equals("DAV::lockdiscovery")) {
+
+ writeLockDiscoveryElements(transaction, generatedXML, path);
+
+ } else {
+ propertiesNotFound.addElement(property);
+ }
+
+ }
+
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText(status);
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
+
+ Enumeration propertiesNotFoundList = propertiesNotFound
+ .elements();
+
+ if (propertiesNotFoundList.hasMoreElements()) {
+
+ status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+ + " "
+ + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
+
+ generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+
+ while (propertiesNotFoundList.hasMoreElements()) {
+ generatedXML.writeElement((String) propertiesNotFoundList
+ .nextElement(), XMLWriter.NO_CONTENT);
+ }
+
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText(status);
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
+
+ }
+
+ break;
+
+ }
+
+ generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
+
+ so = null;
+ }
+
+ private void writeSupportedLockElements(ITransaction transaction,
+ XMLWriter generatedXML, String path) {
+
+ LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
+ path);
+
+ generatedXML.writeElement("DAV::supportedlock", XMLWriter.OPENING);
+
+ if (lo == null) {
+ // both locks (shared/exclusive) can be granted
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::exclusive", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::write", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::shared", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::write", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
+
+ } else {
+ // LockObject exists, checking lock state
+ // if an exclusive lock exists, no further lock is possible
+ if (lo.isShared()) {
+
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::shared", XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::" + lo.getType(),
+ XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
+ }
+ }
+
+ generatedXML.writeElement("DAV::supportedlock", XMLWriter.CLOSING);
+
+ lo = null;
+ }
+
+ private void writeLockDiscoveryElements(ITransaction transaction,
+ XMLWriter generatedXML, String path) {
+
+ LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
+ path);
+
+ if (lo != null && !lo.hasExpired()) {
+
+ generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::activelock", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
+ generatedXML.writeProperty("DAV::" + lo.getType());
+ generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
+ if (lo.isExclusive()) {
+ generatedXML.writeProperty("DAV::exclusive");
+ } else {
+ generatedXML.writeProperty("DAV::shared");
+ }
+ generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::depth", XMLWriter.OPENING);
+ if (_depth == INFINITY) {
+ generatedXML.writeText("Infinity");
+ } else {
+ generatedXML.writeText(String.valueOf(_depth));
+ }
+ generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING);
+
+ String[] owners = lo.getOwner();
+ if (owners != null) {
+ for (int i = 0; i < owners.length; i++) {
+ generatedXML.writeElement("DAV::owner", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+ generatedXML.writeText(owners[i]);
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING);
+ }
+ } else {
+ generatedXML.writeElement("DAV::owner", XMLWriter.NO_CONTENT);
+ }
+
+ int timeout = (int) (lo.getTimeoutMillis() / 1000);
+ String timeoutStr = new Integer(timeout).toString();
+ generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING);
+ generatedXML.writeText("Second-" + timeoutStr);
+ generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING);
+
+ String lockToken = lo.getID();
+
+ generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING);
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+ generatedXML.writeText("opaquelocktoken:" + lockToken);
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING);
+ generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING);
+
+ } else {
+ generatedXML.writeElement("DAV::lockdiscovery",
+ XMLWriter.NO_CONTENT);
+ }
+
+ lo = null;
+ }
+
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoProppatch.java b/src/main/java/net/sf/webdav/methods/DoProppatch.java
new file mode 100644
index 0000000..afeaca7
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoProppatch.java
@@ -0,0 +1,228 @@
+package net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.fromcatalina.XMLHelper;
+import net.sf.webdav.fromcatalina.XMLWriter;
+import net.sf.webdav.locking.LockedObject;
+import net.sf.webdav.locking.ResourceLocks;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+public class DoProppatch extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoProppatch.class);
+
+ private boolean _readOnly;
+ private IWebdavStore _store;
+ private ResourceLocks _resourceLocks;
+
+ public DoProppatch(IWebdavStore store, ResourceLocks resLocks,
+ boolean readOnly) {
+ _readOnly = readOnly;
+ _store = store;
+ _resourceLocks = resLocks;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (_readOnly) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+ }
+
+ String path = getRelativePath(req);
+ String parentPath = getParentPath(getCleanPath(path));
+
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // parent is locked
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // resource is locked
+ }
+
+ // TODO for now, PROPPATCH just sends a valid response, stating that
+ // everything is fine, but doesn't do anything.
+
+ // Retrieve the resources
+ String tempLockOwner = "doProppatch" + System.currentTimeMillis()
+ + req.toString();
+
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ StoredObject so = null;
+ LockedObject lo = null;
+ try {
+ so = _store.getStoredObject(transaction, path);
+ lo = _resourceLocks.getLockedObjectByPath(transaction,
+ getCleanPath(path));
+
+ if (so == null) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ // we do not to continue since there is no root
+ // resource
+ }
+
+ if (so.isNullResource()) {
+ String methodsAllowed = DeterminableMethod
+ .determineMethodsAllowed(so);
+ resp.addHeader("Allow", methodsAllowed);
+ resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ String[] lockTokens = getLockIdFromIfHeader(req);
+ boolean lockTokenMatchesIfHeader = (lockTokens != null && lockTokens[0].equals(lo.getID()));
+ if (lo != null && lo.isExclusive() && !lockTokenMatchesIfHeader) {
+ // Object on specified path is LOCKED
+ errorList = new Hashtable();
+ errorList.put(path, new Integer(WebdavStatus.SC_LOCKED));
+ sendReport(req, resp, errorList);
+ return;
+ }
+
+ List toset = null;
+ List toremove = null;
+ List tochange = new Vector();
+ // contains all properties from
+ // toset and toremove
+
+ path = getCleanPath(getRelativePath(req));
+
+ Node tosetNode = null;
+ Node toremoveNode = null;
+
+ if (req.getContentLength() != 0) {
+ DocumentBuilder documentBuilder = getDocumentBuilder();
+ try {
+ Document document = documentBuilder
+ .parse(new InputSource(req.getInputStream()));
+ // Get the root element of the document
+ Element rootElement = document.getDocumentElement();
+
+ tosetNode = XMLHelper.findSubElement(XMLHelper
+ .findSubElement(rootElement, "set"), "prop");
+ toremoveNode = XMLHelper.findSubElement(XMLHelper
+ .findSubElement(rootElement, "remove"), "prop");
+ } catch (Exception e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ } else {
+ // no content: error
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ HashMap namespaces = new HashMap();
+ namespaces.put("DAV:", "D");
+
+ if (tosetNode != null) {
+ toset = XMLHelper.getPropertiesFromXML(tosetNode);
+ tochange.addAll(toset);
+ }
+
+ if (toremoveNode != null) {
+ toremove = XMLHelper.getPropertiesFromXML(toremoveNode);
+ tochange.addAll(toremove);
+ }
+
+ resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+ resp.setContentType("text/xml; charset=UTF-8");
+
+ // Create multistatus object
+ XMLWriter generatedXML = new XMLWriter(resp.getWriter(),
+ namespaces);
+ generatedXML.writeXMLHeader();
+ generatedXML
+ .writeElement("DAV::multistatus", XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
+ String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
+ + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
+
+ // Generating href element
+ generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
+
+ String href = req.getContextPath();
+ if ((href.endsWith("/")) && (path.startsWith("/")))
+ href += path.substring(1);
+ else
+ href += path;
+ if ((so.isFolder()) && (!href.endsWith("/")))
+ href += "/";
+
+ generatedXML.writeText(rewriteUrl(href));
+
+ generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
+
+ for (Iterator iter = tochange.iterator(); iter
+ .hasNext();) {
+ String property = (String) iter.next();
+
+ generatedXML.writeElement("DAV::propstat",
+ XMLWriter.OPENING);
+
+ generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
+ generatedXML.writeElement(property, XMLWriter.NO_CONTENT);
+ generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
+ generatedXML.writeText(status);
+ generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
+
+ generatedXML.writeElement("DAV::propstat",
+ XMLWriter.CLOSING);
+ }
+
+ generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
+
+ generatedXML
+ .writeElement("DAV::multistatus", XMLWriter.CLOSING);
+
+ generatedXML.sendData();
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } catch (ServletException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction, path,
+ tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoPut.java b/src/main/java/net/sf/webdav/methods/DoPut.java
new file mode 100644
index 0000000..49f5a2e
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoPut.java
@@ -0,0 +1,195 @@
+/*
+ * 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 net.sf.webdav.methods;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.exceptions.AccessDeniedException;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.exceptions.WebdavException;
+import net.sf.webdav.locking.IResourceLocks;
+import net.sf.webdav.locking.LockedObject;
+
+public class DoPut extends AbstractMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoPut.class);
+
+ private IWebdavStore _store;
+ private IResourceLocks _resourceLocks;
+ private boolean _readOnly;
+ private boolean _lazyFolderCreationOnPut;
+
+ private String _userAgent;
+
+ public DoPut(IWebdavStore store, IResourceLocks resLocks, boolean readOnly,
+ boolean lazyFolderCreationOnPut) {
+ _store = store;
+ _resourceLocks = resLocks;
+ _readOnly = readOnly;
+ _lazyFolderCreationOnPut = lazyFolderCreationOnPut;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (!_readOnly) {
+ String path = getRelativePath(req);
+ String parentPath = getParentPath(path);
+
+ _userAgent = req.getHeader("User-Agent");
+
+ Hashtable errorList = new Hashtable();
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // parent is locked
+ }
+
+ if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
+ resp.setStatus(WebdavStatus.SC_LOCKED);
+ return; // resource is locked
+ }
+
+ String tempLockOwner = "doPut" + System.currentTimeMillis()
+ + req.toString();
+ if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
+ TEMP_TIMEOUT, TEMPORARY)) {
+ StoredObject parentSo, so = null;
+ try {
+ parentSo = _store.getStoredObject(transaction, parentPath);
+ if (parentPath != null && parentSo != null
+ && parentSo.isResource()) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+
+ } else if (parentPath != null && parentSo == null
+ && _lazyFolderCreationOnPut) {
+ _store.createFolder(transaction, parentPath);
+
+ } else if (parentPath != null && parentSo == null
+ && !_lazyFolderCreationOnPut) {
+ errorList.put(parentPath, WebdavStatus.SC_NOT_FOUND);
+ sendReport(req, resp, errorList);
+ return;
+ }
+
+ so = _store.getStoredObject(transaction, path);
+
+ if (so == null) {
+ _store.createResource(transaction, path);
+ // resp.setStatus(WebdavStatus.SC_CREATED);
+ } else {
+ // This has already been created, just update the data
+ if (so.isNullResource()) {
+
+ LockedObject nullResourceLo = _resourceLocks
+ .getLockedObjectByPath(transaction, path);
+ if (nullResourceLo == null) {
+ resp
+ .sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ String nullResourceLockToken = nullResourceLo
+ .getID();
+ String[] lockTokens = getLockIdFromIfHeader(req);
+ String lockToken = null;
+ if (lockTokens != null) {
+ lockToken = lockTokens[0];
+ } else {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return;
+ }
+ if (lockToken.equals(nullResourceLockToken)) {
+ so.setNullResource(false);
+ so.setFolder(false);
+
+ String[] nullResourceLockOwners = nullResourceLo
+ .getOwner();
+ String owner = null;
+ if (nullResourceLockOwners != null)
+ owner = nullResourceLockOwners[0];
+
+ if (!_resourceLocks.unlock(transaction,
+ lockToken, owner)) {
+ resp
+ .sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ errorList.put(path, WebdavStatus.SC_LOCKED);
+ sendReport(req, resp, errorList);
+ }
+ }
+ }
+ // User-Agent workarounds
+ doUserAgentWorkaround(resp);
+
+ // setting resourceContent
+ long resourceLength = _store
+ .setResourceContent(transaction, path, req
+ .getInputStream(), null, null);
+
+ so = _store.getStoredObject(transaction, path);
+ if (resourceLength > 0)
+ so.setResourceLength(resourceLength);
+ // Now lets report back what was actually saved
+
+ } catch (AccessDeniedException e) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ } catch (WebdavException e) {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction,
+ path, tempLockOwner);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+ }
+ } else {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ }
+
+ }
+
+ /**
+ * @param resp
+ */
+ private void doUserAgentWorkaround(HttpServletResponse resp) {
+ if (_userAgent != null && _userAgent.indexOf("WebDAVFS") != -1
+ && _userAgent.indexOf("Transmit") == -1) {
+ LOG.trace("DoPut.execute() : do workaround for user agent '"
+ + _userAgent + "'");
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ } else if (_userAgent != null && _userAgent.indexOf("Transmit") != -1) {
+ // Transmit also uses WEBDAVFS 1.x.x but crashes
+ // with SC_CREATED response
+ LOG.trace("DoPut.execute() : do workaround for user agent '"
+ + _userAgent + "'");
+ resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+ } else {
+ resp.setStatus(WebdavStatus.SC_CREATED);
+ }
+ }
+}
diff --git a/src/main/java/net/sf/webdav/methods/DoUnlock.java b/src/main/java/net/sf/webdav/methods/DoUnlock.java
new file mode 100644
index 0000000..a5fe3c9
--- /dev/null
+++ b/src/main/java/net/sf/webdav/methods/DoUnlock.java
@@ -0,0 +1,98 @@
+package net.sf.webdav.methods;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.webdav.StoredObject;
+import net.sf.webdav.ITransaction;
+import net.sf.webdav.WebdavStatus;
+import net.sf.webdav.IWebdavStore;
+import net.sf.webdav.exceptions.LockFailedException;
+import net.sf.webdav.locking.IResourceLocks;
+import net.sf.webdav.locking.LockedObject;
+
+public class DoUnlock extends DeterminableMethod {
+
+ private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
+ .getLogger(DoUnlock.class);
+
+ private IWebdavStore _store;
+ private IResourceLocks _resourceLocks;
+ private boolean _readOnly;
+
+ public DoUnlock(IWebdavStore store, IResourceLocks resourceLocks,
+ boolean readOnly) {
+ _store = store;
+ _resourceLocks = resourceLocks;
+ _readOnly = readOnly;
+ }
+
+ public void execute(ITransaction transaction, HttpServletRequest req,
+ HttpServletResponse resp) throws IOException, LockFailedException {
+ LOG.trace("-- " + this.getClass().getName());
+
+ if (_readOnly) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return;
+ } else {
+
+ String path = getRelativePath(req);
+ String tempLockOwner = "doUnlock" + System.currentTimeMillis()
+ + req.toString();
+ try {
+ if (_resourceLocks.lock(transaction, path, tempLockOwner,
+ false, 0, TEMP_TIMEOUT, TEMPORARY)) {
+
+ String lockId = getLockIdFromLockTokenHeader(req);
+ LockedObject lo;
+ if (lockId != null
+ && ((lo = _resourceLocks.getLockedObjectByID(
+ transaction, lockId)) != null)) {
+
+ String[] owners = lo.getOwner();
+ String owner = null;
+ if (lo.isShared()) {
+ // more than one owner is possible
+ if (owners != null) {
+ for (int i = 0; i < owners.length; i++) {
+ // remove owner from LockedObject
+ lo.removeLockedObjectOwner(owners[i]);
+ }
+ }
+ } else {
+ // exclusive, only one lock owner
+ if (owners != null)
+ owner = owners[0];
+ else
+ owner = null;
+ }
+
+ if (_resourceLocks.unlock(transaction, lockId, owner)) {
+ StoredObject so = _store.getStoredObject(
+ transaction, path);
+ if (so.isNullResource()) {
+ _store.removeObject(transaction, path);
+ }
+
+ resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+ } else {
+ LOG.trace("DoUnlock failure at " + lo.getPath());
+ resp.sendError(WebdavStatus.SC_METHOD_FAILURE);
+ }
+
+ } else {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ }
+ }
+ } catch (LockFailedException e) {
+ e.printStackTrace();
+ } finally {
+ _resourceLocks.unlockTemporaryLockedObjects(transaction, path,
+ tempLockOwner);
+ }
+ }
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..e503ead
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1 @@
+#logging.level.net.sf.webdav=trace
\ No newline at end of file
diff --git a/src/test/java/com/github/zxbu/webdavteambition/WebdavTeambitionApplicationTests.java b/src/test/java/com/github/zxbu/webdavteambition/WebdavTeambitionApplicationTests.java
new file mode 100644
index 0000000..8656acc
--- /dev/null
+++ b/src/test/java/com/github/zxbu/webdavteambition/WebdavTeambitionApplicationTests.java
@@ -0,0 +1,13 @@
+package com.github.zxbu.webdavteambition;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class WebdavTeambitionApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}