commit - c4610f866124bd70c2c5eee691a35ecd94ab2487
commit + 09048cb93e22e7d59bcf356673986574a7979a30
blob - /dev/null
blob + 6ce51b79ebc9ab0be5de9ae2c702d8799a4835f6 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/CopyHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSelectInfo;
+import org.apache.commons.vfs.FileSelector;
+import org.apache.commons.vfs.FileSystemException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class CopyHandler extends CopyMoveBase {
+ protected void copyOrMove(FileObject object, FileObject target, final int depth) throws FileSystemException {
+ target.copyFrom(object, new FileSelector() {
+ public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+ return fileSelectInfo.getDepth() <= depth;
+ }
+
+ public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
+ return fileSelectInfo.getDepth() < depth;
+ }
+ });
+ }
+}
blob - /dev/null
blob + 673a2a9aef5ecaff2c19f0317fa21de8a46d2320 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/CopyMoveBase.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.LockException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public abstract class CopyMoveBase extends WebdavHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ boolean overwrite = getOverwrite(request);
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+ FileObject targetObject = getDestination(request);
+
+
+ try {
+ final LockManager lockManager = LockManager.getInstance();
+ LockManager.EvaluationResult evaluation = lockManager.evaluateCondition(targetObject, getIf(request));
+ if (!evaluation.result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ if ("MOVE".equals(request.getMethod())) {
+ evaluation = lockManager.evaluateCondition(object, getIf(request));
+ if (!evaluation.result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ }
+ } catch (LockException e) {
+ response.sendError(SC_LOCKED);
+ return;
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+
+ if (null == targetObject) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (object.equals(targetObject)) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ if (targetObject.exists()) {
+ if (!overwrite) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ } else {
+ FileObject targetParent = targetObject.getParent();
+ if (!targetParent.exists() ||
+ !FileType.FOLDER.equals(targetParent.getType())) {
+ response.sendError(HttpServletResponse.SC_CONFLICT);
+ }
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ }
+
+ copyOrMove(object, targetObject, getDepth(request));
+ }
+
+ protected abstract void copyOrMove(FileObject object, FileObject target, int depth) throws FileSystemException;
+
+}
blob - /dev/null
blob + 09b60a7b33e721815fda022a1e7167429e306ca8 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/DeleteHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.LockException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSelectInfo;
+import org.apache.commons.vfs.FileSelector;
+import org.mortbay.jetty.Request;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DeleteHandler extends WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(DeleteHandler.class);
+
+ private final static FileSelector ALL_FILES_SELECTOR = new FileSelector() {
+ public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+ return true;
+ }
+
+ public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
+ return true;
+ }
+ };
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+ if (request instanceof Request) {
+ String fragment = ((Request) request).getUri().getFragment();
+ if (fragment != null) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+
+ try {
+ if (!LockManager.getInstance().evaluateCondition(object, getIf(request)).result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ } catch (LockException e) {
+ response.sendError(SC_LOCKED);
+ return;
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+ if (object.exists()) {
+ int deletedObjects = object.delete(ALL_FILES_SELECTOR);
+ LOG.debug("deleted " + deletedObjects + " objects");
+ if (deletedObjects > 0) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ } else {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+}
blob - /dev/null
blob + da2dfd1e8b0b8b132cea073670f143a461a16b91 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/vfs/DepthFileSelector.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav.vfs;
+
+import org.apache.commons.vfs.FileSelectInfo;
+import org.apache.commons.vfs.FileSelector;
+
+/**
+ * A file selector that operates depth of the directory structure and will
+ * select all files up to and including the depth given in the constructor.
+ *
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class DepthFileSelector implements FileSelector {
+ private final int maxDepth;
+ private final int minDepth;
+
+ /**
+ * Create a file selector that will select ALL files.
+ */
+ public DepthFileSelector() {
+ this(0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Create a file selector that will select all files up to and including
+ * the directory depth.
+ *
+ * @param depth the maximum depth
+ */
+ public DepthFileSelector(int depth) {
+ this(0, depth);
+ }
+
+ public DepthFileSelector(int min, int max) {
+ minDepth = min;
+ maxDepth = max;
+ }
+
+ public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
+ int depth = fileSelectInfo.getDepth();
+ return depth >= minDepth && depth <= maxDepth;
+ }
+
+ public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
+ return fileSelectInfo.getDepth() < maxDepth;
+ }
+}
blob - /dev/null
blob + ba1bb8e542feecaca819e506aaa13f7b8be94cfa (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/GetHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.vfs.FileContent;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class GetHandler extends WebdavHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ if (object.exists()) {
+ if (FileType.FOLDER.equals(object.getType())) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ setHeader(response, object.getContent());
+
+ InputStream is = object.getContent().getInputStream();
+ OutputStream os = response.getOutputStream();
+ Util.copyStream(is, os);
+ is.close();
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+
+ void setHeader(HttpServletResponse response, FileContent content) throws FileSystemException {
+ response.setHeader("Last-Modified", Util.getDateString(content.getLastModifiedTime()));
+ response.setHeader("Content-Type", content.getContentInfo().getContentType());
+ response.setHeader("ETag", String.format("%x", content.getFile().hashCode()));
+ }
+
+
+}
blob - /dev/null
blob + 96f6528d7d218c2dc9f6d4e8843711e802848a02 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/HeadHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class HeadHandler extends GetHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ if (object.exists()) {
+ if (FileType.FOLDER.equals(object.getType())) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ } else {
+ setHeader(response, object.getContent());
+ }
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+}
blob - /dev/null
blob + 204d06d06be8367f5bb86da172606b16b3c525ab (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/LockHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.Lock;
+import com.thinkberg.webdav.lock.LockConflictException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.dom4j.*;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Handle WebDAV LOCK requests.
+ *
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class LockHandler extends WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(LockHandler.class);
+
+ private static final String TAG_LOCKSCOPE = "lockscope";
+ private static final String TAG_LOCKTYPE = "locktype";
+ private static final String TAG_OWNER = "owner";
+ private static final String TAG_HREF = "href";
+ private static final String TAG_PROP = "prop";
+ private static final String TAG_LOCKDISCOVERY = "lockdiscovery";
+
+ private static final String HEADER_LOCK_TOKEN = "Lock-Token";
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ try {
+ final LockManager manager = LockManager.getInstance();
+ final LockManager.EvaluationResult evaluation = manager.evaluateCondition(object, getIf(request));
+ if (!evaluation.result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ } else {
+ if (!evaluation.locks.isEmpty()) {
+ LOG.debug(String.format("discovered locks: %s", evaluation.locks));
+ sendLockAcquiredResponse(response, evaluation.locks.get(0));
+ return;
+ }
+ }
+ } catch (LockConflictException e) {
+ List<Lock> locks = e.getLocks();
+ for (Lock lock : locks) {
+ if (Lock.EXCLUSIVE.equals(lock.getType())) {
+ response.sendError(SC_LOCKED);
+ return;
+ }
+ }
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+ try {
+ SAXReader saxReader = new SAXReader();
+ Document lockInfo = saxReader.read(request.getInputStream());
+ //log(lockInfo);
+
+ Element rootEl = lockInfo.getRootElement();
+ String lockScope = null, lockType = null;
+ Object owner = null;
+ Iterator elIt = rootEl.elementIterator();
+ while (elIt.hasNext()) {
+ Element el = (Element) elIt.next();
+ if (TAG_LOCKSCOPE.equals(el.getName())) {
+ lockScope = el.selectSingleNode("*").getName();
+ } else if (TAG_LOCKTYPE.equals(el.getName())) {
+ lockType = el.selectSingleNode("*").getName();
+ } else if (TAG_OWNER.equals(el.getName())) {
+ // TODO correctly handle owner
+ Node subEl = el.selectSingleNode("*");
+ if (subEl != null && TAG_HREF.equals(subEl.getName())) {
+ owner = new URL(el.selectSingleNode("*").getText());
+ } else {
+ owner = el.getText();
+ }
+ }
+ }
+
+ LOG.debug("LOCK(" + lockType + ", " + lockScope + ", " + owner + ")");
+
+ Lock requestedLock = new Lock(object, lockType, lockScope, owner, getDepth(request), getTimeout(request));
+ try {
+ LockManager.getInstance().acquireLock(requestedLock);
+ sendLockAcquiredResponse(response, requestedLock);
+ } catch (LockConflictException e) {
+ response.sendError(SC_LOCKED);
+ } catch (IllegalArgumentException e) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ } catch (DocumentException e) {
+ e.printStackTrace();
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ }
+
+ private void sendLockAcquiredResponse(HttpServletResponse response, Lock lock) throws IOException {
+ if (!lock.getObject().exists()) {
+ response.setStatus(SC_CREATED);
+ }
+ response.setContentType("text/xml");
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader(HEADER_LOCK_TOKEN, "<" + lock.getToken() + ">");
+
+ Document propDoc = DocumentHelper.createDocument();
+ Element propEl = propDoc.addElement(TAG_PROP, "DAV:");
+ Element lockdiscoveryEl = propEl.addElement(TAG_LOCKDISCOVERY);
+
+ lock.serializeToXml(lockdiscoveryEl);
+
+ XMLWriter xmlWriter = new XMLWriter(response.getWriter());
+ xmlWriter.write(propDoc);
+
+ logXml(propDoc);
+ }
+
+ private void logXml(Node element) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
+ xmlWriter.write(element);
+ LOG.debug(bos.toString());
+ } catch (IOException e) {
+ LOG.debug("ERROR writing XML log: " + e.getMessage());
+ }
+ }
+}
blob - /dev/null
blob + f6b909f7909935b5eeb8a150694a36d899dd37a1 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/MkColHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.LockException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class MkColHandler extends WebdavHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ BufferedReader bufferedReader = request.getReader();
+ String line = bufferedReader.readLine();
+ if (line != null) {
+ response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
+ return;
+ }
+
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ try {
+ if (!LockManager.getInstance().evaluateCondition(object, getIf(request)).result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ } catch (LockException e) {
+ response.sendError(SC_LOCKED);
+ return;
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+ if (object.exists()) {
+ response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ if (!object.getParent().exists() || !FileType.FOLDER.equals(object.getParent().getType())) {
+ response.sendError(HttpServletResponse.SC_CONFLICT);
+ return;
+ }
+
+ try {
+ object.createFolder();
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ } catch (FileSystemException e) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ }
+}
blob - /dev/null
blob + 21fb7f4fd07568da5160aadb3563b7b18d3ed5f1 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/MoveHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.vfs.DepthFileSelector;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class MoveHandler extends CopyMoveBase {
+ protected void copyOrMove(FileObject object, FileObject target, int depth) throws FileSystemException {
+ target.copyFrom(object, new DepthFileSelector(depth));
+ object.delete(new DepthFileSelector());
+ }
+}
blob - /dev/null
blob + 6058cbe74bff642e068a09137748ef65593fb12d (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/OptionsHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class OptionsHandler extends WebdavHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.setHeader("DAV", "1, 2");
+
+ String path = request.getPathInfo();
+ StringBuffer options = new StringBuffer();
+ FileObject object = VFSBackend.resolveFile(path);
+ if (object.exists()) {
+ options.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE, COPY, MOVE, LOCK, UNLOCK, PROPFIND");
+ if (FileType.FOLDER.equals(object.getType())) {
+ options.append(", PUT");
+ }
+ } else {
+ options.append("OPTIONS, MKCOL, PUT, LOCK");
+ }
+ response.setHeader("Allow", options.toString());
+
+ // see: http://www-128.ibm.com/developerworks/rational/library/2089.html
+ response.setHeader("MS-Author-Via", "DAV");
+ }
+}
blob - /dev/null
blob + a85eb352d826917304160ce46e2bde264bed1e40 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PostHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class PostHandler extends WebdavHandler {
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
+}
blob - /dev/null
blob + 00a7383b8d339f22c7242ad22205a99bfaec87cf (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PropFindHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.data.DavResource;
+import com.thinkberg.webdav.data.DavResourceFactory;
+import com.thinkberg.webdav.vfs.DepthFileSelector;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.dom4j.*;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class PropFindHandler extends WebdavHandler {
+ private static final String TAG_PROP = "prop";
+ private static final String TAG_ALLPROP = "allprop";
+ private static final String TAG_PROPNAMES = "propnames";
+ private static final String TAG_MULTISTATUS = "multistatus";
+ private static final String TAG_HREF = "href";
+ private static final String TAG_RESPONSE = "response";
+ private static final Log LOG = LogFactory.getLog(PropFindHandler.class);
+
+ void logXml(Node element) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ XMLWriter xmlWriter = new XMLWriter(bos, OutputFormat.createPrettyPrint());
+ xmlWriter.write(element);
+ LOG.debug(bos.toString());
+ } catch (IOException e) {
+ LOG.error(e.getMessage());
+ }
+ }
+
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ SAXReader saxReader = new SAXReader();
+ try {
+ Document propDoc = saxReader.read(request.getInputStream());
+ logXml(propDoc);
+
+ Element propFindEl = propDoc.getRootElement();
+ Element propEl = (Element) propFindEl.elementIterator().next();
+ String propElName = propEl.getName();
+
+ List<String> requestedProperties = new ArrayList<String>();
+ boolean ignoreValues = false;
+ if (TAG_PROP.equals(propElName)) {
+ for (Object id : propEl.elements()) {
+ requestedProperties.add(((Element) id).getName());
+ }
+ } else if (TAG_ALLPROP.equals(propElName)) {
+ requestedProperties = DavResource.ALL_PROPERTIES;
+ } else if (TAG_PROPNAMES.equals(propElName)) {
+ requestedProperties = DavResource.ALL_PROPERTIES;
+ ignoreValues = true;
+ }
+
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+ if (object.exists()) {
+ // respond as XML encoded multi status
+ response.setContentType("text/xml");
+ response.setCharacterEncoding("UTF-8");
+ response.setStatus(SC_MULTI_STATUS);
+
+ Document multiStatusResponse =
+ getMultiStatusRespons(object,
+ requestedProperties,
+ getBaseUrl(request),
+ getDepth(request),
+ ignoreValues);
+ logXml(multiStatusResponse);
+
+ // write the actual response
+ XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
+ writer.write(multiStatusResponse);
+ writer.flush();
+ writer.close();
+
+ } else {
+ LOG.error(object.getName().getPath() + " NOT FOUND");
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } catch (DocumentException e) {
+ LOG.error("invalid request: " + e.getMessage());
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ }
+
+ @SuppressWarnings({"ConstantConditions"})
+ private Document getMultiStatusRespons(FileObject object,
+ List<String> requestedProperties,
+ URL baseUrl,
+ int depth,
+ boolean ignoreValues) throws FileSystemException {
+ Document propDoc = DocumentHelper.createDocument();
+ propDoc.setXMLEncoding("UTF-8");
+
+ Element multiStatus = propDoc.addElement(TAG_MULTISTATUS, "DAV:");
+ FileObject[] children = object.findFiles(new DepthFileSelector(depth));
+ for (FileObject child : children) {
+ Element responseEl = multiStatus.addElement(TAG_RESPONSE);
+ try {
+ URL url = new URL(baseUrl, URLEncoder.encode(child.getName().getPath(), "UTF-8"));
+ LOG.debug(url);
+ responseEl.addElement(TAG_HREF).addText(url.toExternalForm());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ DavResource resource = DavResourceFactory.getInstance().getDavResource(child);
+ resource.setIgnoreValues(ignoreValues);
+ resource.serializeToXml(responseEl, requestedProperties);
+ }
+ return propDoc;
+ }
+}
blob - /dev/null
blob + 07ffae65a4113a2f5526d060bb9abd511fc50a5a (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PropPatchHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.data.DavResource;
+import com.thinkberg.webdav.lock.LockException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * Handle PROPPATCH requests. This currently a dummy only and will return a
+ * forbidden status for any attempt to modify or remove a property.
+ *
+ * @author Matthias L. Jugel
+ */
+public class PropPatchHandler extends WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(PropPatchHandler.class);
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ try {
+ if (!LockManager.getInstance().evaluateCondition(object, getIf(request)).result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ } catch (LockException e) {
+ response.sendError(SC_LOCKED);
+ return;
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+
+ SAXReader saxReader = new SAXReader();
+ try {
+ Document propDoc = saxReader.read(request.getInputStream());
+// log(propDoc);
+
+ response.setContentType("text/xml");
+ response.setCharacterEncoding("UTF-8");
+ response.setStatus(SC_MULTI_STATUS);
+
+ if (object.exists()) {
+ Document resultDoc = DocumentHelper.createDocument();
+ Element multiStatusResponse = resultDoc.addElement("multistatus", "DAV:");
+ Element responseEl = multiStatusResponse.addElement("response");
+ try {
+ URL url = new URL(getBaseUrl(request), URLEncoder.encode(object.getName().getPath(), "UTF-8"));
+ LOG.debug(url);
+ responseEl.addElement("href").addText(url.toExternalForm());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ Element propstatEl = responseEl.addElement("propstat");
+ Element propEl = propstatEl.addElement("prop");
+
+ Element propertyUpdateEl = propDoc.getRootElement();
+ for (Object elObject : propertyUpdateEl.elements()) {
+ Element el = (Element) elObject;
+ if ("set".equals(el.getName())) {
+ for (Object propObject : el.elements()) {
+ setProperty(propEl, object, (Element) propObject);
+ }
+ } else if ("remove".equals(el.getName())) {
+ for (Object propObject : el.elements()) {
+ removeProperty(propEl, object, (Element) propObject);
+ }
+ }
+ }
+ propstatEl.addElement("status").addText(DavResource.STATUS_403);
+
+// log(resultDoc);
+
+ // write the actual response
+ XMLWriter writer = new XMLWriter(response.getWriter(), OutputFormat.createCompactFormat());
+ writer.write(resultDoc);
+ writer.flush();
+ writer.close();
+ } else {
+ LOG.error(object.getName().getPath() + " NOT FOUND");
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ } catch (DocumentException e) {
+ LOG.error("invalid request: " + e.getMessage());
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ }
+
+ private void setProperty(Element root, FileObject object, Element el) {
+ List propList = el.elements();
+ for (Object propElObject : propList) {
+ Element propEl = (Element) propElObject;
+ for (int i = 0; i < propEl.nodeCount(); i++) {
+ propEl.node(i).detach();
+ }
+ root.add(propEl.detach());
+ }
+ }
+
+ private void removeProperty(Element root, FileObject object, Element el) {
+ setProperty(root, object, el);
+ }
+
+
+}
blob - /dev/null
blob + 3fef13476f5e0d253c3c0fe446adb91682414f74 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/PutHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.LockException;
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.ParseException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class PutHandler extends WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(PutHandler.class);
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+
+ try {
+ if (!LockManager.getInstance().evaluateCondition(object, getIf(request)).result) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ } catch (LockException e) {
+ response.sendError(SC_LOCKED);
+ return;
+ } catch (ParseException e) {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return;
+ }
+ // it is forbidden to write data on a folder
+ if (object.exists() && FileType.FOLDER.equals(object.getType())) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ FileObject parent = object.getParent();
+ if (!parent.exists()) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ if (!FileType.FOLDER.equals(parent.getType())) {
+ response.sendError(HttpServletResponse.SC_CONFLICT);
+ return;
+ }
+
+ InputStream is = request.getInputStream();
+ OutputStream os = object.getContent().getOutputStream();
+ long bytesCopied = Util.copyStream(is, os);
+ String contentLengthHeader = request.getHeader("Content-length");
+ LOG.debug(String.format("sent %d/%s bytes", bytesCopied, contentLengthHeader == null ? "unknown" : contentLengthHeader));
+ os.flush();
+ object.close();
+
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ }
+}
blob - /dev/null
blob + 52760b5e28fb05e62c113b44b1bb3564f2192ff0 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/URLEncoder.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+/**
+ * Encode a URL but leave some special characters in plain text.
+ *
+ * @author Matthias L. Jugel
+ */
+class URLEncoder {
+
+ private static final BitSet keepPlain;
+
+ static {
+ keepPlain = new BitSet(256);
+ int i;
+ for (i = 'a'; i <= 'z'; i++) {
+ keepPlain.set(i);
+ }
+ for (i = 'A'; i <= 'Z'; i++) {
+ keepPlain.set(i);
+ }
+ for (i = '0'; i <= '9'; i++) {
+ keepPlain.set(i);
+ }
+ keepPlain.set('+');
+ keepPlain.set('-');
+ keepPlain.set('_');
+ keepPlain.set('.');
+ keepPlain.set('*');
+ keepPlain.set('/');
+ keepPlain.set(':');
+ }
+
+
+ public static String encode(String s, String enc) throws UnsupportedEncodingException {
+ byte[] buf = s.getBytes(enc);
+ StringBuffer result = new StringBuffer();
+ for (byte aBuf : buf) {
+ int c = (int) aBuf;
+ if (keepPlain.get(c & 0xFF)) {
+ result.append((char) c);
+ } else {
+ result.append('%').append(Integer.toHexString(c & 0xFF).toUpperCase());
+ }
+ }
+ return result.toString();
+ }
+}
blob - /dev/null
blob + 176c5c3b89711ed18bc14acb2012a7c370162406 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/UnlockHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.lock.LockManager;
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class UnlockHandler extends WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(UnlockHandler.class);
+
+ public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ FileObject object = VFSBackend.resolveFile(request.getPathInfo());
+ String lockTokenHeader = request.getHeader("Lock-Token");
+ String lockToken = lockTokenHeader.substring(1, lockTokenHeader.length() - 1);
+ LOG.debug("UNLOCK(" + lockToken + ")");
+
+ if (LockManager.getInstance().releaseLock(object, lockToken)) {
+ response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+ } else {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+}
blob - /dev/null
blob + 3a04b32724c8f5ab5247f0d1cae38cba747cea8a (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/Util.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public class Util {
+
+ private static final SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+ public static String getDateString(long time) {
+ return httpDateFormat.format(new Date(time));
+ }
+
+// public static String getISODateString(long time) {
+// return ""; 4
+// }
+
+
+ public static long copyStream(final InputStream is, final OutputStream os) throws IOException {
+ ReadableByteChannel rbc = Channels.newChannel(is);
+ WritableByteChannel wbc = Channels.newChannel(os);
+
+ int bytesWritten = 0;
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
+ while (rbc.read(buffer) != -1) {
+ buffer.flip();
+ bytesWritten += wbc.write(buffer);
+ buffer.compact();
+ }
+ buffer.flip();
+ while (buffer.hasRemaining()) {
+ bytesWritten += wbc.write(buffer);
+ }
+
+ rbc.close();
+ wbc.close();
+
+ return bytesWritten;
+ }
+}
blob - /dev/null
blob + bca6db1498389193b487c1a5c50753dda989f4c5 (mode 644)
--- /dev/null
+++ modules/webdav/src/main/java/com/thinkberg/webdav/WebdavHandler.java
+/*
+ * Copyright 2007 Matthias L. Jugel.
+ *
+ * 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 com.thinkberg.webdav;
+
+import com.thinkberg.webdav.vfs.VFSBackend;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+
+/**
+ * @author Matthias L. Jugel
+ * @version $Id$
+ */
+public abstract class WebdavHandler {
+ private static final Log LOG = LogFactory.getLog(WebdavHandler.class);
+
+ static final int SC_CREATED = 201;
+ static final int SC_LOCKED = 423;
+ static final int SC_MULTI_STATUS = 207;
+
+ protected static URL getBaseUrl(HttpServletRequest request) {
+ try {
+ String requestUrl = request.getRequestURL().toString();
+ String requestUri = request.getRequestURI();
+ String requestUrlBase = requestUrl.substring(0, requestUrl.length() - requestUri.length());
+ return new URL(requestUrlBase);
+ } catch (MalformedURLException e) {
+ // ignore ...
+ }
+ return null;
+ }
+
+ public abstract void service(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+ /**
+ * Get the depth header value. This value defines how operations
+ * like propfind, move, copy etc. handle collections. A depth value
+ * of 0 will only return the current collection, 1 will return
+ * children too and infinity will recursively operate.
+ *
+ * @param request the servlet request
+ * @return the depth value as 0, 1 or Integer.MAX_VALUE;
+ */
+ int getDepth(HttpServletRequest request) {
+ String depth = request.getHeader("Depth");
+ int depthValue;
+
+ if (null == depth || "infinity".equalsIgnoreCase(depth)) {
+ depthValue = Integer.MAX_VALUE;
+ } else {
+ depthValue = Integer.parseInt(depth);
+ }
+
+ LOG.debug(String.format("request header: Depth: %s", (depthValue == Integer.MAX_VALUE ? "infinity" : depthValue)));
+ return depthValue;
+ }
+
+ /**
+ * Get the overwrite header value, whether to overwrite destination
+ * objects or collections or not.
+ *
+ * @param request the servlet request
+ * @return true or false
+ */
+ boolean getOverwrite(HttpServletRequest request) {
+ String overwrite = request.getHeader("Overwrite");
+ boolean overwriteValue = overwrite == null || "T".equals(overwrite);
+
+ LOG.debug(String.format("request header: Overwrite: %s", overwriteValue));
+ return overwriteValue;
+ }
+
+ /**
+ * Get the destination object or collection. The destination header contains
+ * a URL to the destination which is returned as a file object.
+ *
+ * @param request the servlet request
+ * @return the file object of the destination
+ * @throws FileSystemException if the file system cannot create a file object
+ * @throws MalformedURLException if the url is misformatted
+ */
+ FileObject getDestination(HttpServletRequest request) throws FileSystemException, MalformedURLException {
+ String targetUrlStr = request.getHeader("Destination");
+ FileObject targetObject = null;
+ if (null != targetUrlStr) {
+ URL target = new URL(targetUrlStr);
+ targetObject = VFSBackend.resolveFile(target.getPath());
+ LOG.debug(String.format("request header: Destination: %s", targetObject.getName().getPath()));
+ }
+
+ return targetObject;
+ }
+
+ /**
+ * Get the if header.
+ *
+ * @param request the request
+ * @return the value if the If: header.
+ */
+ String getIf(HttpServletRequest request) {
+ String getIfHeader = request.getHeader("If");
+
+ if (null != getIfHeader) {
+ LOG.debug(String.format("request header: If: '%s'", getIfHeader));
+ }
+ return getIfHeader;
+ }
+
+ /**
+ * Get and parse the timeout header value.
+ *
+ * @param request the request
+ * @return the timeout
+ */
+ long getTimeout(HttpServletRequest request) {
+ String timeout = request.getHeader("Timeout");
+ if (null != timeout) {
+ String[] timeoutValues = timeout.split(",[ ]*");
+ LOG.debug(String.format("request header: Timeout: %s", Arrays.asList(timeoutValues).toString()));
+ if ("infinity".equalsIgnoreCase(timeoutValues[0])) {
+ return -1;
+ } else {
+ return Integer.parseInt(timeoutValues[0].replaceAll("Second-", ""));
+ }
+ }
+ return -1;
+ }
+}
blob - /dev/null
blob + 9483a4df30a0be93e787b2e9400ce60e5aead2f0 (mode 755)
--- /dev/null
+++ modules/webdav/src/test/java/com/thinkberg/webdav/tests/DavLockManagerTest.java
+package com.thinkberg.webdav.tests;
+
+import com.thinkberg.webdav.DavTestCase;
+import com.thinkberg.webdav.lock.Lock;
+import com.thinkberg.webdav.lock.LockConflictException;
+import com.thinkberg.webdav.lock.LockManager;
+
+/**
+ * @author Matthias L. Jugel
+ */
+public class DavLockManagerTest extends DavTestCase {
+ private final String OWNER_STR = "testowner";
+
+ public DavLockManagerTest() {
+ super();
+ }
+
+ public void testAcquireSingleSharedFileLock() {
+ Lock sharedLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ try {
+ LockManager.getInstance().acquireLock(sharedLock);
+ } catch (Exception e) {
+ assertNull(e.getMessage(), e);
+ }
+ }
+
+ public void testAcquireDoubleSharedFileLock() {
+ Lock sharedLock1 = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ Lock sharedLock2 = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR + "1", 0, 3600);
+ try {
+ LockManager.getInstance().acquireLock(sharedLock1);
+ LockManager.getInstance().acquireLock(sharedLock2);
+ } catch (Exception e) {
+ assertNull(e.getMessage(), e);
+ }
+ }
+
+ public void testFailToAcquireExclusiveLockOverSharedLock() {
+ Lock sharedLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ Lock exclusiveLock = new Lock(aFile, Lock.WRITE, Lock.EXCLUSIVE, OWNER_STR, 0, 3600);
+ try {
+ LockManager.getInstance().acquireLock(sharedLock);
+ LockManager.getInstance().acquireLock(exclusiveLock);
+ assertTrue("acquireLock() should fail", false);
+ } catch (Exception e) {
+ assertEquals(LockConflictException.class, e.getClass());
+ }
+ }
+
+ public void testConditionUnmappedFails() throws Exception {
+ final String condition = "<http://cid:8080/litmus/unmapped_url> (<opaquelocktoken:cd6798>)";
+ assertFalse("condition for unmapped resource must fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+ public void testConditionSimpleLockToken() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + ">)";
+ LockManager.getInstance().acquireLock(aLock);
+ assertTrue("condition with existing lock token should not fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+ public void testConditionSimpleLockLokenWrong() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "x>)";
+ LockManager.getInstance().acquireLock(aLock);
+ try {
+ LockManager.getInstance().evaluateCondition(aFile, condition);
+ } catch (LockConflictException e) {
+ assertFalse("condition with wrong lock token must fail on locked resource", e.getLocks().isEmpty());
+ }
+ }
+
+ public void testConditionSimpleLockTokenAndETag() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "> [" + Integer.toHexString(aFile.hashCode()) + "])";
+ LockManager.getInstance().acquireLock(aLock);
+ assertTrue("condition with existing lock token and correct ETag should not fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+ public void testConditionSimpleLockTokenWrongAndETag() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "x> [" + Integer.toHexString(aFile.hashCode()) + "])";
+ LockManager.getInstance().acquireLock(aLock);
+ try {
+ LockManager.getInstance().evaluateCondition(aFile, condition);
+ } catch (LockConflictException e) {
+ assertFalse("condition with non-existing lock token and correct ETag should fail",
+ e.getLocks().isEmpty());
+ }
+ }
+
+ public void testConditionSimpleLockTokenAndETagWrong() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "> [" + Integer.toHexString(aFile.hashCode()) + "x])";
+ LockManager.getInstance().acquireLock(aLock);
+ assertFalse("condition with existing lock token and incorrect ETag should fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+ public void testConditionSimpleLockTokenWrongAndETagWrong() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "x> [" + Integer.toHexString(aFile.hashCode()) + "x])";
+ LockManager.getInstance().acquireLock(aLock);
+ assertFalse("condition with non-existing lock token and incorrect ETag should fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+ public void testConditionSimpleLockTokenWrongAndETagOrSimpleETag() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String eTag = Integer.toHexString(aFile.hashCode());
+ final String condition = "(<" + aLock.getToken() + "x> [" + eTag + "]) ([" + eTag + "])";
+ LockManager.getInstance().acquireLock(aLock);
+ try {
+ LockManager.getInstance().evaluateCondition(aFile, condition);
+ } catch (LockConflictException e) {
+ assertFalse("condition with one correct ETag in list should not fail on locked resource",
+ e.getLocks().isEmpty());
+ }
+ }
+
+ public void testConditionSimpleNegatedLockTokenWrongAndETag() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String eTag = Integer.toHexString(aFile.hashCode());
+ final String condition = "(Not <" + aLock.getToken() + "x> [" + eTag + "])";
+ assertTrue("condition with negated wrong lock token and correct ETag should not fail on unlocked resource",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+
+ public void testConditionMustNotFail() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String condition = "(<" + aLock.getToken() + "x>) (Not <DAV:no-lock>)";
+ assertTrue("using (Not <DAV:no-lock>) in condition list must not fail on unlocked resource",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+
+ public void testComplexConditionWithBogusLockToken() throws Exception {
+ Lock aLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+ final String eTag = Integer.toHexString(aFile.hashCode());
+ final String condition = "(<" + aLock.getToken() + "> [" + eTag + "x]) (Not <DAV:no-lock> [" + eTag + "x])";
+ LockManager.getInstance().acquireLock(aLock);
+ assertFalse("complex condition with bogus eTag should fail",
+ LockManager.getInstance().evaluateCondition(aFile, condition).result);
+ }
+
+
+// assertFalse(lockManager.evaluateCondition(aFile, "</resource1> (<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> [W/\"A weak ETag\"]) ([\"strong ETag\"])");
+// lockManager.evaluateCondition(aFile, "(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>) (Not <DAV:no-lock>)");
+// lockManager.evaluateCondition(aFile, "(Not <urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> <urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092>)");
+// lockManager.evaluateCondition(aFile, "</specs/rfc2518.doc> ([\"4217\"])");
+// lockManager.evaluateCondition(aFile, "</specs/rfc2518.doc> (Not [\"4217\"])");
+// lockManager.evaluateCondition(aFile, "(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>) (Not <DAV:no-lock>) </specs/rfc2518.doc> (Not [\"4217\"])");
+// lockManager.evaluateCondition(aFile, "(<opaquelocktoken:10a098> [10a198]) (Not <DAV:no-lock> [10a198])");
+
+// public void testLockConditionRequiredException() {
+// Lock sharedLock = new Lock(aFile, Lock.WRITE, Lock.SHARED, OWNER_STR, 0, 3600);
+// try {
+// LockManager.getInstance().acquireLock(sharedLock);
+// LockManager.getInstance().checkCondition(aFile, null);
+// assertTrue("checkCondition() should fail", false);
+// } catch (Exception e) {
+// assertEquals(LockConditionRequiredException.class, e.getClass());
+// }
+// }
+}
blob - /dev/null
blob + 2389b60f777cec922576a63c3d4ad792dd4073da (mode 644)
--- /dev/null
+++ modules/webdav/src/test/java/com/thinkberg/webdav/DavTestCase.java
+package com.thinkberg.webdav;
+
+import com.thinkberg.webdav.data.DavResource;
+import com.thinkberg.webdav.data.DavResourceFactory;
+import junit.framework.TestCase;
+import org.apache.commons.vfs.FileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.VFS;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Node;
+
+import java.util.Arrays;
+
+/**
+ * Helper class for DAV tests.
+ *
+ * @author Matthias L. Jugel
+ */
+public class DavTestCase extends TestCase {
+ private static final String PROP_EXISTS = "propstat[status='HTTP/1.1 200 OK']/prop/";
+ private static final String PROP_MISSING = "propstat[status='HTTP/1.1 404 Not Found']/prop/";
+ private static final String EMPTY = "";
+
+ protected FileObject aFile;
+ protected FileObject aDirectory;
+
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ FileSystemManager fsm = VFS.getManager();
+ FileObject fsRoot = fsm.createVirtualFileSystem(fsm.resolveFile("ram:/"));
+ aFile = fsRoot.resolveFile("/file.txt");
+ aFile.delete();
+ aFile.createFile();
+ aDirectory = fsRoot.resolveFile("/folder");
+ aDirectory.delete();
+ aDirectory.createFolder();
+ }
+
+ protected void testPropertyValue(FileObject object, String propertyName, String propertyValue) throws FileSystemException {
+ Element root = serializeDavResource(object, propertyName);
+ assertEquals(propertyValue, selectExistingPropertyValue(root, propertyName));
+ }
+
+ protected void testPropertyNoValue(FileObject object, String propertyName) throws FileSystemException {
+ Element root = serializeDavResource(object, propertyName, true);
+ assertEquals(EMPTY, selectExistingPropertyValue(root, propertyName));
+ }
+
+ protected Node selectExistingProperty(Element root, String propertyName) {
+ return root.selectSingleNode(PROP_EXISTS + propertyName);
+ }
+
+ protected Node selectMissingProperty(Element root, String propertyName) {
+ return root.selectSingleNode(PROP_MISSING + propertyName);
+ }
+
+ protected String selectMissingPropertyName(Element root, String propertyName) {
+ return selectMissingProperty(root, propertyName).getName();
+ }
+
+ protected String selectExistingPropertyValue(Element root, String propertyName) {
+ return selectExistingProperty(root, propertyName).getText();
+ }
+
+ protected Element serializeDavResource(FileObject object, String propertyName) throws FileSystemException {
+ return serializeDavResource(object, propertyName, false);
+ }
+
+ protected Element serializeDavResource(FileObject object, String propertyName, boolean ignoreValues) throws FileSystemException {
+ Element root = DocumentHelper.createElement("root");
+ DavResourceFactory factory = DavResourceFactory.getInstance();
+ DavResource davResource = factory.getDavResource(object);
+ davResource.setIgnoreValues(ignoreValues);
+ davResource.serializeToXml(root, Arrays.asList(propertyName));
+ return root;
+ }
+
+}