commit 09048cb93e22e7d59bcf356673986574a7979a30 from: leo date: Mon Jan 26 20:37:51 2009 UTC refactored to separate dav and vfs into modules commit - c4610f866124bd70c2c5eee691a35ecd94ab2487 commit + 09048cb93e22e7d59bcf356673986574a7979a30 blob - /dev/null blob + 6ce51b79ebc9ab0be5de9ae2c702d8799a4835f6 (mode 644) --- /dev/null +++ modules/webdav/src/main/java/com/thinkberg/webdav/CopyHandler.java @@ -0,0 +1,40 @@ +/* + * 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 @@ -0,0 +1,96 @@ +/* + * 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 @@ -0,0 +1,86 @@ +/* + * 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 @@ -0,0 +1,63 @@ +/* + * 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 @@ -0,0 +1,64 @@ +/* + * 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 @@ -0,0 +1,46 @@ +/* + * 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 @@ -0,0 +1,160 @@ +/* + * 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 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 @@ -0,0 +1,78 @@ +/* + * 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 @@ -0,0 +1,32 @@ +/* + * 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 @@ -0,0 +1,52 @@ +/* + * 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 @@ -0,0 +1,32 @@ +/* + * 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 @@ -0,0 +1,145 @@ +/* + * 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 requestedProperties = new ArrayList(); + 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 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 @@ -0,0 +1,138 @@ +/* + * 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 @@ -0,0 +1,83 @@ +/* + * 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 @@ -0,0 +1,66 @@ +/* + * 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 @@ -0,0 +1,48 @@ +/* + * 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 @@ -0,0 +1,68 @@ +/* + * 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 @@ -0,0 +1,150 @@ +/* + * 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 @@ -0,0 +1,169 @@ +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 = " ()"; + 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 )"; + assertTrue("using (Not ) 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 [" + eTag + "x])"; + LockManager.getInstance().acquireLock(aLock); + assertFalse("complex condition with bogus eTag should fail", + LockManager.getInstance().evaluateCondition(aFile, condition).result); + } + + +// assertFalse(lockManager.evaluateCondition(aFile, " ( [W/\"A weak ETag\"]) ([\"strong ETag\"])"); +// lockManager.evaluateCondition(aFile, "() (Not )"); +// lockManager.evaluateCondition(aFile, "(Not )"); +// lockManager.evaluateCondition(aFile, " ([\"4217\"])"); +// lockManager.evaluateCondition(aFile, " (Not [\"4217\"])"); +// lockManager.evaluateCondition(aFile, "() (Not ) (Not [\"4217\"])"); +// lockManager.evaluateCondition(aFile, "( [10a198]) (Not [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 @@ -0,0 +1,81 @@ +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; + } + +}